Skip to content

[M-03] DoS Attack on Swapping via Permit2 Possible #1016

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: may-14-audit
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions contracts/SpokePoolPeriphery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ contract SwapProxy is Lockable {
// Errors
error SwapFailed();
error UnsupportedTransferType();
error InvalidExchange();

/**
* @notice Constructs a new SwapProxy.
Expand Down Expand Up @@ -72,6 +73,9 @@ contract SwapProxy is Lockable {
SpokePoolPeripheryInterface.TransferType transferType,
bytes calldata routerCalldata
) external nonReentrant returns (uint256 outputAmount) {
// Prevent nonce invalidation attack by disallowing exchange to be the permit2 address
if (exchange == address(permit2)) revert InvalidExchange();

// We'll return the final balance of output tokens

// The exchange will either receive funds from this contract via:
Expand Down
29 changes: 28 additions & 1 deletion test/evm/foundry/local/SpokePoolPeriphery.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
import { Test } from "forge-std/Test.sol";

import { SpokePoolVerifier } from "../../../../contracts/SpokePoolVerifier.sol";
import { SpokePoolPeriphery } from "../../../../contracts/SpokePoolPeriphery.sol";
import { SpokePoolPeriphery, SwapProxy } from "../../../../contracts/SpokePoolPeriphery.sol";
import { Ethereum_SpokePool } from "../../../../contracts/Ethereum_SpokePool.sol";
import { V3SpokePoolInterface } from "../../../../contracts/interfaces/V3SpokePoolInterface.sol";
import { SpokePoolPeripheryInterface } from "../../../../contracts/interfaces/SpokePoolPeripheryInterface.sol";
Expand Down Expand Up @@ -1277,6 +1277,33 @@ contract SpokePoolPeripheryTest is Test {
);
}

/**
* Security tests
*/
function testSwapAndBridgeBlocksPermit2AsExchange() public {
// This test verifies that the fix prevents using permit2 as the exchange address
// which would allow DoS attacks via invalidateNonces
vm.startPrank(depositor);

// Attempt to use permit2 as the exchange - this should fail with InvalidExchange
vm.expectRevert(SwapProxy.InvalidExchange.selector);
spokePoolPeriphery.swapAndBridge(
_defaultSwapAndDepositData(
address(mockWETH),
mintAmount,
0,
address(0),
Exchange(address(permit2)), // Using permit2 as exchange should fail
SpokePoolPeripheryInterface.TransferType.Permit2Approval,
address(mockERC20),
depositAmount,
depositor,
true
)
);
vm.stopPrank();
}

/**
* Helper functions
*/
Expand Down
Loading