Denial of Service
id: LS01H
title: Denial of Service
baseSeverity: H
category: denial-of-service
language: solidity
blockchain: [ethereum]
impact: Contract becomes unusable or stuck
status: draft
complexity: low
attack_vector: external
mitigation_difficulty: medium
versions: [">=0.4.0", "<0.8.21"]
cwe: CWE-400
swc: SWC-113
๐ Description
- Denial of Service (DoS) in smart contracts occurs when an attacker can prevent legitimate users from interacting with a contract, either temporarily or permanently.
- This can be achieved by exploiting resource-intensive operations (e.g., unbounded loops), failing external calls, unexpected reverts, or storage manipulation that causes subsequent operations to fail.
- In Ethereum, since each transaction has a gas limit, a common DoS vector is introducing logic that exceeds the block gas limit or causes reversion due to invalid assumptions (like looping through a growing array).
๐จ Vulnerable Code
mapping(address => uint[]) public userDeposits;
function withdrawAll() external {
uint[] storage deposits = userDeposits[msg.sender];
for (uint i = 0; i < deposits.length; i++) {
_processWithdrawal(deposits[i]); // Assume this costs significant gas
}
delete userDeposits[msg.sender];
}
๐งช Exploit Scenario
Step-by-step exploit process:
- Attacker makes hundreds or thousands ...[msg.sender].
- Calls withdrawAll() which loops over all entries.
- The loop execution exceeds the block gas limit โ reverts.
- The attacker is now permanently locked out.
Assumptions:
- The contract allows unlimited or unbounded entries into arrays or mappings.
- There is no gas-bound safety mechanism in looping constructs.
โ Fixed Code
function withdrawBatch(uint start, uint end) external {
uint[] storage deposits = userDeposits[msg.sender];
require(end <= deposits.length, "Invalid end index");
for (uint i = start; i < end; i++) {
_processWithdrawal(deposits[i]);
}
if (end == deposits.length) {
delete userDeposits[msg.sender];
}
}
๐งญ Contextual Severity
- context: "Reward, withdrawal, or governance functions blocked by one user"
severity: H
reasoning: "Entire system availability is compromised"
- context: "Batch loops in admin-only or optional functions"
severity: M
reasoning: "Can be circumvented but affects UX"
- context: "Non-critical DoS affecting display or logging"
severity: L
reasoning: "No impact on funds or control flow"
๐ก๏ธ Prevention
Primary Defenses
- Avoid unbounded loops in external/public functions.
- Introduce batching mechanisms (e.g., start and end indices).
- Set gas guards and enforce maximum data sizes where feasible.
Additional Safeguards
- Implement rate limiting or per-call caps.
- Use gas estimation during testing to simulate worst-case scenarios.
Detection Methods
- Detect via static analysis tools (e.g., Slither: unbounded-loop, dos detectors).
- Fuzzing and gas profiling during QA.
- Formal verification of loop safety and invariants.
๐ฐ๏ธ Historical Exploits
- Name: GovernMental DoS Vulnerability
- Date: 2016-06-11
- Loss: N/A
- Post-mortem: Link to post-mortem
๐ Further Reading
- SWC-113: DoS with Block Gas Limit
- Preventing Denial of Service Attacks in Solidity โ Infuy
- Slither Detector Documentation โ crytic/slither Wiki
โ Vulnerability Report
id: LS01H
title: Denial of Service
severity: H
score:
impact: 5
exploitability: 4
reachability: 5
complexity: 1
detectability: 3
finalScore: 4.3
๐ Justifications & Analysis
- Impact: A well-crafted DoS can freeze user funds or key contract functionality, leading to major trust and financial loss.
- Exploitability: The attack can be triggered externally with minimal effort (e.g., looping, reverting).
- Reachability: Public/external functions without restrictions can be exploited at will.
- Complexity: Attack only requires interacting with the contract normally (e.g., through deposits or submissions).
- Detectability: While many tools detect unbounded loops, nuanced cases (e.g., user-sourced data) may be overlooked without manual review.