Skip to content

Account data corruption when multiple instructions share a read-only AccountLoader in same transaction #565

@Not-Sarthak

Description

@Not-Sarthak

Description

When two Anchor instructions in the same transaction share a read-only AccountLoader account, the second instruction reads corrupted bytes from a completely separate, unrelated account.

Minimal reproduction

Given an Anchor program with two instructions:

Instruction A (refresh_data):

  • config: AccountLoader<Config> (read-only, seeds-verified)
  • data_a: AccountLoader<DataA> (mutable)

Instruction B (update_stats):

  • config: AccountLoader<Config> (read-only, same account as in Instruction A)
  • data_b: AccountLoader<DataB> (mutable, completely different PDA)

All accounts use #[account(zero_copy(unsafe))] with #[repr(C)].

Transaction: [InstructionA, InstructionB]

Result: Inside Instruction B, data_b reads corrupted bytes. A field that should be 0 reads as 1.

Evidence

Check Result
Client-side read of data_b right before sending tx Field = 0
On-chain read of data_b inside Instruction B (same tx) Field = 1
On-chain raw byte read at the exact offset Also 1
Sending Instruction B alone (no Instruction A) Field = 0
Same transaction on LiteSVM Field = 0

Key observations

  • The corruption only occurs when both instructions are in the same transaction
  • The shared account (config) is read-only in both instructions
  • The corrupted account (data_b) is not used at all by Instruction A
  • The corrupted byte is always at the same struct offset regardless of build
  • Struct layout verified via MaybeUninit pointer arithmetic on BPF — offsets are correct
  • LiteSVM handles the identical scenario correctly (224 tests pass)

Likely cause

The Surfpool runtime's account data buffer management between instructions in a multi-instruction transaction appears to have a bug. When the same account is accessed by multiple instructions (even read-only), the data buffers of other accounts in subsequent instructions get corrupted — possibly due to incorrect buffer reuse, stale RefCell borrow state, or account index mapping issues during instruction dispatch.

Environment

  • Surfpool v0.12.0
  • Anchor 0.32.1, Solana CLI 3.1.6
  • macOS (Darwin 25.3.0)
  • All zero-copy structs use #[repr(C)] with byte-aligned fields

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions