Submitted by 0xsomeone
The LendingLedger::sync_ledger function is meant to permit partial transfers of each users positions by updating the amount, rewardDebt, and secRewardDebt variables.
In the current system, the partial transfers performed are insecure as a non-zero _delta can be transferred with zero-value rewardDebt and secRewardDebt 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 cumulative user.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.
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 the LendingLedger::sync_ledger function.
This exploit can be repeated infinitely to compound the rewards extracted via this mechanism.
I coded a simple PoC in foundry, simply add the following function after the LendingLedger.t.sol::setupStateBeforeClaim function:
To execute the above test concisely, kindly perform the following:
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 the cantoPerShare 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 the LendingLedger.
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.
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.
Math
OpenCoreCH (Neobase) confirmed and commented:
0xTheC0der (judge) commented:
