Calling of LiquidityPool#requestDepositWithPermit() or LiquidityPool#requestRedeemWithPermit() could be failed due to a frontrun of a call to ERC20PermitLike.permit() #468
Description
Lines of code
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/LiquidityPool.sol#L220-L226
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/LiquidityPool.sol#L237-L243
Vulnerability details
Impact
Anyone can call ERC20PermitLike(token).permit()
to verify signed message and update corresponding allowance mapping. The signed message can only be permitted one time, any attempt to permit it again will be reverted. Hence malicious user could frontrun ERC20PermitLike(token).permit()
with signed message to block calling of LiquidityPool#requestDepositWithPermit()
or LiquidityPool#requestRedeemWithPermit()
.
Proof of Concept
Add below codes before line 944 of LiquidityPool.t.sol and run test case, LiquidityPool#requestDepositWithPermit()
will be reverted:
erc20.permit(investor, address(investmentManager), amount, block.timestamp, v, r, s);
Add below codes before line 991 of LiquidityPool.t.sol and run test case, LiquidityPool#requestRedeemWithPermit()
will be reverted:
trancheToken.permit(investor, address(investmentManager), maxMint, block.timestamp, v, r, s);
Tools Used
Manual review
Recommended Mitigation Steps
Check allowance before calling permit()
in LiquidityPool#requestDepositWithPermit()
and LiquidityPool#requestRedeemWithPermit()
as Uniswap did:
function requestDepositWithPermit(uint256 assets, address owner, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
public
{
if (IERC20(asset).allowance(owner, address(investmentManager)) < assets) {
ERC20PermitLike(asset).permit(owner, address(investmentManager), assets, deadline, v, r, s);
}
investmentManager.requestDeposit(assets, owner);
emit DepositRequested(owner, assets);
}
function requestRedeemWithPermit(uint256 shares, address owner, uint256 deadline, uint8 v, bytes32 r, bytes32 s)//@audit-ok
public
{
if (share.allowance(owner, address(investmentManager)) < shares) {
share.permit(owner, address(investmentManager), shares, deadline, v, r, s);//@audit-info check if signature is signed by owner and combined by all parameters.
}
investmentManager.requestRedeem(shares, owner);//@audit-info requestRedeem if signature is valid
emit RedeemRequested(owner, shares);
}
Assessed type
Other