Conditional Validation Bypass
Detects security validations placed inside conditional branches that can be bypassed through alternate execution paths.
Conditional Validation Bypass
Overview
Remediation Guide: How to Fix Conditional Validation Bypass
The conditional validation bypass detector identifies Solana programs where critical security checks (signer validation, owner checks, key comparisons) are placed inside conditional branches rather than on the main execution path. When a validation is conditional, there exist execution paths that reach critical operations (transfers, CPI, state writes) without any validation having occurred. An attacker crafts input to take the non-validating branch.
This detector uses CFG-based dataflow analysis to identify execution paths from function entry to critical operations where validation blocks are not dominators — meaning the validation can be skipped.
Why This Is an Issue
Security validations are only effective if they execute on every path that reaches a protected operation. When a validation is inside a conditional:
- An attacker can satisfy the branch condition to skip the validation block
- The critical operation (transfer, mint, state write) executes without authorization
- The program appears secure on some paths but is exploitable on others
- This is especially dangerous when the branch condition depends on attacker-controlled input
This pattern is common in programs that handle multiple instruction types in a single function, where validation applies to some types but not all.
CWE mapping: CWE-862 (Missing Authorization), CWE-807 (Reliance on Untrusted Inputs in a Security Decision).
How to Resolve
// Before: Vulnerable -- signer check inside conditional branch
pub fn process(accounts: &[AccountInfo], instruction_type: u8) -> ProgramResult {
let authority = &accounts[0];
let vault = &accounts[1];
if instruction_type == 1 {
// Only checked on instruction_type == 1
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
}
// VULNERABLE: reached without signer check when instruction_type != 1
let mut data = vault.try_borrow_mut_data()?;
let mut state = VaultState::try_from_slice(&data[8..])?;
state.balance = 0;
state.serialize(&mut &mut data[8..])?;
Ok(())
}
// After: Validate before any critical operation
pub fn process(accounts: &[AccountInfo], instruction_type: u8) -> ProgramResult {
let authority = &accounts[0];
let vault = &accounts[1];
// FIXED: signer check on all paths before state modification
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if instruction_type == 1 {
// Type-specific logic (non-security)
}
let mut data = vault.try_borrow_mut_data()?;
let mut state = VaultState::try_from_slice(&data[8..])?;
state.balance = 0;
state.serialize(&mut &mut data[8..])?;
Ok(())
}
Examples
Vulnerable Code
pub fn withdraw(accounts: &[AccountInfo], amount: u64, is_admin: bool) -> ProgramResult {
let user = &accounts[0];
let vault = &accounts[1];
if is_admin {
// Admin bypass -- no signer check on this path
} else {
// Normal user path -- validated
if !user.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if amount > MAX_USER_WITHDRAWAL {
return Err(ProgramError::InvalidArgument);
}
}
// CRITICAL: attacker sets is_admin = true, bypasses signer check
**vault.try_borrow_mut_lamports()? -= amount;
**user.try_borrow_mut_lamports()? += amount;
Ok(())
}
Fixed Code
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let user = &accounts[0];
let vault = &accounts[1];
// FIXED: signer check is unconditional
if !user.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// FIXED: validate user is vault owner
let vault_data = vault.try_borrow_data()?;
let vault_state = VaultState::try_from_slice(&vault_data[8..])?;
if vault_state.owner != *user.key {
return Err(ProgramError::InvalidAccountData);
}
if amount > vault_state.balance {
return Err(ProgramError::InsufficientFunds);
}
drop(vault_data);
**vault.try_borrow_mut_lamports()? -= amount;
**user.try_borrow_mut_lamports()? += amount;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "conditional-validation-bypass",
"severity": "critical",
"confidence": 0.72,
"description": "Signer validation at block 2 is inside a conditional branch. Block 5 contains a TransferLamports operation reachable via block 3 (bypass path) without passing through the validation. An attacker can take the bypass path to execute the transfer without authorization.",
"location": { "function": "withdraw", "offset": 5 }
}
Detection Methodology
The detector performs CFG dominance analysis:
- Dataflow analysis: Runs
DataflowAnalyzerto compute validation state at each block entry. This propagatesCheckSigner,CheckOwner, andCheckKeyvalidations across the CFG. - Critical operation identification: Identifies blocks containing
TransferLamports,InvokeCpi, orStoreAccountDatastatements. - Bypass path detection: For each critical operation block, checks whether there exists a path from the function entry to that block where no validation has been applied. This is done by examining the dataflow entry state at the critical block.
- Conditional identification: When a validation exists in the function but does not dominate the critical operation (the validation is in a sibling branch rather than an ancestor), a bypass finding is emitted.
- Anchor adjustment: For Anchor programs, confidence is reduced by 0.70 because Anchor constraints execute unconditionally before the instruction handler, making conditional bypass within the handler less likely to be exploitable.
Limitations
False positives:
- Programs where the conditional branch validates a different condition (e.g., “if amount > 0”) that functionally prevents the critical operation even without a signer check.
- Anchor programs where constraints handle validation before the handler executes.
False negatives:
- Validation that depends on complex data-flow conditions (e.g., checking a flag stored in account data that was set by a prior instruction).
- Multi-instruction transaction patterns where validation occurs in a separate instruction.
Related Detectors
- Missing Signer Check — entirely missing signer validation
- Missing Owner Check — entirely missing ownership validation
- Anchor Constraint Bypass — Anchor-specific constraint bypass patterns