Unbounded Proposal Gas
id: LS37H
title: Unbounded Proposal Gas
baseSeverity: H
category: governance
language: solidity
blockchain: [ethereum, optimism, arbitrum, polygon, avalanche]
impact: Governance proposals cannot execute, halting upgrades or DAO actions
status: draft
complexity: high
attack_vector: internal
mitigation_difficulty: medium
versions: [">=0.6.0", "<=0.8.25"]
cwe: CWE-400
swc: SWC-128
๐ Description
- Many DAO and protocol governance systems (e.g., Compound-style, OpenZeppelin Governor, custom DAOs) include proposals that can queue and execute multiple actions in one transaction.
- If these actions are executed in a single unbounded execute() call, the system is vulnerable to gas griefing or denial-of-service (DoS) due to:
- Proposals consuming more gas than the block gas limit
- Inclusion of external or loop-heavy operations
- Attacker-crafted proposals that deliberately exhaust gas before completing
- If even one proposal becomes unexecutable, it can block the queue, especially in FIFO-style timelocks, causing governance paralysis.
๐จ Vulnerable Code
pragma solidity ^0.8.0;
contract SimpleGovernor {
struct Proposal {
address[] targets;
bytes[] calldatas;
bool executed;
}
mapping(uint256 => Proposal) public proposals;
function execute(uint256 proposalId) external {
Proposal storage p = proposals[proposalId];
require(!p.executed, "Already executed");
for (uint256 i = 0; i < p.targets.length; ++i) {
(bool success, ) = p.targets[i].call(p.calldatas[i]); // โ unbounded call
require(success, "Call failed"); // will revert entire proposal
}
p.executed = true;
}
}
๐งช Exploit Scenario
- A malicious user submits a proposal with dozens of batched call() actions.
- The gas cost of executing all actions exceeds the block gas limit.
- When anyone attempts to execute(), the transaction fails.
- Since the proposal cannot be removed, no new proposals can pass through the queue, resulting in governance lockup.
Assumptions:
- Proposal batching is allowed
- Execution is all-or-nothing with no fallback path
โ Fixed Code
pragma solidity ^0.8.0;
contract ResilientGovernor {
struct Proposal {
address[] targets;
bytes[] calldatas;
uint256 executedIndex;
}
mapping(uint256 => Proposal) public proposals;
function execute(uint256 proposalId, uint256 limit) external {
Proposal storage p = proposals[proposalId];
require(p.executedIndex < p.targets.length, "Already executed");
uint256 i = p.executedIndex;
for (; i < p.targets.length && limit > 0; ++i, --limit) {
(bool success, ) = p.targets[i].call(p.calldatas[i]); // โ ๏ธ each call runs individually
require(success, "Execution failed");
}
p.executedIndex = i;
}
}
๐งญ Contextual Severity
- context: "Default"
severity: H
reasoning: "Proposal execution failure causes disruption to governance."
- context: "DeFi protocol relying on proposals for fund streaming"
severity: C
reasoning: "Critical treasury operations can become permanently stuck."
- context: "Permissioned governance or execution with admin fallback"
severity: L
reasoning: "Admins or multisig can recover from failed proposals."
๐ก๏ธ Prevention
Primary Defenses
- Break large proposals into chunks with per-call limits
- Use multi-step execution patterns with checkpointing
Additional Safeguards
- Add a maxActionsPerProposal limit during proposal creation
- Simulate gas usage of each proposal before queueing
- Reject proposals that exceed a safe gas limit threshold
Detection Methods
- Detect unbounded for loops over proposal actions inside execute()
- Look for governance systems with no gas constraints
- Tools: Slither (unbounded-loop, gas-dos), Foundry fuzzing, manual review
๐ฐ๏ธ Historical Exploits
- Name: Aragon Multicall Overflow Bug
- Date: 2023-03
- Loss: N/A
- Post-mortem: Link to post-mortem
๐ Further Reading
โ Vulnerability Report
id: LS37H
title: Unbounded Proposal Gas
severity: H
score:
impact: 4
exploitability: 4
reachability: 4
complexity: 3
detectability: 4
finalScore: 4.0
๐ Justifications & Analysis
- Impact: Proposals may fail permanently, blocking protocol progression
- Exploitability: Anyone with proposal rights can craft a denial proposal
- Reachability: Most governance frameworks allow batched execution
- Complexity: Requires understanding gas limits and proposal crafting
- Detectability: Readily detected by audit or simulation tooling