Open c4-bot-10 opened 3 months ago
MarioPoneder marked the issue as primary issue
That's true and rounding up there is probably a good idea (although it could generate underflows in claim
if this is not changed as well). However, I am not fully convinced about the impact. If (uint256(_delta) * market.accCantoPerShare)
is for instance 1e18 - 1
, the "correct" debt value is 0.999999..., so rounded up it would be 1. We then underestimate the debt by exactly 1 token, which has a value of 10^{-18} USD in the case of cNOTE.
OpenCoreCH (sponsor) confirmed
Limited impact but high likelihood since there is no boundary on the repeatability of the attack, which can lead to further impacts (see Severity Rationalization of report.)
MarioPoneder marked the issue as satisfactory
MarioPoneder marked the issue as selected for report
Lines of code
https://github.com/code-423n4/2024-03-neobase/blob/main/src/LendingLedger.sol#L105-L113
Vulnerability details
Description
The
LendingLedger::sync_ledger
function is meant to permit partial transfers of each user's positions by updating theamount
,rewardDebt
, andsecRewardDebt
variables.In the current system, the partial transfers performed are insecure as a non-zero
_delta
can be transferred with zero-valuerewardDebt
andsecRewardDebt
increments.Specifically, any non-zero
_delta
value that would result in(_delta * market.accCanotPerShare) / 1e18
to result in a truncation can be transferred multiple times to exploit the truncation.As the
LendingLedger::claim
function will utilize the cumulativeuser.amount
value, multiple_delta
transfers can result in a non-truncated(user.amount * market.accCanotPerShare) / 1e18
value that would result in immediate profit as the user would be able to instantly claim the truncated amounts.Impact
A user is able to claim rewards from the
LendingLedger
without any time elapsing and without being eligible for them by taking advantage of debt truncations in theLendingLedger::sync_ledger
function.This exploit can be repeated infinitely to compound the rewards extracted via this mechanism.
Proof of Concept
I coded a simple PoC in
foundry
, simply add the following function after theLendingLedger.t.sol::setupStateBeforeClaim
function:To execute the above test concisely, kindly perform the following:
Severity Rationalization
The above PoC is meant to demonstrate the flaw and does not do so efficiently. In detail, the profitability of the attack versus its gas cost is inherently attached to the error between the divisor (
1e18
) and thecantoPerShare
value.For an error equal to
1e18 - 1
, the attack can be executed in as little as two transfers and can be compounded infinitely. Even if we consider the attack not profitable, an off-by-one error has wider implications in the accounting system of theLendingLedger
.Specifically, the off-by-one error will cause the cumulative rewards of the system to not satisfy all awarded users. As a result, the last user who attempts to claim rewards will cause their claim to fail.
We can argue that there are a lot of avenues to resolve the accounting error once it presents itself, however, the value extracted would be irreversible, and the failure itself could be perpetuated.
Tools Used
Manual Review.
Recommended Mitigation Steps
For the mechanism to behave correctly, it should penalize rewards rather than penalize debt when truncation occurs. To achieve this, the subtraction execution path of
LendingLedger::sync_ledger
should continue subtracting the rounded-down amount from debt while the addition execution path should round the debt added upwards.Assessed type
Math