Skip to content

feat(vcr-mem): layer-2 shadow-stack budget decision logic, frozen-safe (#242, #383)#421

Merged
avrabe merged 1 commit into
mainfrom
vcr-mem-001-layer2-budget-logic
Jun 22, 2026
Merged

feat(vcr-mem): layer-2 shadow-stack budget decision logic, frozen-safe (#242, #383)#421
avrabe merged 1 commit into
mainfrom
vcr-mem-001-layer2-budget-logic

Conversation

@avrabe

@avrabe avrabe commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

VCR-MEM-001 layer-2 derives the shadow-stack budget B from a proven worst-case stack depth instead of the integrator-asserted number that layer-1 (#383, v0.11.51) trusts. This PR lands the decision logic only — the dep-free core — ahead of the gated scry-production-dep wiring.

crates/synth-cli/src/shadow_budget.rs: a pure

budget_from_bound(bound: StackDepthBound, sp_init: u32, fallback: Option<u32>) -> BudgetDecision

over a synth-owned StackDepthBound enum (Bytes(u64) | Unbounded | Unknown). Because the enum is synth's own (not scry_analyze_core::StackBound), the logic and its 9 unit tests compile and run with scry absent from the build graph — and, critically, without scry's ~28-crate transitive closure entering the Bazel rust_binary compile. The adapter from scry's type lives at the future (gated) call site.

Soundness scope — "proven stack DEPTH, asserted no-heap"

Confirmed against the primary source (scry-sai-core-1.12 lib.rs:276-281): max_stack_bytes is "the deepest weighted path through the call graph, each function weighted by the frame its prologue subtracts from the __stack_pointer global … guest shadow-stack only." That is exactly the [0, sp_init) region B controls.

  • Bytes(n)source = ProvenStackDepth, B = align16(n) — labelled "proven stack depth, asserted no-heap". Never restated as a bare "proven footprint" (the native-pointer-abi: size the wasm .bss to data high-water-mark, not the full declared memory page (blocks tiny-RAM targets) #383 overclaim shape is explicitly guarded).
  • Bytes(n) with n > sp_initrefuse, naming it as the original image under-provisioning its stack (a latent source bug surfaced for free, not a generic refuse).
  • Unbounded / UnknownAssertedFallback if a fallback is given, else honest refuse with the cause. Unknown never yields ProvenStackDepth, even with a fallback (unit-tested).
  • B rounded up to 16-byte AAPCS/LLVM stack alignment — rounding up preserves B ≥ proven depth, so soundness is not weakened.

Frozen-safe

No call site yet (#[allow(dead_code)]), no byte-changing codegen, default behaviour unchanged. Frozen fixtures (control_step 0x00210A55, flat+inlined flight_algo 0x07FDF307, divseam) are untouched by construction.

Remaining gated steps (recorded in the roadmap)

  • 2a — dep architecture (user decision): promoting scry-sai-core dev-dep → production dep pulls its closure into the Bazel graph (synth-cli is built there, crates/BUILD.bazel:230; MODULE.bazel uses crate.from_specs(), likely because z3 needs static-link-z3). Choice among {from_cargo migration, feature-gate the scry call out of the Bazel build, ~28 manual specs} — not a unilateral call.
  • 2b — adapter + wiring: scry::StackBound → StackDepthBound adapter at a call site running analyze(), routed into the layer-1 shrink; opt-in (--shadow-stack-size auto); verifies-link scry SCRY-001.

Verification

  • cargo test -p synth-cli --bin synth shadow_budget → 9/9 pass
  • cargo fmt --check clean; cargo clippy -p synth-cli --all-targets -- -D warnings clean
  • rivet check → zero non-xref errors

Refs #242 (VCR epic), #383 (layer-1).

🤖 Generated with Claude Code

#242, #383)

VCR-MEM-001 layer-2 derives the shadow-stack budget B from a PROVEN
worst-case stack depth instead of an integrator-asserted number (layer-1,
#383). This lands the DECISION LOGIC only — deliberately dep-free and
decoupled from the scry production-dep decision (a 28-crate Bazel cascade
that is a separate, user-gated step).

`synth-cli/src/shadow_budget.rs`: pure
`budget_from_bound(bound, sp_init, fallback) -> BudgetDecision` over a
synth-OWNED `StackDepthBound` enum (Bytes(u64)|Unbounded|Unknown), so the
logic + its 9 unit tests compile WITHOUT scry in the build graph. The
adapter from `scry_analyze_core::StackBound` lives at the (gated) call site.

Scope is primary-source-confirmed (scry-sai-core-1.12 lib.rs:276-281:
max_stack_bytes = "the deepest weighted path … each function weighted by the
frame its prologue subtracts from the __stack_pointer global … guest
shadow-stack only") ⇒ Bytes(n) yields source=ProvenStackDepth labelled
"proven stack DEPTH, asserted no-heap" — NOT a bare "proven footprint" (the
#383 overclaim shape is explicitly guarded). Refuses when proven depth >
sp_init, naming it as the ORIGINAL image under-provisioning its stack.
Unbounded/Unknown ⇒ AssertedFallback or honest-refuse; Unknown NEVER yields
ProvenStackDepth even with a fallback. B rounded up to 16-byte AAPCS/LLVM
stack alignment (rounding up preserves B >= proven depth).

Frozen-safe: no call site yet (`#[allow(dead_code)]`), no byte-changing
codegen, default behaviour unchanged. The scry dep-architecture choice and
the analyze()→budget wiring are the remaining gated steps, recorded in the
roadmap (2a user decision / 2b adapter+wiring).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@avrabe avrabe merged commit e375eff into main Jun 22, 2026
14 checks passed
@avrabe avrabe deleted the vcr-mem-001-layer2-budget-logic branch June 22, 2026 15:52
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.25000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-cli/src/shadow_budget.rs 96.25% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

avrabe added a commit that referenced this pull request Jun 22, 2026
…n refuses a proven budget (#242, #383) (#425)

The msgq/gust tests cover layer-2's PROVEN (finite) path; the soundness of the
whole derivation rests on the complementary REFUSAL path working — an unbounded
shadow stack must never receive a finite budget, or the image silently
under-reserves and overflows on silicon. #421 covers this with synthetic bounds
only; this anchors it against REAL scry output.

`scripts/repro/recursive_shadow_stack.wat` recurses through the shadow stack
(each activation decrements the SP global by a 16 B frame), so the worst-case
depth is unbounded. Empirically scry classifies it exactly: sp_global
identified, recursive=true, max_stack_bytes=Unbounded.

`layer2_unbounded_recursion_refuses_proven_budget_242` (synth-cli main.rs
cfg(test)) asserts that scry behaviour AND that `budget_from_bound` never yields
a ProvenStackDepth for it — with a fallback it returns AssertedFallback (not
proven), without one it honestly refuses. This guards the upstream assumption
the entire honest-fail gate depends on: a scry regression that returned a finite
bound for an unbounded stack — the one failure mode that would silently
under-reserve on silicon — would redden CI.

Frozen-safe: scry + wat stay test-only (cfg(test)); production bytes unchanged,
no MODULE.bazel pin. Roadmap VCR-MEM-001 records the proven+honest-fail coverage.

Verification: `cargo test -p synth-cli --bin synth layer2_` -> 3/3 pass; fmt +
clippy -D warnings clean; rivet check zero non-xref errors.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
avrabe added a commit that referenced this pull request Jun 22, 2026
, #394) (#440)

First release of the north-star verified-codegen campaign. Pin sweep
0.11.51 -> 0.12.0 (workspace + all path-dep versions + MODULE.bazel +
Cargo.lock), CHANGELOG, and VCR-DBG-001 roadmap status -> implemented.

Ships:
- VCR-DBG-001 `--debug-line` DWARF emission (PR A #427 / B #429 / C #430):
  debugger-readable .debug_* mapping ARM .text -> wasm source lines, with
  .rel.debug_* relocations verified end-to-end against arm-none-eabi-ld.
  Purely additive; default build bit-identical.
- VCR-MEM-001 layer-2 frozen-safe budget decision logic + oracles (#421-#425).
- gimli 0.33 adaptation (#439) + wast 252 (#438).

Minor bump: new user-facing `--debug-line` feature (semver-correct).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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