Skip to main content
Sigvex

Cross-Program State

Detects state inconsistencies and race conditions across CPI boundaries.

Cross-Program State

Overview

The cross-program state detector identifies state inconsistencies across CPI boundaries including race conditions in cross-program interactions, missing atomic operation guarantees, and state validation issues after CPI returns.

For remediation guidance, see Cross-Program State Remediation.

Why This Is an Issue

When a program reads account state, makes a CPI call, and then uses the previously-read state, the CPI call may have modified that state. This creates a Time-of-Check-Time-of-Use race condition where the program operates on stale data, leading to incorrect calculations, authorization bypasses, or fund theft.

How to Resolve

Before (Vulnerable)

let balance = ctx.accounts.vault.amount;  // Read before CPI
invoke(&external_ix, accounts)?;           // May modify vault
// balance is now stale
if balance >= threshold { process_withdrawal()?; }

After (Fixed)

invoke(&external_ix, accounts)?;
ctx.accounts.vault.reload()?;             // Refresh after CPI
let balance = ctx.accounts.vault.amount;   // Fresh read
if balance >= threshold { process_withdrawal()?; }

Example JSON Finding

{
  "detector": "cross-program-state",
  "severity": "high",
  "confidence": 0.7,
  "message": "Account state read before CPI used after CPI without reload",
  "location": { "function": "process", "block": 3, "statement": 1 }
}

Detection Methodology

  1. State read/write tracking: Records account data reads and writes with block positions.
  2. CPI detection: Identifies all CPI calls including expression-based forms.
  3. Post-CPI stale usage: Flags variables read before CPI that are used after CPI without reload.
  4. Per-CPI state invalidation: Tracks which accounts each CPI could modify.

Limitations

False positives: CPI calls to programs that provably do not modify the read accounts. False negatives: State invalidation through indirect account relationships.

References