GHSA-725g-w329-g7qr kora-lib: Token-2022 Transfer Fee Not Deducted During Payment Verification
SummaryWhen a user pays transaction fees using a Token-2022 token with a `TransferFeeConfig` extension, Kora's `verify_token_payment()` credits the full raw transfer `amount` as the payment value. However, the on-chain SPL Token-2022 program withholds a portion of that amount as a transfer fee, so the paymaster's destination account only receives `amount - transfer_fee`. This means the paymaster consistently credits more value than it actually receives, resulting in systematic financial loss.Severity**High**Affected Component**File:** `crates/lib/src/token/token.rs`**Function:** `verify_token_payment()`**Lines:** 529–654 (specifically 633–639)Root CauseIn `verify_token_payment()`, the `amount` extracted from the parsed SPL transfer instruction is the **pre-fee** amount (what the sender specifies in the `transfer_checked` instruction). The function passes this raw amount to `calculate_token_value_in_lamports()` to determine how many lamports the payment is worth. It never subtracts the Token-2022 transfer fee.The fee estimation path (`fee.rs:analyze_payment_instructions`) correctly accounts for transfer fees by calculating them and adding them to the total fee. But the verification path does not perform the inverse subtraction, creating an asymmetry.Vulnerable CodeFor comparison, the transfer fee calculation exists elsewhere in the codebase and is used during fee estimation:This function exists but is **never called** in `verify_token_payment()`.Proof of ConceptArithmetic DemonstrationGiven:Token-2022 token with 5% transfer fee (500 basis points), whitelisted in `allowed_spl_paid_tokens`Transaction fee cost: 5000 lamports equivalentToken price: 1 token = 5 lamports**What should happen:**User needs to pay 5000 lamports worth → 1000 tokensTransfer fee on 1000 tokens at 5% = 50 tokensPaymaster destination receives: 1000 - 50 = 950 tokens (worth 4750 lamports)User should be required to pay MORE to cover the fee**What actually happens:**User sends `transfer_checked` for `amount = 1000` tokens`verify_token_payment()` calculates: 1000 tokens * 5 lamports/token = 5000 lamports5000 >= 5000 required → **payment verified as sufficient**But paymaster only received 950 tokens (worth 4750 lamports)**Paymaster lost 250 lamports on this transaction****Over 1000 transactions:** Paymaster loses 250,000 lamports (0.25 SOL)Runnable Test (using existing test infrastructure)Impact**Systematic Financial Loss:** The paymaster consistently credits more token value than it receives for every transaction paid with a transfer-fee-bearing Token-2022 token.**Loss Scale:** Proportional to `transfer_fee_basis_points / 10000 * payment_amount` per transaction. For a token with 5% fee and 100 transactions/day at $1 each, that is $5/day or $1,825/year in losses.**Precondition:** Requires a Token-2022 token with `TransferFeeConfig` extension to be whitelisted in `allowed_spl_paid_tokens`. The existing test infrastructure already creates such tokens (`TestAccountSetup::create_usdc_mint_2022()` with 100 bps / 1% fee).RecommendationDeduct the Token-2022 transfer fee before calculating the lamport value of the payment:References`crates/lib/src/token/token.rs:529-654` — `verify_token_payment()` using raw amount`crates/lib/src/token/spl_token_2022.rs:165-198` — `calculate_transfer_fee()` (exists but not called in verification)`crates/lib/src/fee/fee.rs:174-204` — `analyze_payment_instructions()` (correctly accounts for transfer fee in estimation)SPL Token-2022 specification: transfer fees are deducted from the transfer amount by the on-chain program