Boost _ Folks Finance 34047 - [Smart Contract - Low] Adversaries can create a position that is nearly impossible to liquidate due to high gas consumption
Submitted on Sun Aug 04 2024 16:20:03 GMT-0400 (Atlantic Standard Time) by @nnez for Boost | Folks Finance
Report ID: #34047
Report type: Smart Contract
Report severity: Low
Target: https://testnet.snowtrace.io/address/0xf8E94c5Da5f5F23b39399F6679b2eAb29FE3071e
Impacts:
Protocol insolvency
Description
Description
Users' loan position is stored in a mapping of a struct UserLoan
The borPools
member keeps a list of poolId
values representing the pools that users have positions in.
This is similar to how other lending platforms track the markets a user has entered.
Below is the code snippet responsible for adding a new poolId
to user's loan.
If the current borrow balance for the poolId
is zero, the system adds the poolId
to borPools
. However, this approach has a flaw: it's possible to borrow an amount of 0. If the borrow function is called with an amount of 0 multiple times, the increaseBorrow
function will repeatedly add the same poolId
to borPools
.
borPools
is used in getLoanLiquidity
to calculate the effective value of the loan.
We can see that it iterates over the entire length of the array. Each iteration incurs a significant gas cost because it needs to retrieve the price from the oracle during every iteration. An adversary could exploit this by adding multiple poolId
values to the array (up to a maximum of 255). This would increase the gas cost significantly, potentially even reaching the block gas limit, making it impossible to liquidate their position as decribe in the following attack scenario.
Attack Scenario
Attacker deposits AVAX as collateral
Attacker borrows USDC out
Attacker's borPools = [USDC]
Attacker starts repeatedly borrowing AVAX with amount=0
Attacker's borPool = [USDC, AVAX, AVAX, AVAX, ...]
Each iteration increases the gas consumption to liquidate attacker's position because it increases the number of iterations in UserLoanLogic#getLoanLiquidity
which is called in liquidation execution flow.
Attacker can keep repeating Step 3 to the maximum of 255 iterations.
In cases where a complex oracle is used (such as nodes with many parents), the gas consumption for liquidation could reach the block gas limit, making it impossible to liquidate the attacker's position.
While the attacker does have to cover the gas fees to create this position, the cost is minimal. Even in the final iteration, where the gas usage approaches the block limit, the expense remains under $20.
Impact
This could result in bad debt for the protocol, as the attacker's position cannot be liquidated.
Recommended Mitigations
Consider setting a maximum limit on the borrow and collateral pools for a single loan.
Proof of concept
Proof-of-Concept
A test in the secret gist demonstrates the attack scenario described. It shows that an attacker with a borrow pool length of 255 could potentially incur a cost of 13 million gas units to execute the liquidation.
Steps
Run
forge init --no-commit --no-git --vscode
.Create a new test file,
FolksNotLiquidate.t.sol
intest
directory.Put the test from secret gist in the file: https://gist.github.com/nnez/6b8c828a6d38b202525bf3c98d532a11
Run
forge t --match-contract FolksNotAbleToLiquidateTest -vv
Observe the gas used to perform liquidation on ALICE's position.
Last updated