Skip to main content
Sigvex

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

  1. Taint tracking: Identifies variables originating from user input (parameters, account data reads).
  2. Taint propagation: Tracks tainted variables through assignments and expressions.
  3. Canonical validation tracking: Records variables assigned from find_program_address calls as validated.
  4. Vulnerability detection: Finds create_program_address calls 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.

References