Skip to main content
Sigvex

Emergency Pause Remediation

How to fix missing or vulnerable emergency pause mechanisms in Solana programs.

Emergency Pause Remediation

Overview

Related Detector: Emergency Pause

Missing or flawed emergency pause mechanisms leave programs unable to halt operations when a vulnerability is discovered. The fix involves implementing a pause state stored in an on-chain account, checking that state before every critical operation, and restricting pause authority to verified accounts.

Before (Vulnerable)

pub fn transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let vault = &accounts[0];
    let dest = &accounts[1];
    // No pause check -- no way to stop exploitation
    **vault.try_borrow_mut_lamports()? -= amount;
    **dest.try_borrow_mut_lamports()? += amount;
    Ok(())
}

After (Fixed)

pub fn transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let authority = &accounts[0];
    let vault = &accounts[1];
    let dest = &accounts[2];
    let pause_account = &accounts[3];

    // Check pause state
    let pause_data = pause_account.try_borrow_data()?;
    if pause_data[0] == 1 {
        return Err(ProgramError::InvalidInstructionData);
    }
    drop(pause_data);

    if !authority.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    **vault.try_borrow_mut_lamports()? -= amount;
    **dest.try_borrow_mut_lamports()? += amount;
    Ok(())
}

Alternative Mitigations

1. Anchor pause guard

Use a reusable account constraint pattern:

#[derive(Accounts)]
pub struct GuardedTransfer<'info> {
    pub authority: Signer<'info>,
    #[account(constraint = !global_state.paused @ ErrorCode::Paused)]
    pub global_state: Account<'info, GlobalState>,
    #[account(mut)]
    pub vault: Account<'info, Vault>,
}

#[account]
pub struct GlobalState {
    pub paused: bool,
    pub pause_authority: Pubkey,
}

2. Guardian multi-sig pause

Require multiple guardians to agree before pausing:

pub fn vote_pause(accounts: &[AccountInfo]) -> ProgramResult {
    let guardian = &accounts[0];
    let pause_state = &accounts[1];

    if !guardian.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Increment pause votes
    let mut data = pause_state.try_borrow_mut_data()?;
    let votes = u8::from_le_bytes([data[1]]) + 1;
    data[1] = votes;

    // Auto-pause at threshold
    if votes >= 2 {
        data[0] = 1; // paused
    }
    Ok(())
}

3. Granular pause (per-operation)

Instead of a global pause, allow pausing specific operations:

#[account]
pub struct PauseConfig {
    pub transfers_paused: bool,
    pub minting_paused: bool,
    pub admin_paused: bool,
    pub pause_authority: Pubkey,
}

Common Mistakes

Mistake 1: Not checking pause state in all critical paths

Every function that performs state mutations or fund transfers must check the pause state. A single unchecked path allows continued exploitation.

Mistake 2: Allowing anyone to toggle pause

// WRONG: anyone can pause/unpause
pub fn toggle_pause(accounts: &[AccountInfo]) -> ProgramResult {
    let pause = &accounts[0];
    let mut data = pause.try_borrow_mut_data()?;
    data[0] = if data[0] == 0 { 1 } else { 0 };
    Ok(())
}

Always verify the caller is the authorized pause authority.

Mistake 3: Pausing read operations unnecessarily

Read-only operations (balance queries, state views) generally do not need pause protection. Over-pausing degrades user experience without security benefit.

References