Zero Copy Deserialization
Detects unsafe zero-copy deserialization patterns that can lead to memory corruption.
Zero Copy Deserialization
Overview
Remediation Guide: How to Fix Zero Copy Deserialization
The zero-copy deserialization detector identifies unsafe patterns when programs directly interpret account data as typed structures without proper validation. Zero-copy deserialization (via bytemuck, zerocopy, or Anchor’s zero_copy) avoids copying data but introduces risks: buffer over-reads from insufficient size validation, type confusion from missing discriminator checks, and undefined behavior from alignment violations.
Sigvex tracks account data accesses of 8 bytes or more (zero-copy candidates) and checks for size validation, discriminator checks, and alignment verification before the access point.
Why This Is an Issue
- Buffer over-read: accessing 8 bytes from a 4-byte account reads beyond the allocation, exposing adjacent memory.
- Type confusion: without discriminator validation, the program may interpret one account type as another, misaligning fields.
- Alignment violations: casting unaligned data to types requiring alignment (u64, i64) causes undefined behavior on some platforms.
- Uninitialized data: padding bytes in zero-copy structs may contain data from previous occupants.
CWE mapping: CWE-126 (Buffer Over-read), CWE-843 (Type Confusion), CWE-704 (Incorrect Type Conversion).
How to Resolve
Native Solana
pub fn process(account: &AccountInfo) -> ProgramResult {
let data = account.data.borrow();
// 1. Size validation
if data.len() < std::mem::size_of::<MyStruct>() + 8 {
return Err(ProgramError::InvalidAccountData);
}
// 2. Discriminator validation
if data[0..8] != MY_DISCRIMINATOR {
return Err(ProgramError::InvalidAccountData);
}
// 3. Safe zero-copy access
let state: &MyStruct = bytemuck::try_from_bytes(&data[8..8 + std::mem::size_of::<MyStruct>()])
.map_err(|_| ProgramError::InvalidAccountData)?;
// ...
}
Anchor
// Anchor's zero_copy handles alignment and validation
#[account(zero_copy)]
pub struct LargeState {
pub data: [u64; 1024],
}
#[derive(Accounts)]
pub struct Process<'info> {
#[account(mut)]
pub state: AccountLoader<'info, LargeState>,
}
Examples
Vulnerable
let data = account.data.borrow();
// No size check, no discriminator check, no alignment check
let state: &MyStruct = bytemuck::from_bytes(&data[8..]);
Fixed
let data = account.data.borrow();
require!(data.len() >= 8 + std::mem::size_of::<MyStruct>(), InvalidData);
require!(data[0..8] == MY_DISCRIMINATOR, InvalidType);
let state: &MyStruct = bytemuck::try_from_bytes(&data[8..8 + std::mem::size_of::<MyStruct>()])
.map_err(|_| ProgramError::InvalidAccountData)?;
JSON Finding
{
"detector": "zero-copy-deserialization",
"severity": "Critical",
"confidence": 0.80,
"title": "Completely Unvalidated Zero-Copy Deserialization",
"description": "Account data is directly accessed with NO validation.",
"cwe": [129]
}
Detection Methodology
The detector collects validation state (minimum validated size, discriminator checks, alignment checks) from branch conditions, then compares this state against data access operations. Accesses of 8 bytes or more without corresponding validation generate findings at appropriate severity levels: critical for no validation, high for missing size or discriminator checks, medium for missing alignment checks.
Limitations
- The detector uses access size as a heuristic for zero-copy candidates (8+ bytes). Small accesses may still be zero-copy but are not flagged.
- Validation checks in helper functions or prior instructions are not tracked.
- Anchor’s
AccountLoaderprovides safe zero-copy access but may not be distinguishable in decompiled bytecode.
Related Detectors
- Unsafe Deserialization - detects variable-offset access without bounds checks.
- Unchecked Deserialization - detects missing error handling in deserialization.