A complete breakdown of Uninitialized Storage Parameters

by ImmuneBytes
Uninitialized Storage Parameters

We are once again here with the next part of our series All About Smart Contract Bugs & Security – A cakewalk series, talking about uninitialized storage parameters in a smart contract code that can cause severe damage to its longevity. 

Do check out the other parts in the series wherein we talk about Race conditions, Lost Ether in a Transfer, Stack Size Limit, and many other bugs that have proven to weaken smart contracts.

The EVM stores data either as storage or as memory, depending on the type of variable. Understanding how this is done and the default types for local variables of functions is highly recommended when developing contracts because there remains a possibility to produce vulnerable contracts by inappropriately initializing variables. 

If these local storage variables are uninitialized, they can unexpectedly point to other local storage variables in the contract.

Technical Breakdown

A smart contract with uninitialized storage parameters may lead to intentional (i.e. the developer intentionally puts them there to attack later) or unintentional vulnerabilities. These can also be used deliberately to exploit users by making the contract function differently than what was intended. 

Let’s take a look at a smart contract code fragment containing such vulnerability.

//A locked Name Registrar

Contract NameRegistrar {

Bool public unlocked = false; //registrar locked, no name updates

 

Struct NameRecord { //map hashes to addresses

Bytes32 name;

Address mappedAddress;

}

 

mapping(address => NameRecord) public registeredNameRecord; //records who registered names

 

mapping(bytes32 => address) public resolve; //resolves hashes to addresses

 

function register(bytes32 _name, address _mappedAddress) public {

//set up the new NameRecord

NameRecord new Record;

newRecord.name = _name;

newRecord.mappedAddress = _mappedAddress;

resolve[_name] = _mappedAddress;

registeredNameRecord[msg.sender] = newRecord;

require(unlocked); //only allow registrations if contract is unlocked

}

 

}

 

In the above-given code, there is a vulnerability that effectively unlocks the initially locked contract. The unlocked variable is indirectly affected and can be altered since newRecord is not initialized. 

Solidity stores state variables sequentially, consequently this unlocked will be stored in slot 0. Since Solidity defaults complex data types, such as structs to storage when declaring them as local variables, it becomes a pointer to storage. Because newRecord is uninitialized, it is pointing to slot 0, where unlocked is stored. When setting newRecord.name to _name we are effectively changing the storage slot 0 where the variable unlocked is stored. If _name has its last byte be non-zero, then unlock is true and the contract is then unlocked.

Real-life examples: OpenAddressLottery and CryptoRoulette

OpenAddressLottery, a honey pot, was deployed that made use of this uninitialized storage variable quirks to collect ether from some would-be hackers. Check out this Reddit thread for an in-depth explanation of the incident.

Another honey pot, CryptoRoulette also utilised this trick to try and collect some ether. A detailed description of how this works can be found here.

This was a detailed breakdown of how uninitialized storage parameters can affect your smart contract’s performance along with a real-life example to show you the extent of the damage. We hope this helps you in avoiding such mistakes while developing your smart contract. 

Such mistakes are given critical attention in the audits done at ImmuneBytes. Connect with our team to get your smart contract free of any such vulnerabilities and loopholes that can invite hackers to your doorstep.

Tune in next Thursday for Part-XI of this series wherein we discuss Tx.origin.

Spread the love

You may also like