ReviewOS

zig-utils/zig-js

Runtime: finish ArrayBuffer detach/resize race survivor semantics

#25
Closed chris opened this 9 days ago ·
9 days ago

Child tracker from #11/#12/#1.

Goal

Finish the portable ArrayBuffer detach/resize survivor and typed-array race semantics that matter for zig-js, while keeping non-portable JSC reference harness assumptions explicitly classified.

Current PR-249 reference-only evidence

  • cve/mc-life-creator-thread-dies.js remains reference-only because the harness constructs fresh Int32Array views from an ArrayBuffer after the source has already been transferred/detached. zig-js keeps the detached-buffer TypedArray constructor TypeError there.
  • cve/mc-df-arraycopy-relabel.js remains reference-only because it depends on a JSC butterfly verification shell option and a JSC storage-shape assumption. The current probe still fails with RangeError: offset is out of bounds.

Portable behavior already covered

zig-js-owned witnesses cover the portable subsets:

  • creator-owned SharedArrayBuffer / ArrayBuffer storage survives creator Thread exit, GC pressure, resize, and transfer;
  • structured-clone transfer detaches the source and preserves the moved buffer for sibling observers;
  • TypedArray.prototype.set snapshots array-like source length once, so growth before the snapshot can reject when it no longer fits, while growth after the snapshot does not extend the copy;
  • DataView and TypedArray constructor/order tests cover detach checks, resizable-buffer snapshots, and parallel resize serialization.

Remaining work

  • Add more focused zig-js-owned tests if a portable detach/resize survivor rule is not already covered.
  • Keep cve/mc-df-arraycopy-relabel.js classified unless zig-js intentionally grows a compatibility harness for the JSC butterfly verification assumption.
  • Keep cve/mc-life-creator-thread-dies.js classified unless the reference harness stops relying on fresh-view construction after transfer.
  • Re-run zig build threads-reference-probes when behavior changes and promote/reclassify based on current evidence.

Acceptance sketch

  • Portable behavior is covered by focused unit/no-GIL/threadfuzz tests.
  • JSC-specific assumptions remain documented instead of cloned accidentally.
  • #11 audit categories and promotion probes stay current.
9 days ago

Pushed 2bd296c (test(threads): cover creator-owned buffer survival) as the portable #25 witness slice.\n\nWhat it covers:\n- a child Thread creates SharedArrayBuffer and resizable ArrayBuffer storage, then exits;\n- main forces GC after creator exit;\n- sibling Threads read both buffers after the creator is gone;\n- the surviving ArrayBuffer is resized and then transferred;\n- the old source is verified detached and the moved buffer is read from another Thread.\n\nDocs now point the reference-only audit at this focused unit witness plus threadfuzz creatorbuffers for the stable subset behind cve/mc-life-creator-thread-dies.js.\n\nWhat this intentionally does not claim: it does not promote mc-life-creator-thread-dies.js as-is, because that reference file still includes detach/view-construction assumptions that zig-js correctly rejects with TypeError when constructing a TypedArray on an already detached buffer. It also does not change mc-df-arraycopy-relabel.js; the current RangeError there is still the typed-array set length-race blocker documented in the audit.\n\nFocused validation:\n- zig build test -Dtest-filter=creator-owned ArrayBuffer storage --summary none\n- /Users/chris/.bun/bin/bun run docs:build\n- git diff --check

9 days ago

Pushed 7ef9679 (docs(threads): clarify detached buffer probe) as a #25 classification slice.\n\nThe key finding: the remaining failure in cve/mc-life-creator-thread-dies.js is the reference harness constructing fresh Int32Array views from ab after main has already transferred/detached that ArrayBuffer. zig-js keeps the detached-buffer TypedArray constructor TypeError there, which is the right engine contract; we should not weaken that just to make the CVE harness pass as-is.\n\nUpdated:\n- tools/threads-reference-audit.py now classifies the case as a detached ArrayBuffer fresh-view construction race, while noting the portable creator-owned buffer survival subset is already covered.\n- docs/threads/testing.md explains the non-promotable detached-source construction assumption and points at the existing unit/threadfuzz witnesses for the portable storage-lifetime behavior.\n- docs/threads/limits.md now names the remaining reference assumption more precisely.\n\nValidation:\n- python3 tools/threads-reference-audit.py --format markdown\n- python3 tools/threads-reference-audit.py --format json\n- zig build threads-reference-audit --summary all\n- git diff --check

9 days ago

Follow-up on the other #25 promotion probe, cve/mc-df-arraycopy-relabel.js: I re-ran it after 7ef9679 and confirmed it still fails with the documented RangeError: offset is out of bounds.\n\nReading the harness against zig-js's TypedArray.prototype.set path, the immediate failure shape is not evidence of a bad raw-copy/butterfly safety issue in zig-js. The writer intentionally appends past LEN on the shared JS array; under zig-js, dst.set(a, 0) can observe that larger source length and correctly reject because the source no longer fits the destination. The JSC reference case is really targeting a --verifyConcurrentButterfly fast-path TOCTOU, so the as-is file remains a reference/JSC-shape probe, not a portable conformance target.\n\nThat leaves the portable #25 work as: keep/extend zig-js-owned tests for spec-relevant TypedArray/DataView detach/resize races and shared source snapshot rules, while keeping this CVE file classified unless we build a deliberate compatibility harness for its JSC-specific assumptions.

9 days ago

Implemented another #25 portable witness slice in f501ce7 (test(threads): cover typed-array set source snapshots).

What changed:

  • Added a serial TypedArray set snapshots array-like source length test covering both sides of the portable contract:
    • if the source length observed before copying no longer fits the destination, TypedArray.prototype.set throws RangeError before indexed reads/writes;
    • if the source grows after the length snapshot, the copy still uses only the snapshotted prefix.
  • Added a no-GIL witness, parallel_js: TypedArray set snapshots array-like source length under no-GIL grow, where a sibling Thread grows the shared source array while the proxy-backed set operation is between the length snapshot and indexed reads.
  • Updated docs/threads/testing.md and tools/threads-reference-audit.py so cve/mc-df-arraycopy-relabel.js stays classified as JSC butterfly-verification-shaped, while the portable source-length snapshot rule is backed by zig-js-owned tests.

Validation:

  • zig build test -Dtest-filter='TypedArray set snapshots array-like source length' --summary all
  • python3 tools/threads-reference-audit.py --format markdown
  • python3 tools/threads-reference-audit.py --format json
  • zig build threads-reference-audit --summary all
  • bun run docs:build
  • git diff --check

This does not promote mc-df-arraycopy-relabel.js as-is; it documents and tests the portable contract instead of cloning JSC's butterfly/verification assumptions.

9 days ago

Completed for the portable zig-js contract. The ArrayBuffer/DataView/TypedArray detach, resize, transfer, and survivor semantics are covered by zig-js-owned focused tests and the PR-249 reference-only cases remain intentionally classified where they depend on JSC-specific harness/storage assumptions.

Fresh full CI is green for b2e7dbe5e9bc2a27a87ff1bad8be8b0be777e46e: https://github.com/zig-utils/zig-js/actions/runs/29170980761

Sign in to comment on this issue.