fix(runtime): fetch string_from_header must reject handle-band ids (EXC_BAD_ACCESS)#5560
Conversation
…octor/mcp SIGSEGV) The doctor / mcp-list startup segfault (EXC_BAD_ACCESS at the fetch-handle address, e.g. 0x40005/0x40006) root-causes to perry_stdlib::fetch:: string_from_header dereferencing a native registry id as a *StringHeader. lldb on a debug bundle (doctor): frame #0 perry_stdlib::fetch::string_from_header ldr w9,[x1,#0x4] x1=0x40002 frame #1 js_fetch_with_options frame #2 global_this_fetch_thunk fetch() was called with a non-string first argument — a Request/Headers object (a Web Fetch handle, registry id in [0x40000, 0xE0000)). The codegen passes the bare handle id into the url_ptr *StringHeader slot of js_fetch_with_options(url_ptr, method_ptr, body_ptr, headers_json_ptr), and string_from_header reads (*ptr).byte_len at id+4 -> deref of unmapped low address -> SIGSEGV. Its only guard was `ptr < 0x1000` (the TAG_UNDEFINED 0x1 remnant); a 0x40002 handle clears that floor. Non-deterministic for mcp list because whether the id+4 page is mapped at the call depends on heap/page layout: when mapped, byte_len reads garbage and fetch proceeds to the intended error (clean rc=1); when unmapped, SIGSEGV. Fix: reject the whole handle band (is_handle_band, < 0x100000) in string_from_header so a native handle is treated as 'not a string' (None) instead of dereferenced — same robustness pattern as the inline .length / IC-miss / class-field-guard band gates. Adds a regression test. This is the doctor/mcp wall and is INDEPENDENT of the alias-new fix (a9e41e163) and of the inline-.length handle-band fix (175fda498) — both touch different code paths; a9e41e163 merely advanced doctor far enough to reach this fetch() call.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesHandle-band guard in
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Problem
In the fetch implementation,
string_from_headerdereferenced its argument as aStringHeaderpointer without checking that it is a real heap pointer. When the value is a handle-band id (a fetchHeaders/Request/Response/Blobhandle — a small registry id in the fetch handle band0x40000..=0xFFFFF, not aStringHeader*), the deref reads unmapped memory →EXC_BAD_ACCESS(SIGSEGV).This crashed a real bundle's
doctor/network paths (which build/inspect fetch headers); non-deterministic, depending on what's mapped near the small id.Fix
string_from_headernow treats a value whose raw payload falls in the handle band (<= addr_class::HANDLE_BAND_MAX/ withinFETCH_HANDLE_BAND_START..) as "not a string" and returns the not-a-string result instead of dereferencing the id as aStringHeaderpointer. Same family as the inline.lengthhandle-band guard and thejs_get_iteratornear-null guard (#5549) — fast/native paths must reject handle-band ids before treating them as heap pointers.Tests
string_from_header_rejects_handle_band_ids(a fetch handle id at the band start, mid-band, andHANDLE_BAND_MAX - 1are all rejected without deref).cargo test -p perry-runtime --lib: 1072 passed.Summary by CodeRabbit
Bug Fixes
Tests