sec3 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 sec3 team).
Solana’s stake-pool program is used to pool together SOL and redistribute the stakes across the network, to maximize censorship resistance and rewards.
The vulnerability lies in the stake-pool’s process_initialize instruction:
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.
sec3 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.
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).
The first step is to initialize the stake pool:
In the above, the reserve_stake account is initialized with 100 Sol:
Next, the hacker deposits 1.0 Sol into the stake pool and claims 990000000 pool tokens (which is far more than the expected token amount):
Finally, the hacker burns all their pool tokens and withdraws all the money:
sec3 is founded by leading minds in the fields of blockchain security and software verification.
We are pleased to provide audit services to high-impact Dapps on Solana. Please visit sec3.dev or email contact@sec3.dev
For all blogs by sec3, Please visit https://www.sec3.dev/blog