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.
Recommended Fix
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.