Skip to main content

Write to Arbitrary Storage Location

Why is this important?

A smart contract's data (e.g., storing the owner of the contract) is persistently stored at some storage location (i.e., a key or address) on the EVM level. The contract is responsible for ensuring that only authorized user or contract accounts may write to sensitive storage locations. If an attacker is able to write to arbitrary storage locations of a contract, the authorization checks may easily be circumvented. This can allow an attacker to corrupt the storage; for instance, by overwriting a field that stores the address of the contract owner.

Avoid Writing to Arbitrary Storage Location

Option A: Prevent overwrite of data structures

  1. Go through the issues that GuardRails identified in the PR

  2. Identify the code that looks like this:

    pragma solidity ^0.4.25;

    contract Wallet {
    uint[] private bonusCodes;
    address private owner;

    constructor() public {
    bonusCodes = new uint[](0);
    owner = msg.sender;
    }

    function () public payable {
    }

    function PushBonusCode(uint c) public {
    bonusCodes.push(c);
    }

    function PopBonusCode() public {
    require(0 <= bonusCodes.length);
    bonusCodes.length--;
    }

    function UpdateBonusCodeAt(uint idx, uint c) public {
    require(idx < bonusCodes.length);
    // This is the vulnerable line of code
    bonusCodes[idx] = c;
    }

    function Destroy() public {
    require(msg.sender == owner);
    selfdestruct(msg.sender);
    }
    }
  3. As a general advice, given that all data structures share the same storage (address) space, one should make sure that writes to one data structure cannot inadvertently overwrite entries of another data structure

    pragma solidity ^0.4.25;

    contract Wallet {
    uint[] private bonusCodes;
    address private owner;

    constructor() public {
    bonusCodes = new uint[](0);
    owner = msg.sender;
    }

    function () public payable {
    }

    function PushBonusCode(uint c) public {
    bonusCodes.push(c);
    }

    function PopBonusCode() public {
    require(0 < bonusCodes.length);
    bonusCodes.length--;
    }

    function UpdateBonusCodeAt(uint idx, uint c) public {
    require(idx < bonusCodes.length); //Since you now have to push every code this is no longer an arbitrary write.
    bonusCodes[idx] = c;
    }

    function Destroy() public {
    require(msg.sender == owner);
    selfdestruct(msg.sender);
    }
    }
  4. Test it

  5. Ship it 🚢 and relax 🌴

More information