Account Executable Flag
Detects missing executable flag validation before using accounts as program IDs in cross-program invocations.
Account Executable Flag
Overview
Remediation Guide: How to Fix Missing Executable Flag Validation
The account executable flag detector identifies Solana programs that use accounts as program IDs in cross-program invocations (CPI) without verifying that the account is marked as executable. On Solana, accounts are either program accounts (executable = true) or data accounts (executable = false). When making a CPI, the target must be a program account. If the code does not verify the executable flag or validate the program ID against a known constant, an attacker can substitute a data account, causing unexpected behavior.
Sigvex tracks CPI calls (InvokeCpi statements and Cpi expressions), program ID validations (CheckKey with constant values), and hardcoded program constants. Unvalidated variable program IDs used in CPI are flagged. CWE mapping: CWE-20 (Improper Input Validation).
Why This Is an Issue
While the Solana runtime enforces that CPI targets must be executable (the transaction will fail if a data account is passed), explicit validation provides defense-in-depth:
- Clear error messages: Without explicit checks, the runtime produces generic errors that are difficult to debug.
- Prevent confusion attacks: An attacker passing a data account can cause the program to follow error handling paths that may have unintended side effects.
- Composability safety: In complex multi-CPI chains, ensuring each program ID is validated prevents cascading failures.
- Future-proofing: If runtime behavior changes or the program is deployed to a fork with different validation rules, explicit checks maintain security.
How to Resolve
Native Rust
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
const EXPECTED_PROGRAM: Pubkey = /* known program ID */;
pub fn invoke_external(accounts: &[AccountInfo]) -> ProgramResult {
let program = &accounts[0];
// Validate program identity (implicitly ensures executability)
if program.key != &EXPECTED_PROGRAM {
return Err(ProgramError::IncorrectProgramId);
}
// Optionally check executable flag directly
if !program.executable {
return Err(ProgramError::InvalidAccountData);
}
solana_program::program::invoke(
&build_instruction(program.key),
&[accounts[1].clone()],
)?;
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct InvokeExternal<'info> {
// Anchor's Program<T> type validates program identity and executability
pub target_program: Program<'info, MyProgram>,
}
Examples
Vulnerable Code
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
pub fn delegate_operation(accounts: &[AccountInfo]) -> ProgramResult {
let program_account = &accounts[0]; // User-supplied -- no validation!
let data_account = &accounts[1];
// CPI with unvalidated program -- attacker passes a data account
solana_program::program::invoke(
&build_instruction(program_account.key),
&[data_account.clone()],
)?;
Ok(())
}
Fixed Code
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
const APPROVED_PROGRAM: Pubkey = /* known program address */;
pub fn delegate_operation(accounts: &[AccountInfo]) -> ProgramResult {
let program_account = &accounts[0];
let data_account = &accounts[1];
// Validate program identity
if program_account.key != &APPROVED_PROGRAM {
return Err(ProgramError::IncorrectProgramId);
}
solana_program::program::invoke(
&build_instruction(program_account.key),
&[data_account.clone()],
)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "account-executable-flag",
"severity": "high",
"confidence": 0.85,
"description": "Account v1 is used as a program ID in a cross-program invocation without validating that it is an executable program account.",
"location": { "function": "delegate_operation", "offset": 1 }
}
Detection Methodology
The detector performs a two-pass analysis:
- Collection pass: Identifies all CPI calls (both
InvokeCpistatements andCpiexpressions nested in other expressions), and all program ID validations (CheckKeyagainst constant values). - Validation pass: For each CPI call, checks whether the program ID is either a hardcoded constant (compile-time safe) or has been validated via
CheckKeyagainst a constant. Unvalidated program IDs generate findings.
Context modifiers:
- Anchor programs: confidence reduced by 50% (
Program<'info, T>validates program identity) - Admin/initialization functions: confidence reduced by 40%
- Read-only/view functions: confidence reduced by 60%
Limitations
False positives:
- Programs that validate program IDs through indirect means (e.g., reading a config account that stores the expected program ID) may be flagged because the validation is not a direct
CheckKeywith a constant. - Programs where the CPI target is always a well-known program passed by the runtime (e.g., system program) may be flagged if the variable is not checked.
False negatives:
- Validation against a non-constant variable (e.g., another account’s key) is not recognized as safe, since the variable itself could be attacker-controlled. This is by design.
- CPI calls hidden behind complex expression trees beyond the detector’s recursive depth may be missed.
Related Detectors
- CPI Program Validation — broader CPI program validation checks
- Address Lookup Table Poisoning — related program ID substitution via ALTs