Skip to content

Null pointer passed to memcpy in libunicode.c:1192 via String.prototype.normalize, reachable from pure JavaScript #500

@Sebasteuo

Description

@Sebasteuo

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])

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions