Skip to main content

07 - Hook Reentrancy

Analysis

The introduction of Token-2022 and Transfer Hooks created a new reentrancy vector in Solana. When a program performs a CPI (Cross-Program Invocation) to transfer tokens, the transfer hook of the token can execute arbitrary logic.

If your program performs a transfer before updating its internal state, a malicious hook can "re-enter" your program and execute instructions while the state is still in an inconsistent, intermediate phase.

Exploit (Insecure)

Executing interactions before updating local effects:

pub fn withdraw_insecure(ctx: Context<WithdrawInsecure>, amount: u64) -> Result<()> {
let vault = &mut ctx.accounts.vault;

// 1. Check (Correct)
if vault.balance < amount { return Err(ErrorCode::InsufficientFunds.into()); }

// 2. Interaction (VULNERABLE)
// If this transfer triggers a hook, the hook can call back into this program
// and withdraw again because the balance hasn't been reduced yet!
perform_transfer(amount)?;

// 3. Effect (Too late)
vault.balance -= amount;
Ok(())
}

Fix (Secure)

Adhere strictly to the Checks-Effects-Interactions (CEI) pattern. Always update your internal state before calling out to another program.

pub fn withdraw_secure(ctx: Context<WithdrawSecure>, amount: u64) -> Result<()> {
let vault = &mut ctx.accounts.vault;

// 1. Check
if vault.balance < amount { return Err(ErrorCode::InsufficientFunds.into()); }

// 2. Effect (SECURE)
// Update state first!
vault.balance -= amount;

// 3. Interaction
// Even if re-entered, the balance is already reduced.
perform_transfer(amount)?;
Ok(())
}