Install in Claude Code
Copygit clone --depth 1 https://github.com/PurpleAILAB/Vigilo /tmp/dos-attack && cp -r /tmp/dos-attack/packages/opencode/skills/dos-attack ~/.claude/skills/dos-attackThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# 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.
```solidity
// 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.
```solidity
// 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.
```solidity
// 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.
```solidity
// 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
```solidity
// VULNERABLE: Loop over dynamic array
function processAll() external {
for (uint256 i = 0; i < items.length; i++) {
_process(items[i]);
}
}
```
**Attack Flow**:
1. Attacker adds many small items to array
2. Array grows to thousands of entries
3. Gas cost exceeds block limit
4. Function becomes uncallable
5. Funds/operations permanently stuck
**Search Queries**:
```
Grep("for.*\\.length|while.*\\.length", glob="**/*.sol")
Grep("for.*i\\+\\+|for.*i < ", glob="**/*.sol")
```
**Mitigation**:
```solidity
// 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
```solidity
// 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**:
1. Attacker enters system with contract that reverts on receive
2. Refund function iterates to attacker's address
3. Attacker's receive() reverts
4. Entire function reverts
5. All refunds blocked
**Search Queries**:
```
Grep("\\.call\\{value.*require\\(success", glob="**/*.sol")
Grep("\\.transfer\\(|send\\(", glob="**/*.sol")
```
**Mitigation**:
```solidity
// 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
```solidity
// 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**:
1. Attacker creates contract with expensive fallback
2. Callback executes, consuming all forwarded gas
3. Parent transaction fails or behaves unexpectedly
4. Griefing attack succeeds
**Search Queries**:
```
Grep("\\.call\\(|\\.delegatecall\\(", glob="**/*.sol")
Grep("gasleft\\(\\)", glob="**/*.sol")
```
**Mitigation**:
```solidity
// 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
```solidity
// VULNERABLE: Time-sensitive operation
function claimAuction() external {
require(block.timestamp >= auctionEnd, "Auction ongoing");
require(!claimed, "Already claimed");
claimed = true;
// Transfer winning bid