new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Solana & Anchor Auditor · all subjects

CPI and program confusion

6 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Is the CPI target program id verified?

Arbitrary CPI is the Solana reentrancy-class bug: an instruction receives a `program_id` account (commonly `token_program`) and invokes it without checking the address. Attacker deploys a mimic program with the same instruction interface, passes it instead of SPL Token, and the "transfer" does nothing (or logs fake success) while the protocol credits a deposit. Worse with `invoke_signed`: the mimic receives the protocol's PDA signature and can be coded to do anything that signature authorizes in a composed transaction. Fix: Anchor `token_program: Program<'info, Token>` enforces the address; raw code needs `if *token_program.key != spl_token::ID { return Err }`. Detection signal: `invoke`/`invoke_signed` where the program id comes from an instruction account whose address isn't asserted. As of early 2026, also check Token-2022 pinning — `Interface<'info, TokenInterface>` accepts both, which is its own hazard.

Does this program break or leak value under Token-2022?

Token-2022 (Token Extensions) mints behave differently in ways that break programs written for classic SPL Token: transfer-fee mints deliver LESS than the requested amount to the destination (accounting that credits `amount` on deposit leaks the difference); permanent delegates can move user tokens; default-account-state can freeze new accounts; transfer hooks execute arbitrary program logic mid-transfer (a reentrancy-shaped surface); non-transferable mints make transfers fail outright; and extension-bearing accounts are LARGER than the classic 165-byte token account, so hardcoded size math and `Account` deserialization assumptions break. Rule: if the protocol must support Token-2022, use `transfer_checked`, compute net-of-fee amounts via `get_transfer_fee`, and use `InterfaceAccount<TokenAccount>`; if it must NOT, explicitly pin `token_program` to the classic Token program and reject extension mints — silently accepting both is the finding.

Could sysvar accounts still be spoofed in this codebase?

History: Solana sysvars (clock, rent, instructions...) are passed as accounts, and early runtimes/programs didn't verify that the account claiming to be a sysvar was the genuine sysvar address — attackers passed fake accounts containing fabricated sysvar data. The runtime later hardened this (as of early 2026, loading the well-known sysvars by their reserved addresses is enforced), but the LESSON persists: any account standing in for "system truth" must be validated by address. Modern residue: programs reading the instructions sysvar for flash-loan-guard or CPI-introspection patterns must assert `instructions_sysvar.key == &sysvar::instructions::ID` when taken from accounts; programs trusting a passed `clock`/`rent` account from `remaining_accounts` (to save an explicit account slot) repeat the original sin. Wormhole's $326M loss was exactly this class — treat it as the canonical example, not ancient history.

How should arbitrary CPI targets be constrained?

When an instruction must CPI into a caller-supplied program, the ONLY safe pattern is an explicit whitelist: compare the passed program id against a fixed, compiled-in set of allowed program addresses (constants, or a program-owned config account governed by admins) and reject anything else. Heuristics fail: checking that the program is executable, that it's "well-known", that its return data deserializes, or that it reports success all pass for a malicious mimic implementing the same interface and lying. With `invoke_signed` the stakes double — a whitelist is the difference between your PDA signing a token transfer and signing whatever an attacker encodes. Rule: `require!(ALLOWED_PROGRAMS.contains(program_info.key))` before every dynamic `invoke`; keep the list minimal, version-pinned, and upgrade-governed; and still validate every account passed to the whitelisted call — a legitimate program with hostile accounts drains just as well.

Can a Token-2022 mint impersonate a classic SPL mint?

Yes, if detection logic is naive — and version confusion cuts both ways. A program wanting ONLY classic SPL Token mints but checking nothing beyond "deserializes as a Mint" will accept a Token-2022 mint: both layouts share the base 82-byte mint prefix, so bare `Mint::unpack` succeeds, and a Token-2022 mint with NO extensions behaves identically in transfers — until someone later enables transfer fees, a hook, or a permanent delegate and the protocol's accounting breaks. Robust exclusion requires BOTH: pin `token_program.key() == spl_token::ID` (not `Interface<'info, TokenInterface>` — that type intentionally accepts both), AND verify the mint account's `owner == spl_token::ID` directly, since CPI-program pinning alone doesn't prove which program owns the passed mint. Conversely, programs intending Token-2022 support must branch on the mint's owner and parse the extension TLV data rather than assuming fixed account size. Audit signal: mint owner never compared to the token program id.

Does a CPI transfer automatically run a Token-2022 transfer hook?

Yes. When a Token-2022 mint has the transfer-hook extension initialized, the Token-2022 program CPIs into the designated hook program on EVERY transfer of that mint — including transfers your program initiates via `transfer`/`transfer_checked`. Your code doesn't call the hook; the token program does, mid-transfer, with source, destination, mint, and amount. Security consequences: the hook is arbitrary program logic executing inside your transfer's call stack — a reentrancy-shaped surface (a hostile or buggy hook can observe or interfere with your state mid-instruction), and hooks can fail the whole transfer, a liveness/griefing vector for withdrawals and liquidations. If YOUR program is the hook program, you must validate everything: the caller is genuinely Token-2022, the mint is yours, and the extra-account metas match expectations. Audit rule: any program integrating Token-2022 mints must enumerate extensions (TLV parse), treat hook-bearing mints as untrusted callbacks, and prefer allowlists of known-extension mints.

Give your agent this brain