PDA User-Controlled Seeds Remediation
How to fix unvalidated user-controlled input in PDA seeds.
PDA User-Controlled Seeds Remediation
Overview
Related Detector: PDA User-Controlled Seeds
PDA user-controlled seed vulnerabilities allow attackers to derive arbitrary PDA addresses by supplying crafted seed data. The fix is to validate all user input before using it in PDA derivation, and to prefer signer public keys over raw instruction data as seed components.
Recommended Fix
Before (Vulnerable)
let user_data = &instruction_data[0..32];
let (pda, _) = find_program_address(&[user_data], program_id);
After (Fixed)
// Use validated signer key instead of raw instruction data
let user = &accounts[0];
if !user.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let (pda, _) = find_program_address(&[b"vault", user.key.as_ref()], program_id);
Alternative Mitigations
1. Input validation with bounds checking
let seed = &instruction_data[0..32];
// Validate seed length
if seed.len() > MAX_SEED_LEN {
return Err(ProgramError::InvalidArgument);
}
// Add program-specific prefix
let (pda, _) = find_program_address(&[b"data", seed], program_id);
2. Allowlist for seed values
const VALID_TYPES: &[&[u8]] = &[b"vault", b"escrow", b"config"];
let seed_type = &instruction_data[0..6];
if !VALID_TYPES.iter().any(|t| *t == seed_type) {
return Err(ProgramError::InvalidArgument);
}
let (pda, _) = find_program_address(&[seed_type, user.key.as_ref()], program_id);
3. Anchor declarative seeds
#[derive(Accounts)]
pub struct Process<'info> {
#[account(seeds = [b"vault", user.key().as_ref()], bump)]
pub vault: Account<'info, Vault>,
pub user: Signer<'info>, // Must be signer -- prevents impersonation
}
Common Mistakes
Mistake 1: Using instruction data directly as the only seed
// WRONG: user controls entire PDA derivation
let (pda, _) = find_program_address(&[data], program_id);
Always include a static prefix and validated identity.
Mistake 2: Validating seed format but not authorization
// INSUFFICIENT: format is valid but user may access other users' accounts
if seed.len() == 32 { // Length check only
let (pda, _) = find_program_address(&[seed], pid);
}
Also verify the seed corresponds to the signer’s identity.
Mistake 3: Using remaining_accounts as PDA seed source
// WRONG: remaining_accounts are attacker-controlled
let seed_account = ctx.remaining_accounts[0];
let (pda, _) = find_program_address(&[seed_account.key.as_ref()], pid);
Only use validated, typed accounts as seed sources.