Skip to content

webcore(Blob): coalesce concurrent fd-backed blob reads onto a single ReadFile - #35832

Open
robobun wants to merge 13 commits into
mainfrom
farm/f3ee9036/stdin-concurrent-blob-read
Open

webcore(Blob): coalesce concurrent fd-backed blob reads onto a single ReadFile#35832
robobun wants to merge 13 commits into
mainfrom
farm/f3ee9036/stdin-concurrent-blob-read

review: init promise before publishing handler; relax sliced-test ord…

938ed8d
Select commit
Loading
Failed to load commit list.
Claude / Claude Code Review completed Jul 26, 2026 in 35m 15s

Code review found 2 important issues

Found 2 candidates, confirmed 2. See review comments for details.

Details

Severity Count
🔴 Important 2
🟡 Nit 0
🟣 Pre-existing 0
Severity File:Line Issue
🔴 Important src/runtime/webcore/blob/read_file.rs:428-447 'JS-thread-only' invariant on Store::in_flight_blob_reader is unenforced — cross-VM UAF via ObjectURLRegistry
🔴 Important test/js/bun/util/bun-stdin-slice.test.ts:63-65 sliced-vs-unsliced test can throw EPIPE at 'await proc.stdin.end()' when the unsliced reader loses the epoll race

Annotations

Check failure on line 447 in src/runtime/webcore/blob/read_file.rs

See this annotation in the file changed.

@claude claude / Claude Code Review

'JS-thread-only' invariant on Store::in_flight_blob_reader is unenforced — cross-VM UAF via ObjectURLRegistry

The "JS-thread-only" invariant on `Store::in_flight_blob_reader` is unenforced: `ObjectURLRegistry::singleton()` is a process-global `static OnceLock`, and `resolve_and_dupe` hands back a Blob whose `StoreRef` points to the **same heap `Store`** — so `URL.createObjectURL(Bun.file(fd))` on the main thread + `resolveObjectURL(url)` in a Worker gives both JS threads an fd-backed Store on which `try_coalesce_fd_read`/`mark_in_flight` are live. A worker's `load(Acquire)` can then race main's `then()`

Check failure on line 65 in test/js/bun/util/bun-stdin-slice.test.ts

See this annotation in the file changed.

@claude claude / Claude Code Review

sliced-vs-unsliced test can throw EPIPE at 'await proc.stdin.end()' when the unsliced reader loses the epoll race

The sliced-vs-unsliced test can flake with `EPIPE: broken pipe, write` at `await proc.stdin.end()` (line 65 of the shared `run()` helper). 938ed8d relaxed the child-side `result[0]` assertion because either reader can lose the `epoll_ctl(ADD)` race, but when the *unsliced* reader is the loser the child exits after consuming ~3 bytes of the 2 MiB payload, and the parent's `FileSink::on_attached_process_exit` rejects the still-pending `end()` promise before any assertion runs. Swallow the rejectio