3.34 Compiler Bugs

Let's now discuss a set of security risks that manifested themselves because of compiler bugs: these were bugs in older versions of the Solidity compiler that have been since fixed.

They are very specific to certain complex data structures or very specific conditions that one may not often encounter in typical smart contracts. Because of that, we will not be able to get into the details of these compiler bugs and their security risks.

Nevertheless, taking a look at the higher level aspects of these compiler bugs would hopefully let us appreciate the complexity of some of them and the security risks that they may pose.

Storage Array with int under ABIEncoderV2

This specific compiler bug was related to storage arrays and signed integers, and their usage was enabled by the ABIEncoderV2, which was a pragma directive, that needed to be explicitly specified until the latest versions (as it is now used by default).

This specific bug arose when assigning an array of signed integers to a storage array of a different type Type[] = int[]. Under such assignments, it led to data corruption in that array.

This bug was present in Solidity versions 0.4.7 until 0.5.10 (which are much older versions than the latest one that we often encounter), so it's very unlikely that we'll look at smart contracts using these much older versions, but it is something to be kept in mind.

Dynamic Constructor Arguments Clipped under ABIEncoderV2

A contract's constructor that takes structs or arrays that contain dynamically sized arrays (made possible because of ABIEncoderV2) reverted or decoded to invalid data.

This compiler bug was present in Solidity versions 0.4.16 to 0.5.9.

Storage Array with Multi-slot Element under ABIEncoderV2

There was a compiler bug related to storage arrays in Solidity, specifically those with multi-slot elements, again made possible because of ABIEncoderV2.

Such storage arrays containing structs or other statically sized arrays were not read properly when they were directly encoded in external function calls or using the abi.encode() primitive.

This bug was present in Solidity versions 0.4.16 to 0.5.10.

Calldata Structs with Statically Sized and Dynamically Encoded Members under ABIEncoderV2

Another compiler bug was related to the struct type (specifically calldata structs) which consisted in reading from calldata structs that contained dynamically encoded, but statically sized members, could result in incorrect values being read.

This again was limited to the Solidity compiler versions 0.5.6 to 0.5.11.

Packed Storage under ABIEncoderV2

There was a compiler bug related to packed storage. Storage structs and arrays with types smaller than 32 bytes when encoded directly from storage using ABIEncoderV2 could cause data corruption.

This occurred with Solidity compiler versions 0.5.0 to 0.5.7.

Incorrect Loads with Yul Optimizer and ABIEncoderV2

This is another compiler bug specifically coming from the Yul optimizer, part of it resulted in incorrect loads being done.

When the experimental Yul optimizer was activated manually in addition to ABIEncoderV2, it resulted in memory loads and storage loads via MLOAD and SLOAD instructions to be replaced by values that were already written.

So effectively, the Yul optimizer replaced the MLOAD and SLOAD calls with stale values which is a serious bug. This occurred with Solidity compiler versions 0.5.14 to 0.5.15.

Array Slice Dynamically Encoded Base Type under ABIEncoderV2

There was a compiler bug specifically related to array slices, which there are views of the arrays that lets us access specific ranges of those arrays in a very efficient manner.

Accessing such array slices for arrays that had dynamically encoded base types resulted in invalid data being read for the Solidity compiler versions 0.6.0 to 0.6.8.

Missing Escaping in Formatting under ABIEncoderV2

This compiler bug was related to missed escaping. Escaping is relevant to string literals where certain characters can be escaped using the double backslash.

String literals that contained double backslash characters for escaping, that were passed directly to external, or encoding function calls, could result in a different string being used when ABIEncoderV2 was enabled.

Notice that this compiler bug was present across many Solidity compiler versions all the way from 0.5.14 to 0.6.8.

Double Shift Size Overflow

If multiple conditions were true, then the shifting operations resulted in overflows resulting in unexpected values being output.

Some of those conditions were that the optimizer needed to be enabled. These had to be double bitwise shifts where large constants were being used whose sum overflowed 256 bits.

Under such conditions the shifting operations overflowed for the Solidity compiler versions 0.5.5 to 0.5.6.

Incorrect Byte Instruction Optimization

This was a compiler bug originating from incorrect optimization of byte instructions.

The optimizer, when dealing with byte codes whose second argument was 31 or a constant expression that evaluated to 31, incorrectly optimized it which resulted in unexpected values being produced.

This was possible when doing an index access on the bytesNN types (so all the types like bytes1, bytes2 to bytes32) or when using the BYTES opcode in assembly.

Unexpected values were produced when these conditions were met, from Solidity versions 0.5.5 to 0.5.7.

Essential Assignments Removed with Yul Optimizer

There was another compiler bug coming from the Yul optimizer. In this case, the Yul optimizer removed essential assignments for variables that were specifically declared inside for loops.

This would happen while using Yul's continue or break statements, and again limited to the Solidity compiler versions 0.5.8/0.6.0 to 0.5.16/0.6.1.

Privat Methods Overriden

Remember that function visibilities in Solidity can be private, internal, public or external.

private functions are specific to the contract in which they are defined: they can't be called from any other contract, even those deriving from it.

While this is true, it was still possible for a derived contract to declare a function of the same name and type as a private function in one of the base contracts. And by doing so, change the behavior of the base contracts function.

What is interesting to note here, from a security perspective, is that this compiler bug was present across multiple Solidity versions all the way from 0.3.0 to 0.5.17.

Tuple Assignment Multi-stack Slot Components

Tuple assignments where the components occupied several stack slots, for example in the case of nested tuples, resulted in invalid values because of a compiler bug.

Notice again that this compiler bug lasted across 5 breaking Solidity versions: all the way from 0.1.6 to 0.6.6.

Dynamic Array Cleanup

When dynamically sized arrays were being assigned with types whose size was at most 16 bytes in storage, it would cause the assigned array to shrink to reduce their slots.

However, some parts of the deleted slots were not being zeroed out by the compiler. This would lead to stale or dirty data being used. This bug was fixed in Solidity version 0.7.3.

Empty Byte Array Copy

This bug is related to byte arrays, from memory or calldata, that were empty were copied to storage and they could result in data corruption.

This only occurred if the target array's length was subsequently increased, but without storing new data in it. Notice how specific the conditions are for this bug to be triggered. Nevertheless, this bug was discovered and fixed in Solidity version 0.7.4.

Memory Array Creation Overflow

When memory arrays were being created, if they were very large in size, then they would result in overlapping memory regions, which would lead to corruption.

In this case, this compiler bug was introduced in Solidity version 0.2.0 and fixed in version 0.6.5.

Calldata using for compiler bug

Remember, using for primitive is used for calling library functions on specific types used within the smart contract.

In this case the bug was specific to when the parameters used in such function calls were in the calldata portion of the EVM. In such cases, the reading of such parameters would result in invalid data being read. This bug existed accross Solidity versions 0.6.9 to 0.6.10.

Free Function Redefinition

Remember, free functions in Solidity are functions that are declared outside contracts (i.e. at file level).

This compiler bug allowed free functions to be declared with the same name and parameter types. This redefinition or collision was not detected by the compiler as an error. This bug was present in one of the recent Solidity versions: 0.7.1, and fixed in 0.7.2.

Compiler bugs should be taken very seriously because, unlike smart contracts that may differ from each other in the logic implemented, in the data structures or other aspects used, the compiler is a common dependency or perhaps a single point of failure for all the smart contracts compiled with that version.

Having said that, let's also recognize that a compiler is another software, so just like any software it is bound to have bugs and perhaps even more, because the compiler is significantly more complex than a smart contract or any other general software application.

From a security perspective, the things to be kept in mind when looking at a compiler version that's being used in smart contracts is to know which features of that compiler version are considered as being extensively used, and which are considered as experimental and perhaps staying away from them, so that one is not susceptible or vulnerable to any bugs in them.

It is also important to recognize the bugs that have been fixed in the compiler version, the bugs that have been reported and perhaps fixed in later versions (if those are available). These aspects should dictate the choice of the compiler version for the smart contracts and the specific features that are available within those compiler versions.

So the takeaway is that some of these compiler bugs may be so deep down in the compiler code and may be triggered only under specific conditions, that they might not be discovered very soon after the compiler is released. So while the test of time is true, there are no guarantees that a much older compiler version has most of its bugs discovered, reported and fixed.

Last updated