Skip to main content
Sigvex

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

  1. SSTORE operand tracing: For each SSTORE, traces the slot operand backward through the data-flow graph.
  2. User input detection: Flags slots traceable to CALLDATALOAD without intervening hash operations (which would indicate a mapping key, not a raw slot index).
  3. Bounds check detection: Looks for comparison operations that restrict the slot value before the SSTORE.
  4. Mapping hash exclusion: SSTORE operations where the slot is computed via KECCAK256 (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.

References