ERC-2612 permit frontrunning allows griefing DoS on all permit-dependent Bridge operations
Bridge calls IERC20Permit.permit() without a try/catch wrapper, so an attacker who frontruns the permit signature from the mempool consumes the nonce and forces every permit-dependent Bridge operation to revert.
Description
The _executePermits function calls IERC20Permit2.permit() directly
without a try/catch wrapper:
IERC20Permit2(permit.token).permit(ectx.user,address(this),permit.amount,permit.deadline,permit.v,permit.r,permit.s);
ERC-2612 permit signatures are visible in the mempool before inclusion. An
attacker can extract the permit parameters from a pending transaction's
calldata and frontrun by calling permit() directly on the ERC-20 token
contract. The attacker's transaction succeeds (setting the allowance and
consuming the nonce), so when the victim's Bridge transaction is included,
the permit() call reverts because the signature/nonce is already used ,
causing the entire operation to fail.
This affects three critical Bridge functions that rely on _executePermits:
registerKeyPairWithTransfer, key pair registration with token transferstransferBalanceToLatestKey, balance migration to latest rotated keyupgradeWithKeyRotation, contract upgrade with key rotation
The attacker cannot steal funds or profit from this attack, but can repeatedly grief any user attempting these operations, effectively blocking key registration, balance transfers, and upgrades that involve ERC-20 permits.
Impact
An attacker can prevent any permit-dependent Bridge operation from
completing by frontrunning the permit signature. Users are unable to
register key pairs with token transfers, migrate balances, or perform
upgrades with key rotation until they work around the griefing by using a
standard approve transaction instead, which requires an additional
on-chain transaction and awareness of the issue.
Recommendation
Wrap the permit() call in a try/catch block. If the permit fails, check
whether the allowance is already sufficient (which it will be if the
attacker already submitted the permit). This is the standard mitigation
used across the industry:
try IERC20Permit2(permit.token).permit(ectx.user,address(this),permit.amount,permit.deadline,permit.v,permit.r,permit.s) {} catch {if (IERC20(permit.token).allowance(ectx.user,address(this)) < permit.amount) {revert InsufficientAllowance();}}
Resolution
YadaCoin, Confirmed.
Zealynx, Fixed.

