Withdraw does not verify that the LP mint belongs to the pool
programs/corvid-amm/src/instructions/withdraw.rs:L18–L52
- Likelihood
- High
- Impact
- High
- Weakness
- CWE-345
- Description
The
Withdrawaccounts struct declareslp_mint: Account<'info, Mint>without anaddress = pool.lp_mintorhas_one = lp_mintconstraint. Anchor therefore verifies only that the account is a valid mint owned by the SPL Token program, not that it is this pool’s mint.The share of reserves returned is computed as
lp_amount / lp_mint.supplyapplied to both vaults, and the burn is performed against the supplied mint. An attacker can create a new mint, mint a single token to themselves, and pass that mint and token account towithdraw.The program burns the attacker’s one token from the fake mint, computes a share of 1/1 = 100%, and transfers the entire contents of both vaults.
- Preconditions
- The attacker can create an SPL mint (anyone can)
- The pool holds non-zero reserves
- Exploit scenario
- 1.Attacker creates mint M with themselves as mint authority and mints 1 token.
- 2.Attacker calls withdraw on the target pool, passing M as lp_mint and their M token account as the LP source.
- 3.The program burns 1 token of M and computes share = 1 / supply(M) = 100%.
- 4.Both vaults are transferred to the attacker’s token accounts in one transaction.
- Impact
Complete loss of both reserves in every pool, in a single transaction per pool, with no capital required.
- Recommendation
Constrain
lp_minttopool.lp_mint(for example#[account(mut, address = pool.lp_mint)]orhas_one = lp_minton the pool), and add a negative test that passes a foreign mint and expects failure.- Fix
- programs/corvid-amm/src/instructions/withdraw.rsdiff
#[derive(Accounts)] pub struct Withdraw<'info> {- #[account(mut)]+ #[account(mut, has_one = lp_mint, has_one = vault_a, has_one = vault_b)] pub pool: Account<'info, Pool>,- #[account(mut)]+ #[account(mut, address = pool.lp_mint)] pub lp_mint: Account<'info, Mint>, - Resolution
Address constraint added, plus has_one constraints for the mint and both vaults. Negative tests pass a foreign mint and foreign vaults and expect ConstraintHasOne errors.
