Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Threat Contained: marginfi Flash Loan Vulnerability - 1736

Felix WilhelmPosted 5 Months Ago
  • Flash loans, or the borrowing of a large amount of money within a single transaction, work because they must be repaid with interest by the end of the transaction. In EVM, an external call from the context of the smart contract can be made with a callback to a user-controlled contract.
  • In Solana, Cross-Program Invocations (CPI) make this too hard to do. Therefore, instruction introspection is used to ensure that the flash loan is indeed paid by examining the instructions that are supposed to be executed in the future. The instruction sysvar account makes this possible.
  • MarginFi is a lending platform on Solana but it adds some extra flexibility to this. The account MarginfiAccount is used to track users' assets and liabilities. It must remain healthy at all times except in the case that a flash loan has been created for it. In this case, the health check is skipped, assuming that everything will be resolved by the end of the call.
  • When creating a flashloan, the lending_account_start_flashloan function will ensure that there's a following call to lending_account_end_flashloan. Using this call, a health check is performed to ensure that the funds from the flash loan have been returned.
  • Recently, a new instruction called transfer_to_new_account was created. This is for migrating the original MarginfiAccount to a fresh account and empties the original one. This call fails to ensure that we're not in the middle of a flash loan though!
  • This leads to the following attack:
    1. Create an account A with a flash loan.
    2. Borrow the maximum amount of funds for the flash loan.
    3. Call transfer_to_new_account to move the outstanding liabilities from A to a new account B.
    4. End the loan on account A. Since the liabilities are now good and the call to lending_account_end_flashloan is made, this is sufficient.
    5. End the call and never repay the funds. Now, account B contains a flash loan account that has never been paid back.
  • A solid find from Felix! A good lesson for me is that even small changes in functionality can have horrible consequences. The more functionality you have, the more these can interact and cause havoc with each other.