Skip to main content
Sigvex

Durable Nonce Manipulation

Detects missing validation when interacting with durable nonce accounts, enabling replay attacks or transaction reordering.

Durable Nonce Manipulation

Overview

The durable nonce manipulation detector identifies Solana programs that interact with durable nonce accounts without implementing proper validation. Durable nonces allow transactions to remain valid beyond the typical blockhash expiry window, but improper handling can enable replay attacks and unauthorized transaction reordering. For remediation steps, see the Durable Nonce Manipulation Remediation.

Why This Is an Issue

Durable nonce accounts on Solana provide an alternative to recent blockhash-based transaction expiry. Instead of expiring after roughly 150 slots, a transaction using a durable nonce remains valid until the nonce is advanced. Programs that interact with nonce accounts must validate:

  1. Authority: The nonce account authority matches the expected signer. Without this check, any account could be substituted as a nonce, bypassing replay prevention.
  2. State: The nonce account is initialized and contains a valid nonce value. Using an uninitialized nonce account can lead to undefined behavior.
  3. Advancement: The nonce value is properly consumed after use, preventing the same nonce from being reused for replay attacks.

Failure to validate these properties allows attackers to replay transactions, reorder operations to their advantage, or manipulate nonce state to bypass time-sensitive logic.

How to Resolve

// Before: Vulnerable -- no authority or state validation
pub fn use_nonce(accounts: &[AccountInfo]) -> ProgramResult {
    let nonce_account = &accounts[2];
    // Directly uses nonce account without validation
    invoke(
        &system_instruction::advance_nonce_account(nonce_account.key, nonce_account.key),
        &[nonce_account.clone()],
    )?;
    Ok(())
}

// After: Fixed -- validates authority and state before use
pub fn use_nonce(accounts: &[AccountInfo]) -> ProgramResult {
    let nonce_account = &accounts[2];
    let expected_authority = &accounts[3];

    // Validate nonce account authority
    let nonce_data = nonce_account.try_borrow_data()?;
    let nonce_state: nonce::state::Data = bincode::deserialize(&nonce_data)
        .map_err(|_| ProgramError::InvalidAccountData)?;

    require!(
        nonce_state.authority == *expected_authority.key,
        ProgramError::InvalidArgument
    );

    invoke(
        &system_instruction::advance_nonce_account(nonce_account.key, expected_authority.key),
        &[nonce_account.clone(), expected_authority.clone()],
    )?;
    Ok(())
}

Examples

Vulnerable Code

pub fn process_with_nonce(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
    let nonce_info = &accounts[0];
    // No validation of nonce authority or state
    let ix = system_instruction::advance_nonce_account(
        nonce_info.key,
        nonce_info.key,  // Assumes authority is the account itself
    );
    invoke(&ix, &[nonce_info.clone()])?;
    // Process transaction...
    Ok(())
}

Fixed Code

pub fn process_with_nonce(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
    let nonce_info = &accounts[0];
    let authority_info = &accounts[1];

    // Validate nonce account is owned by System Program
    if nonce_info.owner != &system_program::ID {
        return Err(ProgramError::InvalidAccountOwner);
    }

    // Validate authority is a signer
    if !authority_info.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    let ix = system_instruction::advance_nonce_account(
        nonce_info.key,
        authority_info.key,
    );
    invoke(&ix, &[nonce_info.clone(), authority_info.clone()])?;
    Ok(())
}

Example JSON Finding

{
  "detector": "durable-nonce-manipulation",
  "severity": "high",
  "confidence": 0.78,
  "title": "Missing Authority Validation on Nonce Account Operation",
  "description": "Durable nonce account operation 'AdvanceNonceAccount' is performed without validating the nonce account authority.",
  "cwe_ids": [345]
}

Detection Methodology

  1. Nonce operation identification: Scans for CPI calls and syscall invocations that match System Program nonce instruction discriminators (InitializeNonceAccount, AdvanceNonceAccount, WithdrawNonceAccount, AuthorizeNonceAccount, UpgradeNonceAccount).
  2. Authority validation tracking: Identifies patterns where account owner data is loaded and compared, indicating authority validation.
  3. State validation detection: Searches for branch conditions that check nonce account state before performing operations.
  4. Gap analysis: Reports nonce operations that lack either authority or state validation, with confidence adjustments for Anchor programs and admin functions.

Limitations

False positives: Programs that validate nonce authority via helper functions or external libraries may be flagged if the validation pattern is not recognized. Admin-gated functions receive reduced confidence. False negatives: Custom nonce implementations that do not use standard System Program instructions will not be detected.

References