webcore(Blob): coalesce concurrent fd-backed blob reads onto a single ReadFile - #35832
Open
robobun wants to merge 13 commits into
Open
webcore(Blob): coalesce concurrent fd-backed blob reads onto a single ReadFile#35832robobun wants to merge 13 commits into
robobun wants to merge 13 commits into
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
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
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
Loading