Solana Stake Pool: A Semantic Inconsistency Vulnerability Discovered by Soteria
December 17, 2021
Soteria team recently discovered a vulnerability in the official stake-pool program of solana-program-library. The vulnerability has been patched in this PR. Thanks @joncinque for the swiftest action confirming with us and fixing the issue.
This article describes our journey in discovering the vulnerability and constructing the PoC via penetration testing.
We note that the stake-pool code was audited before by multiple companies. This article motivates the need of a more comprehensive and systematic audit process (e.g., through automated analysis and verification tools as adopted by Soteria team).
What’s the vulnerability: inconsistent initialization
Solana’s stake-pool program is used to pool together SOL and redistribute the stakes across the network, to maximize censorship resistance and rewards.
During initialization, it did not issue tokens for the lamports in the reserve stake account. As a result, if the reserve account has any excess funds on creation, the money will be delegated/represented by the tokens issued for the very first deposit. If this deposit is made by an attacker (for example via a front run), the money in the reserve will be stolen.
The issue is subtle because the pool creator may mistakenly put excess lamports into the reserve stake account, and this behavior is undefined. The vulnerability won’t surface if either the initial reserve account has no money or the first depositor is the pool creator.
Important Notice: Since the stake-pool program has been forked, reused, or modified by other Solana projects, the vulnerable code might have been widely deployed. Please be sure to apply the patch if you are using stake-pool.
How we discovered the vulnerability: semantic inconsistency
Soteria team was experimenting a new checker in our toolbox (an earlier prototype was released here). The new checker exhaustively looks at every path in the code and detects semantic inconsistency issues:
If two things often come together then likely there is a hidden invariant (at the logic or semantic level), and a violation of this invariant (say one of them is missing or in a different order) triggers a warning.
In stake-pool, there exists such an invariant: whenever the reserve_stake account is used (read or write) in an instruction, spl_token::instruction (mint_to, transfer or burn) will also be used in the same instruction.
However, in the process_initialize instruction before the patch, the reserve_stake_account is used (line 637), but there exists no use of spl_token::instruction. Therefore, our checker flags a semantic inconsistency.
The PoC (proof of concept)
Based on the reported semantic inconsistency issue above, we proceeded to construct a PoC via penetration testing (more detail on penetration testing techniques can be found in our prior blog Part 3: penetration testing).