Lucene search

K
code423n4Code4renaCODE423N4:2023-08-VERWA-FINDINGS-ISSUES-415
HistoryAug 10, 2023 - 12:00 a.m.

check for the reentrancy attack is missed in the claim function

2023-08-1000:00:00
Code4rena
github.com
7
reentrancy
claim function
lendingledger.sol
native token
reentrancy attack
double call
drain
openzeppelin
recommended mitigation

Lines of code
<https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/LendingLedger.sol#L179&gt;

Vulnerability details

Impact

the function claim in the LendingLedger.sol will send native token $CANTO to the msg.sender by .call which it can be EOA or Contracts, because there is no any RA checks the caller can make double call in the same time to get himself more tokens reward than deserved and even drain the contract tokens.

Proof of Concept

attacker can use a contract that will reentrant the LendingLedger.sol when it receive native token by set a specific logic in side a receive function which it call the claim function before the first call ended and before the all state changes happen which in this case the attacker can take more tokens rewards than deserved. contract like this will allow this case happen:
<https://solidity-by-example.org/hacks/re-entrancy/&gt;

the claim function:

 function claim(
        address _market,
        uint256 _claimFromTimestamp,
        uint256 _claimUpToTimestamp
    ) external is_valid_epoch(_claimFromTimestamp) is_valid_epoch(_claimUpToTimestamp) {
        address lender = msg.sender;
        uint256 userLastClaimed = userClaimedEpoch[_market][lender];
        require(userLastClaimed &gt; 0, "No deposits for this user");
        _checkpoint_lender(_market, lender, _claimUpToTimestamp);
        _checkpoint_market(_market, _claimUpToTimestamp);
        uint256 currEpoch = (block.timestamp / WEEK) * WEEK;
        uint256 claimStart = Math.max(userLastClaimed, _claimFromTimestamp);
        uint256 claimEnd = Math.min(currEpoch - WEEK, _claimUpToTimestamp);
        uint256 cantoToSend;
        if (claimEnd &gt;= claimStart) {
            // This ensures that we only set userClaimedEpoch when a claim actually happened
            for (uint256 i = claimStart; i &lt;= claimEnd; i += WEEK) {
                uint256 userBalance = lendingMarketBalances[_market][lender][i];
                uint256 marketBalance = lendingMarketTotalBalance[_market][i];
                RewardInformation memory ri = rewardInformation[i];
                require(ri.set, "Reward not set yet"); // Can only claim for epochs where rewards are set, even if it is set to 0
                uint256 marketWeight = gaugeController.gauge_relative_weight_write(_market, i); // Normalized to 1e18
                cantoToSend += (marketWeight * userBalance * ri.amount) / (1e18 * marketBalance); // (marketWeight / 1e18) * (userBalance / marketBalance) * ri.amount;
            }
            userClaimedEpoch[_market][lender] = claimEnd + WEEK;
        }
        //@audit RA may happen
        if (cantoToSend &gt; 0) {
            (bool success, ) = msg.sender.call{value: cantoToSend}("");
            require(success, "Failed to send CANTO");
        }
    }

Tools Used

manual review

Recommended Mitigation Steps

add reentrancy checks or use the OZ reentrancy modifier:
<https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol&gt;

Assessed type

Reentrancy


The text was updated successfully, but these errors were encountered:

All reactions