Malt Finance contest - ScopeLift's results

Yield farmable, incentive-centric algorithmic stable coin.

General Information

Platform: Code4rena

Start Date: 25/11/2021

Pot Size: $80,000 USDC

Total HM: 35

Participants: 32

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 27

Id: 59

League: ETH

Malt Finance

Findings Distribution

Researcher Performance

Rank: 11/32

Findings: 5

Award: $2,139.80

🌟 Selected for report: 6

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: gpersoon

Also found by: ScopeLift, leastwood

Labels

bug
duplicate
2 (Med Risk)

Awards

298.0144 USDC - $298.01

External Links

Handle

ScopeLift

Vulnerability details

Impact

TBD how bad the impact is

The docs mention that the notSameBlock modifier (and associated _notSameBlock() method) is used to guard against reentrancy. However, users can still call a method twice in a single transaction by using transferring assets/positions between two accounts they control, and re-entering with the other account

Proof of Concept

N/A

Tools Used

Change notSameBlock to a standard reentrancy guard such as this one from Solmate

abstract contract ReentrancyGuard {
    uint256 private reentrancyStatus = 1;

    modifier nonReentrant() {
        require(reentrancyStatus == 1, "REENTRANCY");
        reentrancyStatus = 2;
        _;
        reentrancyStatus = 1;
    }
}

#0 - 0xScotch

2021-12-08T11:40:08Z

#195

#1 - GalloDaSballo

2022-01-09T23:34:36Z

In lack of POC this goes down to medium

#2 - GalloDaSballo

2022-01-09T23:34:43Z

Duplicate of #195

Findings Information

🌟 Selected for report: ScopeLift

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

1103.7569 USDC - $1,103.76

External Links

Handle

ScopeLift

Vulnerability details

Impact

The internal _withdraw method does not follow the checks-effects-interactions pattern. A malicious token, or one that implemented transfer hooks, could re-enter the public calling function (such as withdraw()) before proper internal accounting was completed. Because the earned function looks up the _userWithdrawn mapping, which is not yet updated when the transfer occurs, it would be possible for a malicious contract to re-enter _withdraw repeatedly and drain the pool.

Proof of Concept

N/A

Tools Used

N/A

The internal accounting should be done before the transfer occurs:

function _withdraw(address account, uint256 amountReward, address to) internal {
    _userWithdrawn[account] += amountReward;
    _globalWithdrawn += amountReward;f

   rewardToken.safeTransfer(to, amountReward);

    emit Withdraw(account, amountReward, to);
  }

#0 - GalloDaSballo

2022-01-09T23:41:55Z

The warden identified a re-entrancy vulnerability that, given the right token would allow to drain the entirety of the contract.

Tokens with hooks (ERC777 and ERC677) would allow to exploit the contract and drain it in it's entirety.

This is a very serious vulnerability. However it can happen exclusively on a malicious or a token with hooks, as such (while I recommend the sponsor to mitigate by following recommendation by the warden), the attack can be completely prevented by using a token without hooks.

For that reason I'll rate the finding of medium severity (as it requires external conditions)

Findings Information

🌟 Selected for report: ScopeLift

Also found by: nathaniel

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

496.6906 USDC - $496.69

External Links

Handle

ScopeLift

Vulnerability details

Impact

On lines 85 and 101, ETH is transferred using a .call to an address provided as an input, but there is no verification that the call call succeeded. This can result in a call to emergencyWithdrawGAS or partialWithdrawGAS appearing successful but in reality it failed. This can happen when the provided destination address is a contract that cannot receive ETH, or if the amount provided is larger than the contract's balance

Proof of Concept

Enter the following in remix, deploy the Receiver contract, and send 1 ETH when deploying the Permissions contract. Call emergencyWithdrawGAS with the receiver address and you'll see it reverts. This would not be caught in the current code

pragma solidity ^0.8.0;

contract Receivier{}

contract Permissions {
    constructor() payable {}

    function emergencyWithdrawGAS(address payable destination) external {
        (bool ok, ) = destination.call{value: address(this).balance}('');
        require(ok, "call failed");
    }
}

Tools Used

Remix

In emergencyWithdrawGAS:

- destination.call{value: address(this).balance}('');
+ (bool ok, ) = destination.call{value: address(this).balance}('');
+ require(ok, "call failed");

And similar for partialWithdrawGAS

#0 - GalloDaSballo

2022-01-22T15:33:17Z

Agree with the finding, I believe if a developer were to not use safeTransfer we'd rate as medium, so while I believe the impact to be minimal (no composability), I'll keep the severity to medium

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter