Skip to content

feat(vm): sanitizer-build borrow-scan guard (#548)#552

Merged
InauguralPhysicist merged 1 commit into
mainfrom
feat/548-borrow-scan-guard
Jul 11, 2026
Merged

feat(vm): sanitizer-build borrow-scan guard (#548)#552
InauguralPhysicist merged 1 commit into
mainfrom
feat/548-borrow-scan-guard

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Summary

Machine-checks the #546 borrow-scan invariant (no builtin returns a borrowed direct child past index 7), which was previously enforced only by a comment. A future violation is a missed compensating incref — a lifetime-dependent use-after-free heisenbug, not a test failure.

  • Guard (src/vm.c): in ASan builds, vm_borrow_scan continues past VM_BORROW_SCAN_CAP after a capped-scan miss and abort()s on a match, naming the builtin (env-chain reverse lookup, abort-path only). Gate is the compiler-set ASan macro (EIGS_BORROW_GUARD, src/vm.h) — zero release cost, no Makefile change; active automatically in make asan, the CI ASan job, and fuzz builds.
  • Planted fault: __borrow_guard_selftest deliberately violates the invariant — registered only in sanitizer builds and under EIGS_BORROW_GUARD_SELFTEST, so fuzzers can never reach a deliberate abort. Documented in BUILTINS.md (stdlib index gate: 215).
  • Suite [119] (4 checks): within-cap borrow clean · past-cap aborts (rc=134) · message names builtin + cap · selftest absent without opt-in. SKIPs on release, where the guard is compiled out by design.
  • VM_BORROW_SCAN_CAP moved to src/vm.h (shared with the self-test); all three call sites (CALL, jit_helper_call, OP_DISPATCH) pass the builtin + frame env for the diagnostic.

Validation

  • ASan+UBSan detect_leaks=1: 2799/2799 (guard active, 4 new checks), leak tally 0.
  • Release: 2795/2795 ([119] SKIPs; guard compiled out).
  • Planted fault verified red-then-green by hand: abort fires naming __borrow_guard_selftest at index 9; within-cap and no-envvar controls clean.

Closes #548

🤖 Generated with Claude Code

…w past the cap (#548)

The #546 cap on vm_borrow_scan rests on 'no builtin returns a borrowed
direct child past index 7', enforced only by a comment. A future
violation is a missed compensating incref: the VM pushes a result it
doesn't own, and the over-release fires later and lifetime-dependently
— a heisenbug UAF, not a test failure.

In ASan builds (compiler-set macro gate, EIGS_BORROW_GUARD in vm.h —
zero release cost) the scan now continues past VM_BORROW_SCAN_CAP and
aborts on a match the capped scan missed, naming the builtin via an
env-chain reverse lookup that runs only on the abort path. The cap
moved to vm.h so the self-test can reference it.

Planted-fault validation (a checker nobody watched fail is not a
checker): __borrow_guard_selftest deliberately returns items[count-1]
of a >cap arg vector. Registered only in sanitizer builds AND under
EIGS_BORROW_GUARD_SELFTEST, so fuzz harnesses (also ASan builds) can
never reach a deliberate abort. Suite [119], 4 checks: within-cap
clean, past-cap aborts rc=134, message names builtin+cap, opt-in
absent by default. SKIPs on release; documented in BUILTINS.md
(stdlib index gate now tracks 215).

Closes #548

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@InauguralPhysicist InauguralPhysicist merged commit 83b1a48 into main Jul 11, 2026
17 checks passed
@InauguralPhysicist InauguralPhysicist deleted the feat/548-borrow-scan-guard branch July 11, 2026 04:29
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.

Debug-build guard for the #546 borrow-scan cap: full-scan past the cap and abort on a missed borrow

1 participant