Bump Seed Brute Force
Detects user-controlled bump seeds in PDA derivation without validation against canonical values.
Bump Seed Brute Force
Overview
Remediation Guide: How to Fix Bump Seed Brute Force
The bump seed brute force detector identifies cases where programs accept bump seeds from user input (instruction data or function parameters) and use them in create_program_address without validating the bump against the canonical value from find_program_address. This enables offline brute-force attacks where attackers iterate bump values (0-255) to find alternative valid PDAs, potentially substituting malicious accounts.
Why This Is an Issue
For any given seed combination and program ID, find_program_address returns the canonical (highest valid) bump. There may be other valid bumps that produce different but valid PDAs. If a program accepts any user-provided bump without checking it matches the canonical value, an attacker can try all 256 possible bump values offline and supply a non-canonical PDA that points to an account they control.
CWE mapping: CWE-330 (Use of Insufficiently Random Values).
How to Resolve
Native Solana
// VULNERABLE: user-provided bump used directly
let bump = instruction_data[32];
let pda = Pubkey::create_program_address(
&[b"vault", user.key.as_ref(), &[bump]],
program_id,
)?;
// FIXED: derive canonical bump and validate
let (expected_pda, canonical_bump) = Pubkey::find_program_address(
&[b"vault", user.key.as_ref()],
program_id,
);
if bump != canonical_bump {
return Err(ProgramError::InvalidSeeds);
}
Anchor
// Anchor validates canonical bump automatically
#[account(seeds = [b"vault", user.key().as_ref()], bump = vault.bump)]
pub vault: Account<'info, Vault>,
Examples
Vulnerable Code
pub fn withdraw(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
let bump = data[0]; // User-controlled!
let vault_pda = Pubkey::create_program_address(
&[b"vault", authority.key.as_ref(), &[bump]],
program_id,
)?;
if vault.key != &vault_pda {
return Err(ProgramError::InvalidAccountData);
}
// Attacker can use a non-canonical bump to substitute a different account
Ok(())
}
Fixed Code
pub fn withdraw(accounts: &[AccountInfo]) -> ProgramResult {
let (expected_pda, _canonical_bump) = Pubkey::find_program_address(
&[b"vault", authority.key.as_ref()],
program_id,
);
if vault.key != &expected_pda {
return Err(ProgramError::InvalidAccountData);
}
// Only canonical PDA is accepted
Ok(())
}
Sample Sigvex Output
{
"detector_id": "bump-seed-brute-force",
"severity": "high",
"confidence": 0.75,
"description": "A bump seed originating from user input is passed to create_program_address without validation via find_program_address.",
"location": { "function": "withdraw", "block": 0, "stmt": 1 }
}
Detection Methodology
- Taint tracking: Identifies variables originating from user input (parameters, account data reads).
- Taint propagation: Tracks tainted variables through assignments and expressions.
- Canonical validation tracking: Records variables assigned from
find_program_addresscalls as validated. - Vulnerability detection: Finds
create_program_addresscalls where the bump argument is tainted and not validated.
Limitations
- Taint tracking operates within a single function; bumps validated in initialization and stored for later use are not tracked.
- The detector cannot distinguish between bump seeds and other seed components in complex argument lists.
- Programs that validate bumps through custom comparison logic (not
find_program_address) may produce false positives.
Related Detectors
- Bump Seed Canonicalization — detects non-canonical bump usage
- PDA Bump Seed Reuse — detects bump reuse across PDAs