Skip to content

Fix stale typed array/fast array access in JS_PrintValue after reentrant callback - #560

Open
iliasabk wants to merge 1 commit into
bellard:masterfrom
iliasabk:fix/print-reentrant-detach
Open

iliasabk wants to merge 1 commit into
bellard:masterfrom
iliasabk:fix/print-reentrant-detach

Conversation

@iliasabk

Copy link
Copy Markdown

Issue

Fixes #558JS_PrintValue() reads stale/freed TypedArray storage when an embedder-supplied write callback re-enters the context and detaches the backing ArrayBuffer mid-print (heap-use-after-free / NULL-deref, reported by Sn0wyDay with an ASan trace).

Root cause

In js_print_object(), the TypedArray print loop cached the element count in len1 and materialized ptr before calling js_print_comma(), which invokes the embedder's write callback. If that callback calls JS_DetachArrayBuffer() (or otherwise mutates the value being printed), the subsequent *ptr dereference touched freed storage.

The fast-array print loop directly above has the same reentrancy flaw: p->u.array.u.values[i] was read after the output callback, so a callback that shrinks or frees the array (e.g. via JS_SetProperty on length) left a dangling element access.

Fix

Both loops now treat the live object state as volatile across every output call:

  • Typed arrays: the live p->u.array.count is rechecked each iteration (it drops to 0 on detach), and the element is copied into a local variable before js_print_comma() runs — the value printed is the one read while the buffer was still valid. Output then proceeds from the copy, so no stale pointer is ever dereferenced.
  • Fast arrays: same live-count recheck, plus JS_DupValueRT()/JS_FreeValueRT() around the output so a reentrant shrink cannot free the element mid-print.

The change is confined to the two element loops; the header/footer prints and all other object classes are untouched.

Verification

Reproducer: embedder write callback that calls JS_DetachArrayBuffer() once the first element has been emitted.

  • Unpatched (04be246): ASan heap-use-after-free read in js_print_object() (freed by JS_DetachArrayBuffer).
  • Patched: prints Uint8Array(5) [ 1, 2 ] — the element copied before the detaching callback is still emitted, then the loop terminates cleanly. No sanitizer findings.
  • All 12 TypedArray classes (Uint8ClampedFloat64, incl. BigInt64/BigUint64/Float16) verified with the same trigger.
  • Fast-array variant (callback shrinks a.length mid-print): [ 1, 2, <3 empty items> ], no crash.
  • make test: all suites pass.

…ant callback

JS_PrintValue's write callback can re-enter the context and detach the
backing ArrayBuffer (JS_DetachArrayBuffer) or mutate a fast array while
js_print_object() is iterating with a cached element count. The element
pointer/value was then read after the callback returned, dereferencing
freed storage.

- typed arrays: recheck the live element count each iteration and copy
  the element into a local before emitting output through the callback
- fast arrays: same live-count recheck; hold a JSValue reference to the
  element across the output so it cannot be freed mid-print

Reproduced with an embedder write callback that detaches the
ArrayBuffer after the first element: ASan reported a
heap-use-after-free read in js_print_object(); after the fix the print
stops cleanly for all 12 typed array classes and for shrunk fast
arrays. make test passes.

Fixes bellard#558
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JS_PrintValue can read detached TypedArray storage after a reentrant write callback

1 participant