Description
QuickJS (version 2025-09-13, commit d7ae12a) passes a NULL pointer to memcpy() in libunicode.c:1192 when String.prototype.normalize("NFC") is called on an empty string created by String.fromCharCode() with zero arguments. This is undefined behavior per the C standard (C11 §7.1.4: "If an argument to a function has an invalid value, the behavior is undefined"), even when the size argument is 0.
Root Cause
In libunicode.c:1192, the NFC fast path executes:
buf = (int *)dbuf->buf;
memcpy(buf, src, src_len * sizeof(int));
When the input string is empty (src_len == 0), src is NULL. While src_len * sizeof(int) evaluates to 0, the C standard states that NULL must not be passed to memcpy regardless of the length parameter. Compilers are permitted to optimize based on the assumption that memcpy arguments are non-null.
PoC
One line of JavaScript — no crafted bytecode, no special setup:
String.fromCharCode(...Array(0).fill(0xD800)).normalize("NFC")
Reproduction:
# Build QuickJS with UBSan (patch Makefile CFLAGS_OPT, disable -flto)
./qjs -e 'String.fromCharCode(...Array(0).fill(0xD800)).normalize("NFC")'
Output:
libunicode.c:1192:9: runtime error: null pointer passed as argument 1, which is declared to never be null
Suggested Fix
Add a guard before the memcpy call:
if (src_len == 0) {
*pdst = (uint32_t *)dbuf->buf;
return 0;
}
Impact
- Undefined Behavior: Per the C standard, this is UB that compilers may exploit for aggressive optimization, potentially leading to unexpected code generation on different platforms/optimization levels.
- Denial of Service: On platforms or toolchains where NULL dereference is trapped, this causes a crash.
- Reachable from pure JavaScript: Any application embedding QuickJS that processes user-supplied strings with
.normalize() is affected. No crafted bytecode required.
Credit
Found by Sebastián Alba Vives (GitHub: [@Sebasteuo])
Description
QuickJS (version 2025-09-13, commit d7ae12a) passes a NULL pointer to
memcpy()inlibunicode.c:1192whenString.prototype.normalize("NFC")is called on an empty string created byString.fromCharCode()with zero arguments. This is undefined behavior per the C standard (C11 §7.1.4: "If an argument to a function has an invalid value, the behavior is undefined"), even when the size argument is 0.Root Cause
In
libunicode.c:1192, the NFC fast path executes:When the input string is empty (
src_len == 0),srcis NULL. Whilesrc_len * sizeof(int)evaluates to 0, the C standard states that NULL must not be passed tomemcpyregardless of the length parameter. Compilers are permitted to optimize based on the assumption thatmemcpyarguments are non-null.PoC
One line of JavaScript — no crafted bytecode, no special setup:
Reproduction:
Output:
libunicode.c:1192:9: runtime error: null pointer passed as argument 1, which is declared to never be null
Suggested Fix
Add a guard before the
memcpycall:Impact
.normalize()is affected. No crafted bytecode required.Credit
Found by Sebastián Alba Vives (GitHub: [@Sebasteuo])