-
Notifications
You must be signed in to change notification settings - Fork 126
Description
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
MaybeUninitpointer 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