The Bug
There is a global buffer overflow in JS_ReadObjectRec function.
ASAN Report
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 311050782
INFO: Loaded 1 modules (80420 inline 8-bit counters): 80420 [0x5584ea629bc0, 0x5584ea63d5e4),
INFO: Loaded 1 PC tables (80420 PCs): 80420 [0x5584ea63d5e8,0x5584ea777828),
./fuzz: Running 1 inputs 1 time(s) each.
Running: crash-420240802a9692d300d1f67777e255373bb31083
quickjs.c:37040:32: runtime error: index 128 out of bounds for type 'const char *const[24]'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior quickjs.c:37040:32 in
=================================================================
==2943628==ERROR: AddressSanitizer: global-buffer-overflow on address 0x5584ea53ace0 at pc 0x5584ea1bf8c2 bp 0x7ffcc37a9cd0 sp 0x7ffcc37a9cc8
READ of size 8 at 0x5584ea53ace0 thread T0
#0 0x5584ea1bf8c1 in JS_ReadObjectRec /home/quickjs-ng/quickjs/./quickjs.c:37040:32
#1 0x5584ea1bcad4 in JS_ReadObject2 /home/quickjs-ng/quickjs/./quickjs.c:37260:15
#2 0x5584ea230e88 in JS_ReadObject /home/quickjs-ng/quickjs/./quickjs.c:37275:12
#3 0x5584ea230e88 in LLVMFuzzerTestOneInput /home/quickjs-ng/quickjs/fuzz.c:18:19
#4 0x5584e9fe1663 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/quickjs-ng/quickjs/fuzz+0x373663) (BuildId: 7a87fb5d1bd097c8db62c64f6f9c0cad19fffa70)
#5 0x5584e9fcb3df in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/quickjs-ng/quickjs/fuzz+0x35d3df) (BuildId: 7a87fb5d1bd097c8db62c64f6f9c0cad19fffa70)
#6 0x5584e9fd1136 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/quickjs-ng/quickjs/fuzz+0x363136) (BuildId: 7a87fb5d1bd097c8db62c64f6f9c0cad19fffa70)
#7 0x5584e9ffaf52 in main (/home/quickjs-ng/quickjs/fuzz+0x38cf52) (BuildId: 7a87fb5d1bd097c8db62c64f6f9c0cad19fffa70)
#8 0x7fd277ecfd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#9 0x7fd277ecfe3f in __libc_start_main csu/../csu/libc-start.c:392:3
#10 0x5584e9fc5ca4 in _start (/home/quickjs-ng/quickjs/fuzz+0x357ca4) (BuildId: 7a87fb5d1bd097c8db62c64f6f9c0cad19fffa70)
0x5584ea53ace0 is located 32 bytes to the left of global variable 'js_regexp_string_iterator_proto_funcs' defined in './quickjs.c:46455:35' (0x5584ea53ad00) of size 64
0x5584ea53ace0 is located 0 bytes to the right of global variable 'js_regexp_funcs' defined in './quickjs.c:46428:35' (0x5584ea53aca0) of size 64
SUMMARY: AddressSanitizer: global-buffer-overflow /home/quickjs-ng/quickjs/./quickjs.c:37040:32 in JS_ReadObjectRec
Shadow bytes around the buggy address:
0x0ab11d49f540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ab11d49f550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ab11d49f560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ab11d49f570: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ab11d49f580: 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
=>0x0ab11d49f590: f9 f9 f9 f9 00 00 00 00 00 00 00 00[f9]f9 f9 f9
0x0ab11d49f5a0: 00 00 00 00 00 00 00 00 f9 f9 f9 f9 00 00 00 00
0x0ab11d49f5b0: f9 f9 f9 f9 00 00 00 00 00 00 00 00 00 00 00 00
0x0ab11d49f5c0: f9 f9 f9 f9 00 00 00 00 00 00 00 00 00 00 00 00
0x0ab11d49f5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ab11d49f5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==2943628==ABORTING
Description
In the JS_ReadObjectRec function, an out-of-bounds array access occurs when processing invalid tag values in binary data. The function reads a tag value from user input and directly uses it as an index into the bc_tag_str array without bounds checking.
Code
static JSValue JS_ReadObjectRec(BCReaderState *s)
{
JSContext *ctx = s->ctx;
uint8_t tag;
JSValue obj = JS_UNDEFINED;
if (js_check_stack_overflow(ctx->rt, 0))
return JS_ThrowStackOverflow(ctx);
if (bc_get_u8(s, &tag)) // #1 <-- tag is being read here
return JS_EXCEPTION;
bc_read_trace(s, "%s {\n", bc_tag_str[tag]); // #2 <-- tag is being used here
...
}
Reproducing
fuzz.c:
// clang -g -O1 -fsanitize=fuzzer -o fuzz fuzz.c
#include "quickjs.h"
#include "quickjs.c"
#include "cutils.c"
#include "libregexp.c"
#include "libunicode.c"
#include "xsum.c"
#include <stdlib.h>
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
{
JSRuntime *rt = JS_NewRuntime();
if (!rt)
exit(1);
JSContext *ctx = JS_NewContext(rt);
if (!ctx)
exit(1);
JSValue val = JS_ReadObject(ctx, buf, len, /*flags*/0);
JS_FreeValue(ctx, val);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
and use the following input:
echo -ne "\x14\x00\x80\x00\x26\x00\xFF\x7F\xAB" > crashing_input.bin
The Bug
There is a global buffer overflow in JS_ReadObjectRec function.
ASAN Report
Description
In the JS_ReadObjectRec function, an out-of-bounds array access occurs when processing invalid tag values in binary data. The function reads a tag value from user input and directly uses it as an index into the bc_tag_str array without bounds checking.
Code
Reproducing
fuzz.c:
and use the following input: