Skip to main content
Sigvex

Instruction Index Validation Remediation

How to fix hardcoded instruction index comparisons that can be bypassed.

Instruction Index Validation Remediation

Overview

Related Detector: Instruction Index Validation

Hardcoded instruction index comparisons (e.g., index == 0) can be bypassed by prepending instructions to the transaction. The fix involves validating the total instruction count, verifying the identity of neighboring instructions, or using CPI instead of instruction introspection.

Before (Vulnerable)

pub fn verify_first(ix_sysvar: &AccountInfo) -> ProgramResult {
    let current = load_current_index_checked(ix_sysvar)?;
    // Bypassed by prepending a harmless instruction
    require!(current == 0, InvalidInstructionOrder);
    Ok(())
}

After (Fixed)

pub fn verify_context(
    ix_sysvar: &AccountInfo,
    expected_program: &Pubkey,
) -> ProgramResult {
    // Validate total instruction count
    let current = load_current_index_checked(ix_sysvar)? as usize;

    // Verify preceding instruction is from expected program
    if current > 0 {
        let prev = load_instruction_at_checked(current - 1, ix_sysvar)?;
        require!(
            prev.program_id == *expected_program,
            InvalidPrecedingInstruction
        );
    }

    // Verify following instruction is from expected program
    if let Ok(next) = load_instruction_at_checked(current + 1, ix_sysvar) {
        require!(
            next.program_id == *expected_program,
            InvalidFollowingInstruction
        );
    }

    Ok(())
}

Alternative Mitigations

Validate instruction count to ensure the transaction has exactly the expected number of instructions:

let count = get_instruction_count(ix_sysvar)?;
require!(count == EXPECTED_INSTRUCTION_COUNT, UnexpectedInstructionCount);

Use CPI instead of introspection where possible. CPI calls enforce program identity at the runtime level and do not depend on transaction-level instruction ordering.

Common Mistakes

Replacing index == 0 with index == 1 after discovering the bypass. This only works if the attacker prepends exactly one instruction. The fundamental issue is relying on absolute position.

Checking the instruction count but not the instruction identity. An attacker can pad the transaction with no-op instructions from any program to match the expected count while still reordering the meaningful instructions.

Assuming Anchor prevents this. Anchor uses discriminator-based dispatch, but instruction introspection via the sysvar still uses indices. Anchor does not validate transaction-level instruction ordering.

References