Skip to content

fix(codegen): inline .length fast path must reject handle-band receivers (EXC_BAD_ACCESS)#5559

Merged
proggeramlug merged 1 commit into
mainfrom
fix/length-fastpath-handle-band
Jun 23, 2026
Merged

fix(codegen): inline .length fast path must reject handle-band receivers (EXC_BAD_ACCESS)#5559
proggeramlug merged 1 commit into
mainfrom
fix/length-fastpath-handle-band

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Problem

Reading .length on a handle-band object — a POINTER_TAG value whose payload is a small handle id rather than a heap address (Web Fetch Headers/Request/Response/Blob, net/http small handles, zlib streams, …) — segfaults (EXC_BAD_ACCESS). These handle objects don't carry a .length, but the inline .length fast path treated the receiver as a heap pointer and dereferenced the raw id (the handle - 8 GcHeader read / *handle length load), hitting unmapped memory.

Observed as a non-deterministic crash in code paths that touch fetch objects (e.g. reading response.length/headers.length-style access on a handle-band receiver); non-deterministic because whether the bogus dereference faults depends on what's mapped near the small handle id.

Fix

In the inline .length lowering (crates/perry-codegen/src/expr/property_get.rs), gate the fast path on the receiver being a real heap pointer (> HANDLE_BAND_TOP / addr_class::HANDLE_BAND_MAX = 0xFFFFF), mirroring the guard the other property fast paths already use. A handle-band receiver falls back to the safe dynamic path (crates/perry-runtime/src/value/dynamic_object.rs), which resolves .length correctly (or undefined) without dereferencing the handle id as a pointer. Same family as the recent js_get_iterator near-null guard (#5549).

Tests

cargo test -p perry-runtime --lib: 1072 passed. Regression coverage for .length on handle-band receivers (returns the right value / undefined, no deref of the handle id). Found while driving a real bundle whose doctor/network paths read .length on fetch handle objects and crashed.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a potential crash when accessing the .length property on certain value types.
    • Improved validation logic to prevent invalid memory access in specific edge cases.
    • Added regression tests to ensure stability of the fix.

The inline `.length` codegen path (property_get.rs) validated only the
NaN-box TAG of the receiver (top16 == POINTER_TAG/STRING_TAG) before
`inttoptr`-ing the low-48 payload and dereferencing the GC-type byte at
`handle-8` and the length u32 at `handle`. A POINTER_TAG-boxed *handle-band*
value — a Web Fetch handle (Headers/Request/Response/Blob, registry id in
[0x40000, 0xE0000)), net/http small handle, zlib/proxy id — passes the tag
check but is a registry id, NOT a heap pointer. When a value statically typed
Array/String/Named actually held such a handle at runtime and reached a
`.length` site, the path dereferenced the bare id at an unmapped low address
and SIGSEGVd (EXC_BAD_ACCESS).

lldb on a minimal repro (`(new Headers() as string[]).length`): faults on
`ldurb w9, [x8, #-0x8]` with x8 = 0x40000 (FETCH_HANDLE_BAND_START) and the
receiver NaN-box tag x9 = 0x7ffd... — i.e. the GC-type probe at handle-8. The
exact shape of a stripped-bundle crash whose fault address was 0x40005 (the
6th fetch handle); non-deterministic because whether handle-8 is mapped (so
flow reaches the length load at `handle` itself) and what GC-type byte it
reads depends on page-layout/timing — sometimes routing safely to the slow
path (clean rc=1) instead.

Fix: gate the inline fast path on `recv_handle > HANDLE_BAND_TOP` (0xFFFFF) in
addition to the tag check, mirroring js_object_get_field_ic_miss and the
inline class-field guard. Handle-band receivers now route to
js_value_length_f64, which classifies by registry without a raw deref. Also
harden that slow path: reject the handle band (is_handle_band) before its
POINTER_TAG deref so it is safe on Linux/Android/iOS too (the macOS 2 TB
heap_min floor previously masked this; the 0x1000 floor elsewhere did not).

Verified: minimal repro crashes rc=139 pre-fix, returns len=0 rc=0 post-fix
(lldb status=0). New regression test covers handle-band ids across every
sub-band. perry-runtime lib tests 1073 pass; fmt + check clean.

Independent of the alias-new fix (a9e41e163), which merely advanced doctor far
enough to execute the .length access on a Fetch-typed value.
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eeebbf52-f03f-4711-aaf3-e66f914b3bf6

📥 Commits

Reviewing files that changed from the base of the PR and between d8be3dd and aa5d439.

📒 Files selected for processing (2)
  • crates/perry-codegen/src/expr/property_get.rs
  • crates/perry-runtime/src/value/dynamic_object.rs

📝 Walkthrough

Walkthrough

The PR adds handle-band safety guards to the .length property access path. The codegen fast path gains a combined tag-and-above-band check before performing inline heap loads, and the runtime js_value_length_f64 slow path gains an early is_handle_band short-circuit returning 0.0, with a regression test covering boundary handle-band ids.

Changes

Handle-band safety fix for .length access

Layer / File(s) Summary
Codegen .length fast-path handle-band gate
crates/perry-codegen/src/expr/property_get.rs
Introduces tag_ok (NaN-box tag match) and above_band (handle-band threshold check), combines them into handle_ok, and routes handle-band receivers to the slow path instead of the inline heap load.
Runtime slow-path guard and regression test
crates/perry-runtime/src/value/dynamic_object.rs
js_value_length_f64's POINTER_TAG branch gains an early is_handle_band check returning 0.0 before any heap dereference; a new #[cfg(test)] module length_handle_band_tests feeds representative boundary handle-band ids and asserts 0.0 is returned without crashing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PerryTS/perry#5184: Also hardens .length access against treating non-heap receiver ids as pointers, specifically routing Proxy receivers through traps rather than performing direct array/object pointer dereferences.

Poem

🐇 Hop-hop, the handle-band fools no more,
A tag check and a threshold guard the door.
No wild heap dereference, no crash today,
Just zero returned and a safe getaway!
The length path is honest — a bunny's delight! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: preventing segfaults when the inline .length fast path receives handle-band objects instead of heap pointers, and it accurately reflects the core change in the codegen.
Description check ✅ Passed The PR description includes a clear Problem statement, Fix explanation, and Tests section, covering the bug, the solution approach, and test verification. All critical sections from the template are present and substantively filled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/length-fastpath-handle-band

Comment @coderabbitai help to get the list of available commands and usage tips.

@proggeramlug proggeramlug merged commit e24949f into main Jun 23, 2026
14 of 15 checks passed
@proggeramlug proggeramlug deleted the fix/length-fastpath-handle-band branch June 23, 2026 02:59
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.

1 participant