Skip to content

built-ins: normalize guest indices/pointers/lengths to u32; Table.get traps on a negative index (review) - #304

Merged
lannbot merged 1 commit into
mainfrom
fix/review-builtin-index-u32
Sep 7, 2026
Merged

built-ins: normalize guest indices/pointers/lengths to u32; Table.get traps on a negative index (review)#304
lannbot merged 1 commit into
mainfrom
fix/review-builtin-index-u32

Conversation

@lannbot

@lannbot lannbot commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Adversarial correctness review of main @ 396a216 — the u32-normalization cluster (task-core F3, streams F3, and the Table notes from the CABI/streams tracks).

Guest-supplied i32 indices/pointers/lengths arrive signed; the async and stream built-ins used them raw. Two observable effects, both pinned by builtin_index_normalization_test.ts (6 tests):

  • Table.remove(i) mutated (array[i] = null; free.push(i)) BEFORE the caller's class check trapped, so a guest passing 0xFFFFFFFF (JS -1) pushed -1 onto the free list; Table.get(-1) returned undefined (JS array[-1], not the null sentinel) instead of trapping.
  • stream.write with n = 0xFFFFFFFF saw a negative length: the Buffer.MAX_LENGTH trap never tripped and the write parked / completed with 0.

Fix: >>> 0 at every built-in entry (so -1 is 0xFFFFFFFF → out of range → trap in Table.get before any mutation, matching the reference's u32-indexed Table.get/remove, definitions.py 682-703) plus an i < 0 guard in Table.get as defense in depth.

Gates: just test-runtime green; just conformance 0 failed / 0 stale; just sched-seeds green; full just gates green on the union of the five review PRs.

Automerge armed.

… traps on a negative index

Guest-supplied i32 arguments arrive signed. The async and stream built-ins
(waitable-set.wait/poll/drop, waitable.join, subtask.cancel/drop,
stream/future read/write/cancel-*/drop-*, error-context.*, the FACT
stream/future transfer trampolines) used them raw, unlike the resource
intrinsics in intrinsics/mod.ts. Two observable effects:

- `Table.remove(i)` mutated (`array[i] = null; free.push(i)`) BEFORE the
  caller's class check trapped: a guest passing 0xFFFFFFFF (JS -1) pushed -1
  onto the free list, so the next `handles.add` could hand out index -1
  behind a guest-catchable trap. `Table.get` also returned `undefined` for a
  negative index instead of trapping (JS `array[-1]` is `undefined`, not the
  `null` sentinel), so the range trap never fired.
- `stream.write` with n = 0xFFFFFFFF saw a negative length: the
  `Buffer.MAX_LENGTH` trap (definitions.py 911-920) never tripped and the
  write parked or completed with 0.

With `>>> 0` at every built-in entry, -1 is 0xFFFFFFFF, which is out of range
and traps in `Table.get` before any mutation, exactly as the reference's
u32-indexed `Table.get/remove` (682-703). `Table.get` additionally guards
`i < 0` as defense in depth.

Regression: builtin_index_normalization_test.ts. Conformance 0 failed /
0 stale; sched-seeds green.
@lannbot
lannbot enabled auto-merge September 7, 2026 02:12
@lannbot
lannbot merged commit 9a5b8d1 into main Sep 7, 2026
4 checks passed
@lannbot
lannbot deleted the fix/review-builtin-index-u32 branch September 7, 2026 02:16
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.

2 participants