A crafted sequence of bytes triggers memory write past the bounds of a heap allocated buffer.
Hi, creating the public issue here since the issue affects unreleased master branch only. Originally it was reported as a private issue https://gitlab.freedesktop.org/uchardet/uchardet/-/issues/33
Tested Version
Master branch, post 0.0.8.
Details
Heap buffer write overflow in nsUTF8Prober::HandleData
The out of bounds write happens in nsUTF8Prober::HandleData
[1] when *codePointBufferIdx
becomes bigger than the size of the buffer *codePointBuffer
. The buffer can be zero or 1024
. A dedicated codePointBufferSize
is allocated for each codepoint prober. However it is not passed to the function as an argument [2] and while the index is incremented [1] in a loop [2] the function has no knowledge about the true size of the buffer.
nsProbingState nsUTF8Prober::HandleData(const char* aBuf, PRUint32 aLen,
int** codePointBuffer,
int* codePointBufferIdx) // [2]
{
PRUint32 codingState;
for (PRUint32 i = 0; i < aLen; i++) // [3]
{
...
if (codingState == eStart)
{
...
(*codePointBuffer)[(*codePointBufferIdx)++] = currentCodePoint; // [1]
currentCodePoint = 0;
}
else
{
currentCodePoint = ((0xff & aBuf[i]) & 0x3fu) | (currentCodePoint << 6);
}
}
...
}
Update: after ab1d2f11 it happens in slightly different place: if (keepNext) {
block (line 383)
Impact
This issue may lead to an arbitrary code execution.
Resources
crash-6c48314f16273784c008ae3c580a8e636a5a17a1-minimized
To reproduce the issue:
- Make ASAN build
- Run the following program to hit the breakpoint or out of bounds access with ASAN
FILE *f = fopen("crash-6c48314f16273784c008ae3c580a8e636a5a17a1-minimized", "rb");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = malloc(fsize);
fread(string, fsize, 1, f);
fclose(f);
uchardet_t ud = uchardet_new();
uchardet_handle_data(ud, buf, fsize);
The output when built with ASAN:
==12==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x621000003900 at pc 0x000000593cf3 bp 0x7fff9662e510 sp 0x7fff9662e508
WRITE of size 4 at 0x621000003900 thread T0
SCARINESS: 36 (4-byte-write-heap-buffer-overflow)
#0 0x593cf2 in nsUTF8Prober::HandleData(char const*, unsigned int, int**, int*) /src/uchardet/src/nsUTF8Prober.cpp:83:51
#1 0x584e95 in nsMBCSGroupProber::HandleData(char const*, unsigned int, int**, int*) /src/uchardet/src/nsMBCSGroupProber.cpp:383:27
#2 0x57e3cd in nsUniversalDetector::HandleData(char const*, unsigned int) /src/uchardet/src/nsUniversalDetector.cpp:275:34
#3 0x5786ae in uchardet_handle_data /src/uchardet/src/uchardet.cpp:220:63
#4 0x57842c in LLVMFuzzerTestOneInput /src/uchardet/fuzz/fuzz_uchardet.c:6:2
#5 0x449e23 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:611:15
#6 0x435582 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:324:6
#7 0x43ae2c in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:860:9
#8 0x464362 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#9 0x7ff3b4264082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
#10 0x42b74d in _start (/out/fuzz_uchardet+0x42b74d)
DEDUP_TOKEN: nsUTF8Prober::HandleData(char const*, unsigned int, int**, int*)--nsMBCSGroupProber::HandleData(char const*, unsigned int, int**, int*)--nsUniversalDetector::HandleData(char const*, unsigned int)
0x621000003900 is located 0 bytes to the right of 4096-byte region [0x621000002900,0x621000003900)
allocated by thread T0 here:
#0 0x575f0d in operator new[](unsigned long) /src/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:98:3
#1 0x583c2b in nsMBCSGroupProber::Reset() /src/uchardet/src/nsMBCSGroupProber.cpp:256:30
#2 0x5820a5 in nsMBCSGroupProber::nsMBCSGroupProber(unsigned int) /src/uchardet/src/nsMBCSGroupProber.cpp:141:3
#3 0x57e132 in nsUniversalDetector::HandleData(char const*, unsigned int) /src/uchardet/src/nsUniversalDetector.cpp:205:36
#4 0x5786ae in uchardet_handle_data /src/uchardet/src/uchardet.cpp:220:63
#5 0x57842c in LLVMFuzzerTestOneInput /src/uchardet/fuzz/fuzz_uchardet.c:6:2
#6 0x449e23 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:611:15
#7 0x435582 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:324:6
#8 0x43ae2c in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:860:9
#9 0x464362 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#10 0x7ff3b4264082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
DEDUP_TOKEN: operator new[](unsigned long)--nsMBCSGroupProber::Reset()--nsMBCSGroupProber::nsMBCSGroupProber(unsigned int)
SUMMARY: AddressSanitizer: heap-buffer-overflow /src/uchardet/src/nsUTF8Prober.cpp:83:51 in nsUTF8Prober::HandleData(char const*, unsigned int, int**, int*)
Shadow bytes around the buggy address:
0x0c427fff86d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c427fff86e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c427fff86f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c427fff8700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c427fff8710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c427fff8720:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c427fff8730: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c427fff8740: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c427fff8750: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c427fff8760: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c427fff8770: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==12==ABORTING