Skip to main content
Sigvex

Instruction Introspection

Detects unsafe instruction introspection with missing bounds or length validation when accessing transaction instructions via sysvar.

Instruction Introspection

Overview

Remediation Guide: How to Fix Instruction Introspection

The instruction introspection detector identifies unsafe use of the Instructions sysvar (sol_get_sysvar_instructions) to access other instructions within the current transaction. Programs use instruction introspection to verify that specific instructions precede or follow the current one (e.g., confirming a price oracle update occurred). Without proper bounds validation, accessing a non-existent instruction index causes a runtime panic.

This detector flags three categories of issues: missing bounds checks before loading an instruction by index, missing data length validation before deserializing instruction data, and use of suspicious or invalid constant indices (negative values or unusually high values).

Why This Is an Issue

An attacker controls the transaction structure and can craft transactions with fewer instructions than the program expects. If the program accesses instruction index 2 but the transaction only contains 2 instructions (indices 0 and 1), the runtime panics. This enables denial of service and can break authorization logic that depends on verifying the presence of specific preceding instructions.

How to Resolve

use solana_program::sysvar::instructions::{
    load_current_index_checked, load_instruction_at_checked,
};

pub fn verify_preceding_instruction(
    instruction_sysvar: &AccountInfo,
) -> ProgramResult {
    let current_index = load_current_index_checked(instruction_sysvar)?;

    // Validate bounds before access
    if current_index == 0 {
        return Err(ProgramError::InvalidArgument); // No preceding instruction
    }

    let prev_ix = load_instruction_at_checked(
        (current_index - 1) as usize,
        instruction_sysvar,
    )?;

    // Validate data length before deserialization
    if prev_ix.data.len() < 8 {
        return Err(ProgramError::InvalidInstructionData);
    }

    let value = u64::from_le_bytes(prev_ix.data[0..8].try_into().unwrap());
    Ok(())
}

Examples

Vulnerable Code

pub fn check_oracle(instruction_sysvar: &AccountInfo) -> ProgramResult {
    // No bounds check -- panics if instruction index 1 doesn't exist
    let oracle_ix = load_instruction_at_checked(1, instruction_sysvar)?;
    // No length check -- panics if data is too short
    let price = u64::from_le_bytes(oracle_ix.data[0..8].try_into().unwrap());
    Ok(())
}

Fixed Code

pub fn check_oracle(instruction_sysvar: &AccountInfo) -> ProgramResult {
    let count = load_current_index_checked(instruction_sysvar)? as usize + 1;
    if count < 2 {
        return Err(ProgramError::InvalidArgument);
    }
    let oracle_ix = load_instruction_at_checked(1, instruction_sysvar)?;
    if oracle_ix.data.len() < 8 {
        return Err(ProgramError::InvalidInstructionData);
    }
    let price = u64::from_le_bytes(oracle_ix.data[0..8].try_into().unwrap());
    Ok(())
}

Sample Sigvex Output

[HIGH] Instruction Introspection Without Bounds Check
  Location: check_oracle (block 0, stmt 0)
  Description: Instruction introspection call uses index without bounds
    validation. An attacker can craft transactions with fewer instructions.
  CWE: CWE-129 (Improper Validation of Array Index)

Detection Methodology

  1. Syscall identification: The detector identifies calls to instruction introspection syscalls (sol_get_sysvar_instructions, load_instruction_at, get_instruction).
  2. Bounds validation tracking: For each introspection call, it checks whether the index argument was previously validated via a conditional comparison, or whether instruction count was queried before the access.
  3. Length validation tracking: After the introspection result is obtained, the detector checks whether the instruction data length is validated before deserialization.
  4. Constant index analysis: Negative indices trigger critical findings. Indices of 10 or higher trigger medium-severity findings as suspicious.

Limitations

False positives: Programs that use load_instruction_at_checked (the safe variant) may still be flagged if the detector does not distinguish checked from unchecked variants at the bytecode level. False negatives: Bounds checks in helper functions called before the introspection may not be detected without interprocedural analysis.

References