Arbitrary Storage Write
Detects functions that write to storage slots determined by user-controlled input, enabling attackers to overwrite any state variable.
Arbitrary Storage Write
Overview
Remediation Guide: How to Fix Arbitrary Storage Write
The arbitrary storage write detector identifies SSTORE operations where the storage slot is derived from user-controlled input without sufficient bounds checking. In the EVM, all contract state lives in a flat 2^256-slot storage space. If an attacker can control the slot index, they can overwrite the owner variable, proxy implementation address, balance mappings, or any other state.
Sigvex traces the first operand of each SSTORE instruction (the slot) back through the data-flow graph. If the slot value can be traced to CALLDATALOAD without an intervening bounds check or mapping hash, the detector flags it.
Why This Is an Issue
Arbitrary storage writes give an attacker the same capabilities as a contract owner. By computing the correct storage slot for the owner variable (typically slot 0) and calling the vulnerable function with that slot index, the attacker replaces the owner address with their own. From there, they can drain all funds, upgrade the proxy, or self-destruct the contract.
This vulnerability class includes array length manipulation (writing to array.length to expand the array and gain access to adjacent storage) and uninitialized storage pointer attacks (Solidity < 0.5.0).
How to Resolve
// Before: Vulnerable — user controls storage slot
function writeSlot(uint256 slot, uint256 value) external {
assembly {
sstore(slot, value) // Attacker can overwrite any slot
}
}
// After: Fixed — restrict to known slots with access control
mapping(uint256 => uint256) public data;
function writeData(uint256 key, uint256 value) external onlyOwner {
data[key] = value; // Mapping hash prevents arbitrary slot access
}
Examples
Vulnerable Code
contract VulnerableStorage {
address public owner;
uint256[] public values;
// CRITICAL: user controls the storage slot directly
function setStorage(uint256 slot, uint256 value) external {
assembly {
sstore(slot, value)
}
}
}
Fixed Code
contract SafeStorage {
address public owner;
mapping(bytes32 => uint256) private _data;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function setData(bytes32 key, uint256 value) external onlyOwner {
_data[key] = value;
}
}
Sample Sigvex Output
{
"detector_id": "arbitrary-storage",
"severity": "critical",
"confidence": 0.90,
"description": "SSTORE at offset 0x5a writes to a slot derived from calldata without bounds validation. An attacker can overwrite any storage variable including the owner.",
"location": { "function": "setStorage(uint256,uint256)", "offset": 90 }
}
Detection Methodology
- SSTORE operand tracing: For each
SSTORE, traces the slot operand backward through the data-flow graph. - User input detection: Flags slots traceable to
CALLDATALOADwithout intervening hash operations (which would indicate a mapping key, not a raw slot index). - Bounds check detection: Looks for comparison operations that restrict the slot value before the
SSTORE. - Mapping hash exclusion:
SSTOREoperations where the slot is computed viaKECCAK256(standard Solidity mapping/array pattern) are excluded, as the hash prevents arbitrary slot targeting.
Limitations
False positives:
- Admin-only functions that allow storage manipulation for migration purposes may be flagged.
- Diamond proxy (EIP-2535) storage management functions may trigger findings.
False negatives:
- Slot values computed through multiple arithmetic operations from user input may not be fully traced.
- Assembly blocks with complex stack manipulation may obscure the slot origin.
Related Detectors
- Access Control — detects missing authorization on state-changing functions
- Storage Collision — detects proxy storage slot conflicts
- Unsafe Delegatecall — detects delegatecall enabling arbitrary storage access