Skip to content

Latest commit

 

History

History
103 lines (86 loc) · 6.13 KB

ContractStorage.md

File metadata and controls

103 lines (86 loc) · 6.13 KB

Solidity Storage

State variables are variables whose values are permanently stored in the contract storage in a compact way that can result in values sharing the same storage slot. Slots in storage are stored as key-value pairs with a length of 256 bits (32 bytes) with a default value of 0. Slots are allocated in order of the variable declaration, starting at address 0. Storage only stores variables and not constant. The first item in a storage slot is stored lower-order aligned, slots follow a right to left order. In case a variable exceeds the remaining size of the slot, the variable will be passed to the left slot. Structs and array create a new slot (and also the items following them).

Fixed size variable

They are basic data types such as uint8, uint16, uint24, uint32, uint64, uint128, uint256, bool, address their arrangement in slots can be seen by executing this simple contract that only stores data:

pragma solidity ^0.4.22;

contract StorageTest {
    uint8 public a;
    address public d;
    bool public e;

    uint16[3] public b;
    struct Values{
        uint32 n1;
        uint32 n2;
    }

    Values public c;

    constructor(){
        a = 1;
        b[0] = 3;
        b[1] = 5;
        b[2] = 7;
        e = true;
        d = 0x29D7d1dd5B6f9C864d9db560D72a247c178aE86B;
        c.n1 = 22;
        c.n2 = 23;
    }
}

By running this Web3.py script we can see how the variables are arranged in the contract storage:

from web3 import Web3
provider = Web3(Web3.HTTPProvider('HTTP://127.0.0.1:7545'))
for i in range(4):
    value = provider.eth.get_storage_at('0x97FF0359Ad8079Dd1ebB7a526dfE86f8fdbe2fd6', i)
    print(value.hex(), i)

The given output is:

0x0129d7d1dd5b6f9c864d9db560d72a247c178ae86b01 0
0x0700050003 1
0x1700000016 2
0x00 3

From this it's possible to understand that variables a, d and e, because their size is less than 256 bit ((8+40+1) < 256) are all contained in a single slot: 0x0129d7d1dd5b6f9c864d9db560d72a247c178ae86b01 that is the union of 01, the hexadecimal representation of True, 29d7d1dd5b6f9c864d9db560d72a247c178ae86b, the address contained in the d variable, and 01, the value of the a variable. Similarly, the values of the array b are contained in the second slot and the values of the struct c in the third.

Dynamic Size variables

Due to their unpredictable size, mappings and dynamically-sized array types cannot be stored “in between” the state variables preceding and following them. Dynamical arrays always occupy a new slot (32 bytes) that will store the length of the array. The elements they contain are stored in different slots determined using a Keccak-256 hash. Array data is located starting at keccak256(p), where p is the storage location of the mapping/array, and it's arranged in the same way as statically-sized array data. Dynamic arrays of dynamic arrays store data following this arrangement recursively. The value of a mapping key k is located at keccak256(k . p) where . and means concatenation and p is the storage location of the mapping (where its length is stored). k value in case there are strings or byte array should be hashed via keccak256; in other cases it should be padded to 32 bytes.

Example: Mapping

This smart contract comes from a Capture The Ether challenge, whose goal is to set the isComplete bool value to True.

pragma solidity ^0.4.21;

contract MappingChallenge {
    bool public isComplete;
    uint256[] map;

    function set(uint256 key, uint256 value) public {
        // Expand dynamic array as needed
        if (map.length <= key) {
            map.length = key + 1;
        }

        map[key] = value;
    }

    function get(uint256 key) public view returns (uint256) {
        return map[key];
    }
}

As you can see, there is no function that directly affects the value of the isComplete variable, the only function that modifies the values of the contract is set(), that modifies a uint256 array by permitting to write input data at a specific array position (given as input too). A resolution can be found remembering that EVM deals with contract storage as a 256 bit pointer by a 32 byte value slot and that the dynamic array will occupy one slot for its length and other slots for its elements, those slots start at an address given by the keccak256 hash of slot where the dynamic array length is stored. The solution tactic to set the smart contract isComplete bool variable to True is to exploit the storage limitation of the smart contract and the dynamic size of the array to overwrite the memory slot containing the value of isComplete. To occupy all smart contract available storage memory we can call the function set() and pass as the key parameter the value 2**256 - 2, the maximum value that will push the array to exceed contract memory limits. To set isComplete variable to true through the map dynamic array it's needed first to calculate the slots where its elements are saved. Elements are saved starting at the keccak256 hash of the slot where dynamic array length is saved (the slot number 1): keccak-256(0x0000000000000000000000000000000000000000000000000000000000000001) = 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 Then to recover the map key where the data of isComplete is saved we can just:

print('0x{0:0x}'.format(2**256 - 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6))

This will return the address: 0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a Finally just by calling the function set and with the above address as the key (and a value different from zero) we can change bool variable isComplete data.

Mitigation

This smart contract vulnerability isn't a bug, it's just a limitation of the EVM execution layer that limits smart contract usable storage data. To avoid this (ideal) situation just don't give the user that much power to modify storage or use other smart contract data structures, like a mapping, where no memory is allocated to unused elements (unlike what happened with array elements prior to 2 ** 256-2).