Address Lookup Table Poisoning
Detects unsafe use of Address Lookup Tables without authority validation, allowing attackers to substitute malicious accounts.
Address Lookup Table Poisoning
Overview
Remediation Guide: How to Fix Address Lookup Table Poisoning
The address lookup table (ALT) poisoning detector identifies Solana programs that load addresses from Address Lookup Tables without verifying the table’s authority or validating loaded addresses before use in critical operations. Versioned transactions use ALTs to compress account lists, but if a program trusts addresses from an attacker-controlled lookup table, the attacker can cause addresses to resolve to malicious accounts.
Sigvex tracks ALT load syscalls, table authority validation (owner checks on the lookup table account), and address validation (branch conditions on loaded variables). Programs that use ALT-loaded addresses in critical operations without prior validation are flagged. CWE mapping: CWE-345 (Insufficient Verification of Data Authenticity).
Why This Is an Issue
Address Lookup Tables are a Solana feature for versioned transactions that allow a single table to hold up to 256 addresses. Programs reading addresses from ALTs must verify:
- Table authority: The ALT was created and managed by a trusted authority, not an attacker.
- Address integrity: Loaded addresses match expected program or account identities.
- Table state: The table is initialized and has not been modified by an attacker mid-transaction.
Without these checks, an attacker can:
- Create a malicious ALT with addresses pointing to attacker-controlled accounts.
- Substitute the malicious ALT in a versioned transaction.
- Cause the program to CPI into attacker-controlled programs or transfer funds to attacker accounts.
How to Resolve
Native Rust
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
const TRUSTED_ALT_AUTHORITY: Pubkey = /* your trusted authority */;
pub fn use_lookup_table(accounts: &[AccountInfo]) -> ProgramResult {
let lookup_table = &accounts[0];
let loaded_account = &accounts[1];
// Step 1: Verify lookup table authority
if lookup_table.owner != &solana_address_lookup_table_program::id() {
return Err(ProgramError::IncorrectProgramId);
}
// Step 2: Validate loaded address matches expected value
if loaded_account.key != &EXPECTED_PROGRAM_ID {
return Err(ProgramError::InvalidArgument);
}
// Safe to use loaded_account
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct UseAlt<'info> {
/// CHECK: Validated against expected address
#[account(constraint = target_program.key() == EXPECTED_PROGRAM_ID)]
pub target_program: AccountInfo<'info>,
}
Examples
Vulnerable Code
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
pub fn swap_via_alt(accounts: &[AccountInfo]) -> ProgramResult {
let dex_program = &accounts[0]; // Loaded from ALT -- no validation!
let user_token = &accounts[1];
let pool_token = &accounts[2];
// CPI to unvalidated program -- attacker substitutes a malicious program
solana_program::program::invoke(
&create_swap_instruction(dex_program.key, user_token.key, pool_token.key),
&[dex_program.clone(), user_token.clone(), pool_token.clone()],
)?;
Ok(())
}
Fixed Code
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
const AUTHORIZED_DEX: Pubkey = /* known DEX program ID */;
pub fn swap_via_alt(accounts: &[AccountInfo]) -> ProgramResult {
let dex_program = &accounts[0];
let user_token = &accounts[1];
let pool_token = &accounts[2];
// Validate the DEX program address before CPI
if dex_program.key != &AUTHORIZED_DEX {
return Err(ProgramError::IncorrectProgramId);
}
solana_program::program::invoke(
&create_swap_instruction(dex_program.key, user_token.key, pool_token.key),
&[dex_program.clone(), user_token.clone(), pool_token.clone()],
)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "address-lookup-table-poisoning",
"severity": "high",
"confidence": 0.78,
"description": "Program loads addresses from an Address Lookup Table without validating the table's authority. An attacker can create a malicious lookup table causing addresses to resolve to attacker-controlled accounts.",
"location": { "function": "swap_via_alt", "offset": 0 }
}
Detection Methodology
The detector performs two analysis passes:
- Pattern collection: Identifies ALT load syscalls (by syscall name heuristics including “lookup”, “alt”, “address_table”, “load_address”), table authority checks (AccountOwner reads), and address validations (branch conditions on loaded variables).
- Validation gap detection: Reports ALT loads without table authority validation, and loaded addresses used in critical operations (function calls, CPI) without prior address validation.
Context modifiers:
- Anchor programs: confidence reduced by 35% (Anchor does not validate ALTs; ALT poisoning is a transaction-level concern)
- Admin/initialization functions: confidence reduced by 40%
- Read-only/view functions: confidence reduced by 70%
Limitations
False positives:
- Programs that validate ALT addresses through external oracles or off-chain verification may be flagged.
- ALT authority validation performed in a separate instruction within the same transaction is not visible to the detector.
False negatives:
- Indirect ALT usage through helper functions that abstract the load syscall may not be detected.
- ALTs loaded in the transaction runtime (not via explicit syscall in the program) are not visible at the program level.
Related Detectors
- Account Executable Flag — detects missing executable validation for CPI targets
- CPI Program Validation — detects unvalidated program IDs in cross-program invocations