| name | dos-attack |
| description | Auto-loaded by logic-auditor agent during Phase 2. Provides detection patterns for: gas griefing, unbounded loops, external call failures, block gas limit exploitation, storage DoS, and liveness failures. Core artifact: Liveness Analysis Map.
|
| user-invocable | false |
Denial of Service (DoS) Attack Patterns
OWASP SC10:2025 - DoS attacks prevent legitimate users from accessing protocol functionality, potentially locking funds permanently.
2025-2026 Statistics: DoS vulnerabilities caused protocol freezes affecting $180M+ in locked funds, with gas griefing attacks increasing 45% YoY.
Why DoS Happens (Root Causes)
Root Cause 1: Unbounded Iteration
Loops without gas limits become unusable as data grows.
// VULNERABLE: Unbounded loop
function distributeRewards() external {
for (uint256 i = 0; i < holders.length; i++) { // @audit holders can grow unbounded
token.transfer(holders[i], rewards[holders[i]]);
}
}
Attacker's view: "I'll create thousands of tiny positions. Eventually, no one can call this function."
Root Cause 2: External Call Dependency
Function success depends on external call that attacker can make fail.
// VULNERABLE: Relies on external transfer success
function withdrawAll() external {
for (uint256 i = 0; i < users.length; i++) {
payable(users[i]).transfer(balances[users[i]]); // @audit One revert blocks all
}
}
Attacker's view: "If I'm in the array and my receive() reverts, nobody gets paid."
Root Cause 3: Storage Slot Exhaustion
Unlimited storage growth makes operations cost-prohibitive.
// VULNERABLE: Unlimited storage growth
mapping(address => uint256[]) public userDeposits;
function deposit() external payable {
userDeposits[msg.sender].push(msg.value); // @audit Array grows forever
}
function getTotalDeposits(address user) external view returns (uint256) {
uint256 total;
for (uint256 i = 0; i < userDeposits[user].length; i++) { // @audit View can run out of gas
total += userDeposits[user][i];
}
return total;
}
Root Cause 4: Block Gas Limit Exploitation
Transaction exceeds block gas limit, making it impossible to execute.
// VULNERABLE: Can exceed block gas limit
function processAllPending() external {
while (pendingQueue.length > 0) {
_processSingle(pendingQueue[0]);
pendingQueue.pop();
}
}
The Liveness Analysis Map (Core Artifact)
For each critical function, document:
Function: withdrawAll()
├── External Calls: N calls to user addresses
├── Loop Bound: users.length (unbounded)
├── Gas Estimate: O(n) where n = users count
├── Failure Mode: Single revert blocks all withdrawals
├── Recovery: None - funds permanently locked
└── Risk: CRITICAL
| Function | Dependency | Bound | Failure Impact | Recovery |
|---|
| distributeRewards | N transfers | Unbounded | Protocol freeze | None |
| processQueue | Queue size | Bounded (100) | Temporary delay | Retry |
| batchLiquidate | M liquidations | User-controlled | Partial failure | Continue |
Detection Patterns
Pattern 1: Unbounded Loop DoS
Root Cause: Unbounded Iteration
// VULNERABLE: Loop over dynamic array
function processAll() external {
for (uint256 i = 0; i < items.length; i++) {
_process(items[i]);
}
}
Attack Flow:
- Attacker adds many small items to array
- Array grows to thousands of entries
- Gas cost exceeds block limit
- Function becomes uncallable
- Funds/operations permanently stuck
Search Queries:
Grep("for.*\\.length|while.*\\.length", glob="**/*.sol")
Grep("for.*i\\+\\+|for.*i < ", glob="**/*.sol")
Mitigation:
// SECURE: Pagination pattern
function processRange(uint256 start, uint256 end) external {
require(end <= items.length && end - start <= MAX_BATCH);
for (uint256 i = start; i < end; i++) {
_process(items[i]);
}
}
Pattern 2: External Call Failure DoS
Root Cause: External Call Dependency
// VULNERABLE: One failure blocks all
function refundAll() external {
for (uint256 i = 0; i < refundees.length; i++) {
(bool success,) = refundees[i].call{value: amounts[i]}("");
require(success, "Refund failed"); // @audit Blocks on any failure
}
}
Attack Flow:
- Attacker enters system with contract that reverts on receive
- Refund function iterates to attacker's address
- Attacker's receive() reverts
- Entire function reverts
- All refunds blocked
Search Queries:
Grep("\\.call\\{value.*require\\(success", glob="**/*.sol")
Grep("\\.transfer\\(|send\\(", glob="**/*.sol")
Mitigation:
// SECURE: Pull pattern
mapping(address => uint256) public pendingRefunds;
function withdraw() external {
uint256 amount = pendingRefunds[msg.sender];
pendingRefunds[msg.sender] = 0;
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}
Pattern 3: Gas Griefing
Root Cause: Unchecked Gas Forwarding
// VULNERABLE: Forwards all gas to untrusted call
function executeCallback(address target, bytes calldata data) external {
(bool success,) = target.call(data); // @audit Attacker can consume all gas
require(success);
}
Attack Flow:
- Attacker creates contract with expensive fallback
- Callback executes, consuming all forwarded gas
- Parent transaction fails or behaves unexpectedly
- Griefing attack succeeds
Search Queries:
Grep("\\.call\\(|\\.delegatecall\\(", glob="**/*.sol")
Grep("gasleft\\(\\)", glob="**/*.sol")
Mitigation:
// SECURE: Limit gas forwarded
(bool success,) = target.call{gas: 50000}(data);
// OR use try/catch
try ICallback(target).callback{gas: 50000}(data) {} catch {}
Pattern 4: Block Stuffing
Risk: Attacker fills blocks to prevent time-sensitive operations
// VULNERABLE: Time-sensitive operation
function claimAuction() external {
require(block.timestamp >= auctionEnd, "Auction ongoing");
require(!claimed, "Already claimed");
claimed = true;
// Transfer winning bid...
}
Attack Flow:
- Attacker sees they're losing auction
- Before auction ends, attacker submits many high-gas transactions
- Blocks become full, legitimate claimAuction() can't execute
- Attacker extends effective auction time
- Eventually claims at manipulated state
Mitigation:
- Add grace periods for time-sensitive operations
- Use commit-reveal for auctions
- Allow partial execution
Pattern 5: Storage Collision DoS
Root Cause: Unlimited Mapping/Array Growth
// VULNERABLE: Unlimited storage per user
function addOrder(uint256 amount) external {
userOrders[msg.sender].push(Order(amount, block.timestamp));
}
function cancelAllOrders() external {
delete userOrders[msg.sender]; // @audit Gas increases with array size
}
Attack Flow:
- Attacker creates millions of tiny orders
- Tries to cancel all (or system tries to process)
- Gas exceeds limits
- Operations blocked
Search Queries:
Grep("push\\(|delete.*\\[", glob="**/*.sol")
Grep("mapping.*\\[\\]|address.*=>.*\\[\\]", glob="**/*.sol")
Pattern 6: Return Bomb Attack
Root Cause: Unbounded Return Data
// VULNERABLE: Copies all return data
function executeCall(address target, bytes calldata data) external returns (bytes memory) {
(bool success, bytes memory result) = target.call(data); // @audit result can be huge
require(success);
return result;
}
Attack Flow:
- Attacker creates contract returning massive data (e.g., 1MB)
- Memory expansion costs explode
- Transaction runs out of gas
- Call fails unexpectedly
Mitigation:
// SECURE: Limit return data or use assembly
assembly {
let success := call(gas(), target, 0, add(data, 32), mload(data), 0, 0)
// Only copy limited return data if needed
}
DoS Prevention Checklist
Loop Safety
External Call Safety
Storage Safety
Time Safety
Search Query Reference
# Find unbounded loops
Grep("for.*\\.length|while.*length", glob="**/*.sol")
Grep("for.*i\\+\\+.*\\{", glob="**/*.sol")
# Find external calls
Grep("\\.call\\{|\\.transfer\\(|\\.send\\(", glob="**/*.sol")
Grep("require\\(success", glob="**/*.sol")
# Find storage patterns
Grep("push\\(|pop\\(|delete", glob="**/*.sol")
Grep("mapping.*\\[\\]", glob="**/*.sol")
# Find time dependencies
Grep("block\\.timestamp|block\\.number", glob="**/*.sol")
Severity Classification
Critical
- Funds permanently locked
- Core protocol functions uncallable
- No recovery mechanism
High
- Temporary protocol freeze possible
- Significant gas griefing impact
- User funds at risk
Medium
- View functions can fail
- Minor operations blockable
- Recovery exists but costly
Rationalization Table (Reject These Excuses)
| Excuse | Reality |
|---|
| "Array won't grow that large" | Attackers WILL grow it. Assume worst case. |
| "Users won't create malicious contracts" | Attackers absolutely will. Every address is suspect. |
| "Gas is cheap" | Block gas limit is fixed. 30M gas max per block. |
| "We can upgrade if needed" | Funds may be locked BEFORE you can upgrade. |
| "This is theoretical" | Akropolis, SpankChain, and others lost millions to DoS. |
| "View functions don't matter" | External protocols depend on your views. DoS spreads. |