Skip to content

Calling of LiquidityPool#requestDepositWithPermit() or LiquidityPool#requestRedeemWithPermit() could be failed due to a frontrun of a call to ERC20PermitLike.permit() #468

Open
@c4-submissions

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

Metadata

Assignees

No one assigned

    Labels

    Q-23QA (Quality Assurance)Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntaxbugSomething isn't workingdowngraded by judgeJudge downgraded the risk level of this issueedited-by-wardengrade-asatisfactorysatisfies C4 submission criteria; eligible for awardssponsor confirmedSponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")sufficient quality reportThis report is of sufficient quality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions