A heap-buffer-overflow was discovered in the modbus_write_bits function. This issue can be triggered when the function is fed with specially crafted input, which leads to out-of-bounds read and can potentially cause a crash or other unintended behaviors.
Actual behavior if applicable
The function modbus_write_bits exhibits undefined behavior and crashes when processing certain inputs where the nb (number of bits) parameter leads to an out-of-bounds read of the input buffer. This is evident from the ASan reports indicating a heap-buffer-overflow.
Expected behavior or suggestion
The function should safely handle all input ranges and sizes without causing buffer overflows.
Steps to reproduce the behavior (commands or source code)
Fuzz driver I used:
#include <fuzzer/FuzzedDataProvider.h>
#include "modbus.h"
#include <cstdint>
#include <cstddef>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
modbus_t* ctx = modbus_new_tcp("127.0.0.1", 1502); // port as 502 is also ok.
if (ctx == nullptr) {
return 0;
}
FuzzedDataProvider provider(data, size);
int addr = provider.ConsumeIntegral<int>();
int nb = provider.ConsumeIntegral<int>();
std::vector<uint8_t> bits = provider.ConsumeBytes<uint8_t>(nb);
int ret = modbus_write_bits(ctx, addr, nb, bits.data());
modbus_close(ctx);
modbus_free(ctx);
return ret;
}
DEDUP_TOKEN: modbus_write_bits--LLVMFuzzerTestOneInput--fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long)
[1m[32m0x60200000b571 is located 0 bytes to the right of 1-byte region [0x60200000b570,0x60200000b571)
[1m[0m[1m[35mallocated by thread T0 here:[1m[0m
0 0x569ead in operator new(unsigned long) /src/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
#1 0x56cb55 in __libcpp_operator_new<unsigned long> /usr/local/bin/../include/c++/v1/new:245:10
#2 0x56cb55 in __libcpp_allocate /usr/local/bin/../include/c++/v1/new:271:10
#3 0x56cb55 in allocate /usr/local/bin/../include/c++/v1/__memory/allocator.h:105:38
#4 0x56cb55 in allocate /usr/local/bin/../include/c++/v1/__memory/allocator_traits.h:262:20
#5 0x56cb55 in __vallocate /usr/local/bin/../include/c++/v1/vector:931:37
#6 0x56cb55 in vector /usr/local/bin/../include/c++/v1/vector:1062:9
#7 0x56cb55 in std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > FuzzedDataProvider::ConsumeBytes<unsigned char>(unsigned long, unsigned long) /usr/local/lib/clang/15.0.0/include/fuzzer/FuzzedDataProvider.h:361:18
#8 0x56c78c in ConsumeBytes<unsigned char> /usr/local/lib/clang/15.0.0/include/fuzzer/FuzzedDataProvider.h:110:10
#9 0x56c78c in LLVMFuzzerTestOneInput /src/libmodbus/fuzz/FuzzClient.cpp:16:40
#10 0x43ded3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:611:15
#11 0x43d6ba in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool, bool*) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:514:3
#12 0x43ed89 in fuzzer::Fuzzer::MutateAndTestOne() /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:757:19
#13 0x43fa55 in fuzzer::Fuzzer::Loop(std::__Fuzzer::vector<fuzzer::SizedFile, std::__Fuzzer::allocator<fuzzer::SizedFile> >&) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:895:5
#14 0x42edbf in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:912:6
#15 0x458412 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#16 0x7fe3e6c85082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 87b331c034a6458c64ce09c03939e947212e18ce)
or use the attached file for reproducing:
[crash-05ddbd1d0695e401044d58f3388298c1138958d4.txt](https://github.com/stephane/libmodbus/files/15141098/crash-05ddbd1d0695e401044d58f3388298c1138958d4.txt)
## libmodbus output with debug mode enabled
Client connection accepted from 127.0.0.1.
Waiting for an indication...
ERROR Connection timed out: select
<00><01><02><00><80><04><01><01><2A>Quit the loop: Connection timed out
```
- The crashed line is at:
https://github.com/stephane/libmodbus/blob/5c14f13944ec7394a61a7680dcb462056c4321e3/src/modbus.c#L1461
libmodbus version
libmodbus 3.1.10
OS and/or distribution
Linux, Ubuntu 20.04
Environment
amd64
Description
A heap-buffer-overflow was discovered in the
modbus_write_bits
function. This issue can be triggered when the function is fed with specially crafted input, which leads to out-of-bounds read and can potentially cause a crash or other unintended behaviors.Actual behavior if applicable
The function
modbus_write_bits
exhibits undefined behavior and crashes when processing certain inputs where the nb (number of bits) parameter leads to an out-of-bounds read of the input buffer. This is evident from the ASan reports indicating a heap-buffer-overflow.Expected behavior or suggestion
The function should safely handle all input ranges and sizes without causing buffer overflows.
Steps to reproduce the behavior (commands or source code)
DEDUP_TOKEN: modbus_write_bits--LLVMFuzzerTestOneInput--fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) [1m[32m0x60200000b571 is located 0 bytes to the right of 1-byte region [0x60200000b570,0x60200000b571) [1m[0m[1m[35mallocated by thread T0 here:[1m[0m
0 0x569ead in operator new(unsigned long) /src/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
DEDUP_TOKEN: operator new(unsigned long)--libcpp_operator_new-- libcpp_allocate
SUMMARY: AddressSanitizer: heap-buffer-overflow /src/libmodbus/src/modbus.c:1461:17 in modbus_write_bits
Shadow bytes around the buggy address:
0x0c047fff9650: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[35mfd[1m[0m
0x0c047fff9660: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[35mfd[1m[0m
0x0c047fff9670: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m
0x0c047fff9680: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m
0x0c047fff9690: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m
=>0x0c047fff96a0: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[35mfd[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m00[1m[0m [1m[0m01[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m[[1m[0m01[1m[0m][1m[31mfa[1m[0m
0x0c047fff96b0: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
0x0c047fff96c0: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
0x0c047fff96d0: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
0x0c047fff96e0: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
0x0c047fff96f0: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: [1m[0m00[1m[0m
Partially addressable: [1m[0m01[1m[0m [1m[0m02[1m[0m [1m[0m03[1m[0m [1m[0m04[1m[0m [1m[0m05[1m[0m [1m[0m06[1m[0m [1m[0m07[1m[0m
Heap left redzone: [1m[31mfa[1m[0m
Freed heap region: [1m[35mfd[1m[0m
Stack left redzone: [1m[31mf1[1m[0m
Stack mid redzone: [1m[31mf2[1m[0m
Stack right redzone: [1m[31mf3[1m[0m
Stack after return: [1m[35mf5[1m[0m
Stack use after scope: [1m[35mf8[1m[0m
Global redzone: [1m[31mf9[1m[0m
Global init order: [1m[36mf6[1m[0m
Poisoned by user: [1m[34mf7[1m[0m
Container overflow: [1m[34mfc[1m[0m
Array cookie: [1m[31mac[1m[0m
Intra object redzone: [1m[33mbb[1m[0m
ASan internal: [1m[33mfe[1m[0m
Left alloca redzone: [1m[34mca[1m[0m
Right alloca redzone: [1m[34mcb[1m[0m
==13==ABORTING
MS: 2 ChangeBit-ChangeBit-; base unit: c68bed0f37ab64033b5e4e6c6e801a00b8c8f87c
0x0,0x1,0x2,0x0,0x80,0x4,0x1,0x1,0x2a,
\000\001\002\000\200\004\001\001*
artifact_prefix='./'; Test unit written to ./crash-05ddbd1d0695e401044d58f3388298c1138958d4
Base64: AAECAIAEAQEq
stat::number_of_executed_units: 1401
stat::average_exec_per_sec: 700
stat::new_units_added: 17
stat::slowest_unit_time_sec: 0
stat::peak_rss_mb: 32
./tests/unit-test-server echo "AAECAIAEAQEq" | base64 -d | nc 127.0.0.1 1502
Client connection accepted from 127.0.0.1. Waiting for an indication... ERROR Connection timed out: select
<00><01><02><00><80><04><01><01><2A>Quit the loop: Connection timed out ``` - The crashed line is at: https://github.com/stephane/libmodbus/blob/5c14f13944ec7394a61a7680dcb462056c4321e3/src/modbus.c#L1461