fix(gc): classify fetch handle ids by band, not magnitude — SIGSEGV on instanceof (#7526) - #7530
Conversation
fetch_subclass_handle_id dereferenced a Web Fetch handle id at addr-8 because its guard was a magnitude floor, not a band check; new Response() yields 0x40000 exactly, so `r instanceof Request` segfaulted on the first handle a program allocates. Also stops the gap snapshot from parking a crash in the expected-output channel.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR hardens fetch handle address checks in the runtime, adds regression coverage for handle-band addresses, rejects ChangesFetch handle safety and gap enforcement
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Suggested reviewers: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Fixes #7526.
The crash: a handle id dereferenced as a heap pointer
fetch_subclass_handle_idclassified its receiver by magnitude, not by band:is_valid_obj_ptr's ownHEAP_MINis0x1000, so that pair is two magnitude tests and no band test. Every handle band sits above the floor and belowHANDLE_BAND_MAX(0x100000), so none of them was excluded — Web Fetch ids live in[0x40000, 0xE0000).new Response(...)returns id0x40000exactly, andr instanceof Requestreaches this probe, so the first fetch handle a program allocates segfaulted. Under lldb:Deterministic, reproduced on two hosts, and not profile-dependent (fails under
perry-devand--releasealike). It is also not the GC-rooting family that dominated today: nothing here is stale, the address was never a pointer.Fix
addr_class::is_plausible_heap_addr— the canonicalis_above_handle_band && is_valid_obj_ptrpairing — plusaddr_class::try_read_gc_header, which magnitude-classifies before touching memory. This is the predicate the module docs already nominate for exactly this job.The regression test walks the band boundaries (
COMMON_HANDLE_BAND_END,FETCH_HANDLE_BAND_{START,END},PROXY_ID_BAND_START,HANDLE_BAND_MAX - 1,1) rather than one value, so a band added toaddr_classwithout a matching guard here fails it.Sabotage-verified, and my first attempt was insufficient — worth recording. Reverting only the band check left
try_read_gc_headerin place, which classifies safely on its own, so the test still passed. Restoring the original guard and the raw deref makes the test binary die withsignal: 11, SIGSEGV. A partial revert is not a sabotage test.test_gap_fetch_instanceof_5433is now byte-identical to node 26.5.1, exit 0.The reason nobody saw it: the snapshot laundered it
The crash was accepted in
gap_snapshot.json:Both cited issues are CLOSED. The entry claimed
bug-openwhile nothing tracked it, and it was the onlystatus: "crash"entry in the file — an exception, not a convention.run_gap_tests.shalready printed crashes every run, deliberately, with a comment explaining that crashes "are hard defects, never cosmetic gaps". But printing is not gating, so it sat there for a month. This adds the enforcement: the harness now refuses anystatus: "crash"snapshot entry and exits 2.It starts green — this change removes the only such entry — so it can only go red on a new attempt to park a crash. Planting a probe entry makes it refuse, verified.
Validation
cargo test -p perry-runtime --no-fail-fast1762 passed / 0 failed.raw_handle_debt.py999 (baseline 999),check_file_size.sh,addr_class_inventory.py,cargo fmt --all --checkall clean.Sibling sites — named, not fixed here
The same magnitude-floor idiom (
< GC_HEADER_SIZE + 0x1000with no band test) appears in at least eight other places:util_promisify.rs:752,node_v8.rs:153,native_handle.rs:149,timer.rs:944,dgram.rs:314,proxy.rs:{534,869,1149}. Whether each is reachable with a banded value needs checking per site, so they are not swept in blind here. I will file that as its own issue rather than widen a crash fix.Summary by CodeRabbit
Bug Fixes
Testing
Documentation
Release