Skip to content

fix(vm): reacquire mutable syscall output pointers after account growth - #1762

Open
0xzrf wants to merge 1 commit into
Syndica:mainfrom
0xzrf:syscall_stale_output_ptrs_fix
Open

fix(vm): reacquire mutable syscall output pointers after account growth#1762
0xzrf wants to merge 1 commit into
Syndica:mainfrom
0xzrf:syscall_stale_output_ptrs_fix

Conversation

@0xzrf

@0xzrf 0xzrf commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #1741

Problem

sol_get_return_data has two mutable outputs (r1 return data, r3 program id). Sig translated the return-data address, kept the resulting host pointer, and then translated the program-id address. Under direct_mapping + VASA (SIMD-0460), that second translation can miss and invoke the access-violation handler, which grows the account: AccountSharedData.resize reallocates and frees the old backing, and the handler re-anchors region.host_memory to the new allocation. The subsequent @memcpy then wrote the return data through the stale (freed) pointer, so the live account never received it.

The syscall still reported success, so the divergence is silent:

Runtime Final account prefix
Sig (Debug / ReleaseSafe / ReleaseFast) 7b 00 00 00
Agave 42 22 33 44

Agave avoids this by touching every mutable output range before retaining any pointer, letting all growth settle, and only then acquiring the pointers (translate_mut!). The macro's own contract states the invariant Sig violated: "No other translated references can be live when calling this."

Scope

this PR covers both:

  • getReturnData — 2 mutable outputs.
  • getProcessedSiblingInstruction — 4 mutable outputs (header, program_id, data, accounts), all captured back-to-back. Here the stale pointers also corrupted the six isOverlapping checks, which compare host addresses and would have diffed against a freed range.

findProgramAddress already implemented this pattern (with a comment naming the hazard), so this brings the remaining sites in line with the existing in-repo solution.

Changes

getReturnData — touch both output ranges via vmap before translating, then copy through the post-growth pointers.

getProcessedSiblingInstruction — three ordered changes:

  1. Copy the guest-supplied data_len / accounts_len out of header up front, so the later translations aren't sized by reads through a pointer they can strand (agave's "collect the parameters first" phase).
  2. Touch all four output ranges — meta_addr included — before retaining anything. Zero-length ranges are skipped to match translateSlice, which never translates them, mirroring touch_slice_mut's early return; without this guard a zero-length output would newly error on an address the old code never resolved.
  3. Re-acquire all four pointers, header among them, so both the overlap checks and the trailing header.data_len write target live memory. Agave re-maps meta_addr in its second translate_mut! for exactly this reason.

vmap is used for the touch phase rather than translate* because it performs access checking and growth but skips the alignment check, matching agave's touch_* helpers. This keeps error ordering as access-violation-before-unaligned; using translateType to touch would surface UnalignedPointer where agave reports AccessViolation.

Tests

Three regression tests, each failing before the fix and passing after:

  • getReturnData reacquires output after growth (vm/syscalls/lib.zig) — isolation test with a synthetic growth handler.
  • getReturnData reacquires outputs after real account growth (runtime/program/bpf_loader/execute.zig) -- integration test for getReturnData using the existing AccessViolationHandlerCtx.handle to check the error
  • getProcessedSiblingInstruction reacquires outputs after growth (vm/syscalls/lib.zig)

@github-project-automation github-project-automation Bot moved this to 🏗 In progress in Sig Jul 25, 2026
@dnut dnut added the external label Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: 🏗 In progress

Development

Successfully merging this pull request may close these issues.

Retains a stale sol_get_return_data output pointer across account growth

2 participants