Skip to content

Latest commit

 

History

History
146 lines (103 loc) · 3.98 KB

036.md

File metadata and controls

146 lines (103 loc) · 3.98 KB

Expert Tawny Sawfish

Medium

Permit signature can be frontrun in GovernanceStakerPermitAndStake's permitAndStake() functions causing denial of service

Summary

A frontrunning vulnerability exists in the GovernanceStakerPermitAndStake contract where an attacker can invalidate a user's EIP-2612(Inherited through IERC20Permit here) permit signature before their transaction executes, causing their staking operation to fail.

Vulnerability Detail

The vulnerability stems from how EIP-2612 permit signatures work in combination with the contract's staking functions. Here's the internal working of EIP-2612 and how it leads to the vulnerability:

  1. EIP-2612's Internal working
  • Each user has a nonce counter starting at 0.
  • A permit signature contains:
struct Permit {
    address owner;      // Token owner
    address spender;    // Address getting approval
    uint256 value;      // Amount being approved
    uint256 nonce;      // Current nonce of owner
    uint256 deadline;   // Signature expiry
}
  1. When permit() is called, the contract:
  • Verifies the deadline hasn't passed
  • Recovers signer from the signature
  • Verifies signer is the owner
  • Verifies nonce matches owner's current nonce
  • Increments owner's nonce
  • Sets allowance
  1. Attack Path:
  • User signs permit with their current nonce (N)
  • Attacker sees the transaction in mempool
  • Attacker extracts signature parameters
  • Attacker frontruns by calling permit() directly
  • User's nonce increments to N+1
  • User's original transaction fails as signature was for nonce N

The EIP-2612 says permit() could be frontrun .

In the security section of the EIP-2612 they say:

Though the signer of a Permit may have a certain party in mind to submit their transaction, another party can always front run this transaction and call permit before the intended party. The end result is the same for the Permit signer, however.

Read about the Internal Working Of EIP-2612 Here

Code Reference

Impact

  • Users' staking transactions can be forced to fail
  • Gas costs are wasted
  • Denial of service through signature invalidation
  • No direct loss of funds but requires users to generate new signatures and retry transactions
  • User frustration

Code Snippet

function permitAndStake(
    uint256 _amount,
    address _delegatee,
    address _claimer,
    uint256 _deadline,
    uint8 _v,
    bytes32 _r,
    bytes32 _s
) external virtual returns (DepositIdentifier _depositId) {
    try IERC20Permit(address(STAKE_TOKEN)).permit(
        msg.sender, address(this), _amount, _deadline, _v, _r, _s
    ) {} catch {}
    _depositId = _stake(msg.sender, _amount, _delegatee, _claimer);
}

Tool used

  • Manual Review
  • Foundry

Recommendation

Several approaches could mitigate this vulnerability:

  1. Commit-Reveal Pattern
struct Commitment {
    bytes32 commitmentHash;
    uint256 timestamp;
}
mapping(address => Commitment) public commitments;

function commitStake(bytes32 commitmentHash) external {
    commitments[msg.sender] = Commitment(commitmentHash, block.timestamp);
}

function permitAndStake(
    uint256 amount,
    address delegatee,
    address claimer,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s,
    bytes32 salt
) external {
    require(block.timestamp >= commitments[msg.sender].timestamp + MIN_COMMITMENT_DELAY);
    require(keccak256(abi.encodePacked(salt, amount, delegatee, claimer)) == 
            commitments[msg.sender].commitmentHash);
    // Rest of permitAndStake logic
}
  1. Custom Replay Protection: Add a custom nonce system specific to the staking operation.

  2. Short Deadlines

function permitAndStake(...) external {
    require(deadline <= block.timestamp + MAX_PERMIT_DELAY);
    // Rest of function
}