Affected code: quickjs.c, js_promise_withResolvers() at current master 954dc53.
A single ordinary JavaScript expression can trigger a native heap use-after-free when a property definition fails under the runtime memory limit:
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0:halt_on_error=1 \
./qjs --memory-limit 119434b -e "Promise.withResolvers()"
The exact limit is build-dependent. On the tested Linux x86_64 ASan build, 119434b through 119438b reproduce consistently. ASan reports a READ of size 4 in JS_FreeValueRT, called by js_promise_withResolvers at quickjs.c:56061. The object was freed by JS_DefinePropertyValue at quickjs.c:11538, called from the reject-property definition at quickjs.c:56054.
Expected: Promise.withResolvers() returns or throws OOM cleanly.
Actual: the host process performs a UAF read and aborts under ASan.
Root cause: JS_DefinePropertyValue() consumes and releases val even when it returns an error. js_promise_withResolvers() clears result_promise and resolving_funcs[0] only after successful calls, and never clears resolving_funcs[1]. On failure, exception cleanup releases the stale local again. The promise, resolve, and reject property paths are all statically affected; the command above directly reproduces the reject path.
This was introduced by b0eb4fe (#1173) and is distinct from #1613 / GHSA-crgj-9jv8-xfhq, which fixed stale output from js_create_resolving_functions() before v0.16.0. This reproduces on v0.16.1.
The ownership fix is to set each local to JS_UNDEFINED immediately after the consuming JS_DefinePropertyValue call, before checking its return value. I tested that change in a separate ASan build: no ASan findings across 119400b..119460b or sampled handled-OOM limits, git diff --check passed, and the project suite reported 0/89 errors (9 excluded). A deterministic allocation-failure regression harness does not appear to exist in the current suite.
Reported publicly per SECURITY.md guidance for AI-assisted source findings; the root cause, reproducer, ASan trace, affected history, and fix were independently verified.
Affected code: quickjs.c, js_promise_withResolvers() at current master 954dc53.
A single ordinary JavaScript expression can trigger a native heap use-after-free when a property definition fails under the runtime memory limit:
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0:halt_on_error=1 \ ./qjs --memory-limit 119434b -e "Promise.withResolvers()"The exact limit is build-dependent. On the tested Linux x86_64 ASan build, 119434b through 119438b reproduce consistently. ASan reports a READ of size 4 in JS_FreeValueRT, called by js_promise_withResolvers at quickjs.c:56061. The object was freed by JS_DefinePropertyValue at quickjs.c:11538, called from the reject-property definition at quickjs.c:56054.
Expected: Promise.withResolvers() returns or throws OOM cleanly.
Actual: the host process performs a UAF read and aborts under ASan.
Root cause: JS_DefinePropertyValue() consumes and releases
valeven when it returns an error. js_promise_withResolvers() clears result_promise and resolving_funcs[0] only after successful calls, and never clears resolving_funcs[1]. On failure, exception cleanup releases the stale local again. The promise, resolve, and reject property paths are all statically affected; the command above directly reproduces the reject path.This was introduced by b0eb4fe (#1173) and is distinct from #1613 / GHSA-crgj-9jv8-xfhq, which fixed stale output from js_create_resolving_functions() before v0.16.0. This reproduces on v0.16.1.
The ownership fix is to set each local to JS_UNDEFINED immediately after the consuming JS_DefinePropertyValue call, before checking its return value. I tested that change in a separate ASan build: no ASan findings across 119400b..119460b or sampled handled-OOM limits, git diff --check passed, and the project suite reported 0/89 errors (9 excluded). A deterministic allocation-failure regression harness does not appear to exist in the current suite.
Reported publicly per SECURITY.md guidance for AI-assisted source findings; the root cause, reproducer, ASan trace, affected history, and fix were independently verified.