Missing check in process_transfer
leading to inflationary bug
Description
Transfers between confidential accounts require a zero knowledge (ZK) argument proving that the source account balance is greater than the transferred amount and that the transferred amount is not negative.
Confidential token transfer transactions consist of two instructions. The first required instruction contains a cryptographic ZK argument that proves the validity of the transfer without disclosing any information about the balances involved or the transferred amount. The other instruction performs the computations and updates the account state to actually perform the transfer.
The ZK argument instruction is processed by a special built-in program that verifies its validity, reverting if validation fails. More specifically, the ZK argument is an equation in which some variables have values that correspond to the state of the accounts involved in the transaction.
The other instruction is processed by the token program. The program verifies that the instruction containing the ZK argument exists and that its inputs are consistent with the state of the involved accounts, tying the ZK argument to the state of the blockchain.
The token program does not correctly verify all the ZK argument inputs. One of the fields associated with the ZK argument, new_source_ciphertext
, is ignored. This field contains the expected value of the source account encrypted balance after the transfer is performed. The lack of this check implies that the source account encrypted balance is not validated. This effectively decouples the ZK argument from the balance of the source account.
Impact
A malicious transaction constructed to exploit the issue allows to perform repeated transfers, totalling an amount bigger than the source account encrypted balance.
We created a proof-of-concept exploit by constructing a transaction with multiple instructions performing a transfer, all referencing the same instruction containing the ZK argument.
The source account encrypted balance underflows and becomes invalid, but the destination account encrypted pending balance is credited multiple times, creating tokens out of nothing and inflating the supply. The supply inflation will not be reflected by the information stored in the mint account associated with the token. The destination account is able to apply the pending balance and make use of the unfairly obtained amount normally.
The PoC would perform the following operations:
[!] Starting double transfer PoC
[!] Current balances:
Alice:
- available balance: 42
- pending balance: 0
Bob:
- available balance: 0
- pending balance: 0
[!] Running malicious transaction. Instructions:
- Instruction 0: TransferWithFeeData instruction
- amount: 42
- Instruction 1: ConfidentialTransferInstruction::Transfer instruction
- Instruction 2: ConfidentialTransferInstruction::Transfer instruction (repeated)
[!] Current balances:
Alice: could not decrypt balances
Bob:
- available balance: 0
- pending balance: 84
[!] Applying Bob pending balance
[!] Current balances:
Alice: could not decrypt balances
Bob:
- available balance: 84
- pending balance: 0
Recommendations
Ensure that the source account encrypted balance corresponds to the expected amount contained in the ZK argument (the new_source_ciphertext
field of the TransferData
struct).
Remediation
The Solana Foundation team was alerted of this finding while the audit was ongoing. The team quickly confirmed the issue and submitted a remediation patch for our review. The patch correctly implements the suggested remediation.
Pull request #3867↗ fixes the issue following our recommendation. The PR head commit c7fbd4b
was merged in the master
branch on December 3, 2022.
The confidential token transfer extension was not used at the time the audit was conducted; therefore, no funds were at risk.