Instruction Fallback Handler Remediation
How to fix unsafe fallback logic for unrecognized instruction discriminators.
Instruction Fallback Handler Remediation
Overview
Related Detector: Instruction Fallback Handler
An unsafe fallback handler allows unrecognized instruction discriminators to execute code that was never intended to be callable. The fix requires ensuring every discriminator match includes a catch-all arm that returns an error, and that entry point functions always read and validate the discriminator before dispatching.
Recommended Fix
Before (Vulnerable)
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let tag = instruction_data[0];
if tag == 0 {
return handle_init(accounts, instruction_data);
}
if tag == 1 {
return handle_transfer(accounts, instruction_data);
}
// No error for unknown tags -- falls through silently
Ok(())
}
After (Fixed)
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
if instruction_data.is_empty() {
return Err(ProgramError::InvalidInstructionData);
}
match instruction_data[0] {
0 => handle_init(accounts, &instruction_data[1..]),
1 => handle_transfer(accounts, &instruction_data[1..]),
_ => Err(ProgramError::InvalidInstructionData),
}
}
Alternative Mitigations
Use Anchor’s dispatch system. Anchor generates unique 8-byte discriminators from the instruction name hash and rejects any instruction whose discriminator does not match a declared handler:
#[program]
pub mod my_program {
pub fn initialize(ctx: Context<Initialize>) -> Result<()> { Ok(()) }
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> { Ok(()) }
// Unknown discriminators automatically return an error
}
Use an enum-based dispatch pattern for native programs to ensure exhaustive matching:
pub enum Instruction {
Initialize,
Transfer { amount: u64 },
}
impl Instruction {
pub fn unpack(data: &[u8]) -> Result<Self, ProgramError> {
let (tag, rest) = data.split_first()
.ok_or(ProgramError::InvalidInstructionData)?;
match tag {
0 => Ok(Self::Initialize),
1 => { /* parse amount from rest */ Ok(Self::Transfer { amount }) },
_ => Err(ProgramError::InvalidInstructionData),
}
}
}
Common Mistakes
Using if/else if chains without a final else that errors. The last branch silently succeeds for any unrecognized value.
Returning Ok(()) in the default case. A no-op default appears harmless but allows attackers to submit garbage transactions that succeed, potentially interfering with transaction ordering assumptions.
Checking the discriminator after performing side effects. If the program writes account data before validating the discriminator, the validation is ineffective for preventing unauthorized state changes.