Reentrancies are widespread throughout various functions in Lender.sol.
đ Metrics âšī¸: 10 out of 21 functions (47.6%) are susceptible to reentrancy which leads to attacks.
These vulnerabilities can lead to a lot of undesirable behaviors, from minor glitches to significant financial losses for users. This report highlights some identified scenarios, but other undiscovered exploits might still exist.
Reentrancies in the contract can have various implications:
Some might just trigger duplicate event emissions. đ
Some can cause unnecessary storage consumption. đĻ
The most severe can allow malicious actors to scam funds from valid users. đ¨
The root cause of these issues is the unregulated external calls to ERC20 contracts. Educating users about reviewing token contracts before transactions can mitigate risks. However, relying on user diligence alone is not realistic if we expect mass adoption.
For widespread adoption, building inherently secure applications is crucial. Relying on users to be consistently vigilant exposes them to potential exploitation, especially by malicious ERC20 tokens.
| Symbol | Explanation |
|---|---|
| đ´ | Critical risk: Potential loss of user funds. |
| đĄ | Moderate risk: Risk of unwanted behaviors and exploit of storage consumption. |
| âĒ | Observational: Reentrancy detected but no immediate harmful effect identified, yet. |
repay()Scenario:
Pool configuration: Debt represented by Scammers token (STKN), Collateral could be any valuable token like WBTC.
Malicious Borrower (MB): This is just a disguise. In reality, it's another address owned by the pool's lender.
There are legitimate users whom the scammers have persuaded to use their STKN.
Steps:
1ī¸âŖ. MB borrows a certain amount of STKN, providing 300 WBTC as collateral.
2ī¸âŖ. A legitimate user borrows STKN, depositing 700 WBTC.
3ī¸âŖ. The malicious borrower (MB) then calls repay(). However, the STKN contract contains reentrant code which triggers repay() again, causing it to execute twice.
Assuming MB uses the same loanId for both calls to repay():
The first call acts as expected, returning 300 WBTC to MB.
The second call, however, extracts another 300 WBTC from the pool. This is because the pool, at that moment, has a total of 1000 WBTC and believes it has sufficient balance to return 300 WBTC. This is despite the fact that those 1000 WBTC were deposited by different users.
Visualization of the second call to repay() with inline explanations. Read first the comment marked with 1ī¸âŖââ, then go to the top of the function again and read the comments in execution order:
đ§ Note â ī¸: If you are not familiar on how a reentrancy attacker contract looks like here is an example: Solidity by Example, Reentrancy
Now the pool has 400 WBTC left and if the valid user tries to repay its debt there is no way the pool can give him back the 700 WBTC.
đ§ Note â ī¸: More sophisticated scams can employ genuine, value-bearing tokens as a decoy. They might lure non-technical users through a PROXY contract. This proxy seemingly manages the users' real tokens by redirecting all lending activities to the legitimate ERC20 token contract. However, before this redirection occurs, the proxy might sometimes execute malicious operations. This tactic creates an illusion, leading users to believe that their genuine tokens are being managed correctly. In reality, they're being manipulated through the PROXY contract.
borrow() and setPool()The problem with these 2 functions is essentially the same. They can be used to fill up the contract's storage with fake values effectively making it more expensive to operate with.
In setPool() users can create as much pools as they want and in borrow() users can push as much loans as they want to the loans array. Here reentrancy only helps them with creating more fake objects in storage with less transactions.
đ Notice âšī¸: I've covered this storage exploit more detaildly and proposed solutions in other of my findings.
đ§ Note â ī¸ There might likely be more possible exploits with the reentrancies of these 2 functions.
buyLoan()Emits duplicate events, extra fees are paid, pools' debt can be doubled... Notice, in this case, reentrancy can be avoided just by simply applying the Checks-Effects-Interactions pattern.
zapBuyLoan()As zapBuyLoan() calls buyLoan() and setPool(), it can be used to execute the exploits mentioned in those functions.
refinance() , seizeLoan() and giveLoan()Due to timing reasons, I cannot provide detailed examples for these other 3 functions, but they manage funds and suffer from reentrancy, given the explained errors along the report they are highly likely to be a source for scammed funds or directly, stolen funds.
addToPool() and removeFromPool()I couldn't find any exploit using these functions but reentrancy is posible on them.
The impact is fatal:
System malfunctions. đģ
Financial losses for users. đģ
Duplicate system logs. đģ
High severity repercussions. đģ
Manual audit.
Slither syntax analyzer.
Solidity visual developer function dependency graph.
Incorporate OpenZeppelin's ReentrancyGuard to the functions mentioned. Even to those flagged with âĒ, given the system's complexity, I highly recommend taking this precaution.
Where appropriate, use the Checks-Effects-Interactions pattern because it provides a less gas-intensive solution than ReentrancyGuard.
Enhance code's tests to detect these vulnerabilities. For that you can implement ERC20 mock contracts that apply reentrancy. There are OpenZeppelin codes that can be used for that or guide you in their implementation:
đ§ Note â ī¸ After changes are made, have another audit.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.