Project 0x Case Study

By Sharon Byrne-Cotter | Friday, April 17th, 2020

Abstract

Smart contracts facilitate the transfer of value and help determine digital asset behavior. This results in a higher need for formal proofs and computer-aided checks compared to traditional software which does not typically perform these functions. 0x is an open protocol that enables the peer-to-peer exchange of assets on the Ethereum blockchain. It is one of the largest open protocols with over 30 projects building on top of it, amassing over 713,000 total transactions, and a volume of $750 million.

0x release v3.0 went live on December 2nd 2019. The release included a significantly more complex exchange environment. As such, 0x sought out MythX, along with ConsenSys Diligence, to perform a manual security audit in order to increase confidence in the correctness of the smart contract code.

MythX performed the following techniques in the 3.0 branch of the 0x monorepo:

  • Run MythX Pro to check each smart contract individually for bugs
  • Execute fuzzing campaigns on a live multi-contract environment using the Harvey greybox fuzzer
  • Verify correctness properties of the smart contracts using symbolic execution and greybox fuzzing

As a result:

  • 37 potential issues were detected by MythX Pro
  • 149 potential issues were detected by MythX’s extended greybox fuzzing campaign
  • 5 custom contract properties were verified through custom checks

Continuously verifying the code using MythX, including the custom checks built-in this project, was recommended to prevent regressions and new security issues.

Problem statement

0x’s network of decentralized exchanges has processed over $750 million since inception. As such, 0x wanted to ensure that no funds would be at risk during the transition from v2.0 to v3.0. The v3.0 release has a more complicated exchange environment due to an increased amount of smart contracts interacting with each other, resulting in more complexities and potentially introducing hidden bugs. 0x’s need to deeply analyze the code, integrate continuous analysis into their deployment pipeline, and verify specific contract properties led to a natural partnership with MythX. Both automated analysis and human auditing was conducted to ensure high confidence that the v3.0 release would be secure and bug-free.

“Working with the MythX team solidified our perspective on the effectiveness of fuzz testing, and strengthened the trust in the audit report ConsenSys led on our v3.0 release.” – 0x Team

Smart contract security solution

MYTHX PRO VULNERABILITY SCAN

MythX Pro, a security analysis tool that detects 26 different classes of security vulnerabilities by performing static analysis, dynamic analysis, and symbolic execution, was used to detect smart contract bugs on 197 contracts. Each smart contract was compiled individually and checked against a class of known vulnerabilities from the SWC registry. The SWC registry is a database that contains a list of known smart contract vulnerabilities, with each known vulnerability having its own SWC identifier (ID). The following table lists the bug classes that were tested for. A checkmark in the “Pass” column indicates that no issues were detected in the category, while an “X” indicates that one or more issues in the category were found.

Table 1: Vulnerabilities checked with MythX Pro

In addition, a coverage estimate was generated to estimate the residual risk that a new path or behavior would appear. This is helpful because input fuzzing is a randomized process and does not yield a guarantee that all issues have been discovered. Diagram 4 shows the estimated residual risk for the smart contracts that were analyzed. Having a lower estimated residual risk value indicates that the chances of a new vulnerability or behavior appearing is low.

Diagram 3: Estimated residual risk of the analyzed smart contracts

In total, Harvey discovered 149 issues.

Table 2: Harvey discovered issues

Diagram 4: Vulnerabilities found sorted by SWC types

The results were reviewed manually to understand why the issues were flagged, with a greater focus on the issues that were most likely to cause security risks such as re-entrancy and improper handling of external calls. All 22 instances of SWC-104, SWC-107, and SWC-113 were reviewed. The review did not uncover exploitable vulnerabilities. It was also noted that the issues with the highest risk were intended by the 0x developers. For example in Example 1, the transaction would not be reverted for failing calls, which could result in propagating the failure. However, this issue along with the others detected are mitigated since the contract that could exploit the vulnerabilities is a “trusted” contract, which is another 0x contract.

Example 1: How the transaction will not revert if the call fails

VERIFICATION OF CUSTOM PROPERTIES

Custom tests for five security properties were created for the 20 deployed smart contracts to check the intended behavior of specific contracts, also known as functional correctness. Fuzzing, symbolic execution, and SMT solving were used to determine whether the smart contracts behaved correctly with respect to the properties.

Generally, such tests are implemented by inserting runtime assertions into the code. MythX or offline versions of Harvey and Mythril, an analysis tool that performs symbolic execution and SMT solving, are then used to detect counterexamples.

The custom properties were chosen by referring to 0x’s design document on bug classes to avoid and to see which bugs were expressible for 0x’s codebase. Table 2 shows which properties were checked for. A check mark indicates that this property holds where an “x” indicates detected unintended behavior.

Table 2: List of custom properties checked for against 0x’s codebase

Three instances of integer underflows were detected for the property, Fixedpoint Integer Arithmetics.

Example 2: Outputs of three instances of integer underflows

In addition to the checks listed above in Table 2, another custom check for a more specific property was created based on an issue discovered by the ConsenSys Diligence audit team. The check verifies the complex state invariants for the MixinStorage contract. After using Harvey, it was determined that the code responsible for the issue was not part of the contracts that were deployed to the mainnet. It will be possible to automatically check the property once this contract is part of the migrated contracts. This would be an example of checking for violations during the course of the development life cycle.

Conclusion

The MythX team was able to detect 37 potential issues using MythX Pro, 149 potential issues using MythX’s extended greybox fuzzing campaign, and verified 5 custom contract properties through custom checks. To prevent regression and newly introduced security bugs, continuously verifying the code using MythX, including the custom checks built in this project, was recommended.

TECHNICAL MILESTONES

The completion of the 0x project performed by both MythX and ConsenSys Diligence not only resulted in high confidence that the upcoming 0x v3.0 release will be secure, it has also produced a significant milestone for Ethereum security.

  • MythX Pro is the first automated security analysis tool that is able to perform both symbolic execution and fuzzing on a major project
  • Comprehensive coverage and realistic analysis and testing was performed by deploying smart contracts to a local testnet and extracting information to create the initial state (deployed state) for the greybox fuzzing campaign
  • The estimated residual risk on the likelihood that a new path or behavior would appear was calculated on smart contracts analyzed by our Harvey greybox fuzzer

“Working with the MythX team solidified our perspective on the effectiveness of fuzz testing, and strengthened the trust in the audit report ConsenSys led on our v3.0 release.” 0x Team


TECHNOLOGY PARTNERSHIP

About MythX

MythX, a ConsenSys product, scans for security vulnerabilities in Ethereum smart contracts. Its comprehensive range of analysis techniques which include static analysis, dynamic analysis, and symbolic execution, accurately detects security vulnerabilities and provides an in-depth analysis report. With a vibrant ecosystem of world-class integration partners that amplify developer productivity, MythX can be utilized in all phases of the smart contract development lifecycle.

For more information, visit mythx.io

__________________________

About ConsenSys Diligence

Our smart contract auditing and blockchain security services are delivered by a highly experienced team, driven by their passion for enabling more secure platforms and ecosystems. ConsenSys Security auditors and researchers are distributed all over the world and focused on creating tools that are truly helpful to auditors and smart contract developers.

For more information, visit diligence.consensys.net

Share this post:

Catching Weird Security Bugs in Solidity Smart Contracts with Invariant Checks

By Bernhard Mueller | Monday, April 6th, 2020

Contract invariants are properties of the program program state that are expected to always be true. In my previous article I discussed the use of Solidity assertions to check contract invariants. This article expands on the use of invariants and provides a couple of additional examples.

An interesting feature of invariant checking on the bytecode level is that it allows you to detect low-level issues, including issues caused by compiler optimisation or idiosyncrasies of the programming language, by defining high-level rules. For instance, in the last article we saw that a high-level rule “the contract should always remain unlocked” could be broken by exploiting Solidity memory addressing. Let’s look at some more examples for the effectiveness of contract invariants.

Example 1: Integer rounding

Integer divisions in Solidity round down to the nearest integer. Failure to consider the rounding behaviour can lead to bugs and vulnerabilities. Consider the example from Sigma Prime’s blog post which also has a great writeup on this bug class:

pragma solidity ^0.5.0;

contract FunWithNumbers {

    uint constant public tokensPerEth = 10;
    uint constant public weiPerEth = 1e18;

    mapping(address => uint) public balances;

    function buyTokens() public payable {
        uint tokens = msg.value/weiPerEth*tokensPerEth; // convert wei to eth, then multiply by token rate
        balances[msg.sender] += tokens;
    }

    function sellTokens(uint tokens) public {
        require(balances[msg.sender] >= tokens);
        uint eth = tokens/tokensPerEth;
        balances[msg.sender] -= tokens;
        msg.sender.transfer(eth*weiPerEth); 
    }
}

Let’s put on the auditor’s hat and come up with some meaningful properties to check. Amongst other things we could assert that, when comparing the contract account state at the beginning and end of a transaction, the following should always be true:

  1. The token balance of msg.sender increases when the contract account balance increases;
  2. The contract account balance increases when the token balance of msg.sender increases;
  3. The token balance of msg.sender decreases when the contract account balance decreases;
  4. The contract account balance decreases when the token balance of msg.sender decreases.

This could be generalised even more (e.g. the sum of all token balances increases if and only if the contract balance increases) and supplemented with additional checks (balance always increases by the right amount, …) to catch more possible bugs.

MythX supports standard Solidity assertions and the MythX AssertionFailed event which allows you to add a custom error message. One way of checking contract invariants is by creating a modifier to wrap all public and external functions. This ensures that the invariant holds at the end of all internal message calls and transactions. This approach has some limitations but let’s roll with it the meantime (support for inline specifications is coming to MythX soon™).

Typically we’ll create modifier along the following lines:

modifier checkInvariants {    

  // Save old state
  uint sender_balance_old = balances[msg.sender];    
  
  // Execute the function body
  _;

  // MythX AssertionFailed event    
   if (--- this should never occur ---) {
       emit AssertionFailed("Some error message");
    }

   // Solidity assertion    
   assert(--- this should always hold ---);
}

We’ll add the checks by creating a wrapper contract and overriding the public functions in FunWithNumbers:

contract VerifyFunWithNumbers is FunWithNumbers {

uint contract_balance_old;

constructor() public {
contract_balance_old = address(this).balance;
}

event AssertionFailed(string message);

modifier checkInvariants {
uint sender_balance_old = balances[msg.sender];

_;

if (address(this).balance > contract_balance_old && balances[msg.sender] <= sender_balance_old) {
emit AssertionFailed("Invariant violation: Sender token balance must increase when contract account balance increases");
}
if (balances[msg.sender] > sender_balance_old && contract_balance_old >= address(this).balance) {
emit AssertionFailed("Invariant violation: Contract account balance must increase when sender token balance increases");
}
if (address(this).balance < contract_balance_old && balances[msg.sender] >= sender_balance_old) {
emit AssertionFailed("Invariant violation: Sender token balance must decrease when contract account balance decreases");
}
if (balances[msg.sender] < sender_balance_old && address(this).balance >= contract_balance_old) {
emit AssertionFailed("Invariant violation: Contract account balance must decrease when sender token balance decreases");
}

contract_balance_old = address(this).balance;
}function buyTokens() public payable checkInvariants {
super.buyTokens();
}

function sellTokens(uint tokens) public checkInvariants {
super.sellTokens(tokens);
}
}

Running this contract through the MythX Remix plugin detects two invariant violations. Let’s have a closer look at the reported issues.

User buys tokens for 6 Wei. 6/10 hets rounded to zero so no tokens are added to the user’s balance.

The first counterexample produced by MythX shows a user attempting to buy tokens with a very low call value of 6 Wei. In this case, the 6 Wei get added to the contract balance but the balance of the user does not increase.

Taking another look at the code, we can see that if less than 1 Ether is sent when calling buyTokens(), the result of the division msg.value/weiPerEth will be rounded down to 0 and the subsequent multiplication with tokensPerEth will likewise return 0.

The second counterexample shows a user first buying one Ether (1000000000000000000 Wei) worth of tokens (transaction 2), then selling 6 tokens (transaction 3). This deducts 6 tokens from the user’s balance but does not transfer any Ether to the user due to the rounding error in sellTokens(uint tokens) : uint eth = tokens/tokensPerEth will be rounded down to 0 if tokens is lower than 10 or to the nearest multiple of 10 otherwise.

Example 2: Arbitrary storage writes via large-sized arrays

A couple of years ago I wrote about proving security properties using the open-source tool Mythril. By checking contract invariants with MythX you can achieve similar results. It is however important to point out the difference between the two methods:

  • The method shown previously was based on under-constrained symbolic execution of the runtime bytecode (using an older version of Mythril) whereby storage variables were initialised as symbolic variables. This method detects all instances of a bug but also returns false positives.
  • MythX combines symbolic execution and input fuzzing after partially initialising the contract account state with the concrete values. By doing this it avoids false positives but there’s a residual risk of false negatives. The risk of missing bugs declines the longer the tools are allowed to run.

With that said, let’s revisit the example shown in the old article which was based on Doug Hoyte’s classic USCC submission. This smart contract contains a hidden method of overwriting the owner state variable:

pragma solidity ^0.5.0;

contract Pwnable {
    address public owner;
    uint[] private bonusCodes;
    
    constructor() public {
        bonusCodes = new uint[](0);
        owner = msg.sender;
    }
    
    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);
        bonusCodes[idx] = c;
    }
}

To catch improper modifications of the owner variable we can informally define the property:

  • The owner must not change in the course of the transaction unless the transaction sender matches the existing owner.

Which translates to the following Solidity assertion:

assert(owner == old_owner || msg.sender == old_owner);

Using the aforementioned method of creating a wrapper around the Pwnable contract, we get:

contract VerifyPwnable is Pwnable {   

  modifier checkInvariants {
        address old_owner = owner;       
        _;        
        assert(msg.sender == old_owner || owner == old_owner);
     }
    
     function PushBonusCode(uint c) public checkInvariants {
         super.PushBonusCode(c);
     }    

    function PopBonusCode() public checkInvariants {
        super.PopBonusCode();
    }   

    function UpdateBonusCodeAt(uint idx, uint c) public checkInvariants {
        super.UpdateBonusCodeAt(idx, c);
    }}

This time MythX detects one assert violation in the code:

Apparently there is a way to change the contract owner even though the state variable owner is not explicitly accessed anywhere in the code. This happens due to the way dynamically sized arrays are laid out in storage:

  • The length of the array uint[] bonusCodesis stored at storage slot 1;
  • Accessing array element n points to storage address keccak(1) + n.

The state variable owner lives at storage slot 0. To access it, one must first underflow the unsigned integer variable bonusCodes.length which happens in the first function call:

  • PopBonusCode()

In the second call we write to the array index that equals(MAX_UINT — keccak(1)). This will result in a write to the storage address keccak(1)+(MAX_UINT — keccak(1)) which overflows into 0:

  • UpdateBonusCodeAt(35707666377435648211887908874984608119992236509074197713628505308453184860938, 0)

Voila! The owner is now set to the zero address (to set it to any other address, convert it to an unsigned integer and pass it with the second argument). You can try this out with the Remix JavaScript VM or Ganache to verify that it works.

It’s worth pointing out that even without the assertion, MythX automatically detects the two individual flaws that make the owner overwrite possible. The first issue is the integer overflow caused by decreasing bonusCodes.length. Note that this is not possible anymore with solc 6.0 or higher which makes the array length read-only.

Additionally, MythX reports that a writes to an arbitrary storage location are possible following the length underflow:

TL;DR

By checking high-level invariants using symbolic execution and input fuzzing you can verify assumptions about how your code behaves. This can uncover unexpected bugs caused by peculiarities of the Solidity programming language, such as rounding errors, unsafe integer arithmetics and addressing of elements in storage.

By the way, all examples in this article can be reproduced using the free version of MythX. You should try it out!

Share this post:

Targeted fuzzing using static lookahead analysis: how to guide fuzzers using online static analysis

By Valentin Wustholz | Tuesday, March 31st, 2020

In previous posts, we introduced Harvey, a fuzzer for Ethereum smart contracts, and presented two techniques to boost its effectiveness: input prediction and multi-transaction fuzzing.

Harvey is being developed by MythX in collaboration with Maria Christakis from MPI-SWS. It is one of the tools that powers our smart contract analysis service. Sign up for our free plan to give it a try!

In this post, we summarize our upcoming ICSE 2020 paper and provide a high-level overview of how we use online static analysis to guide Harvey. If you are not familiar with fuzzing we suggest to check out the above post since we will assume some basic background below.

Targeted fuzzing

Let’s assume that there are specific target locations in a program you want to fuzz. For example, these could be locations that were recently modified or contain assertions that should be checked. Or maybe a static analyzer has reported warnings for these locations.

The goal of targeted fuzzing is to generate inputs that explore these locations as quickly and frequently as possible. This is challenging since the fuzzer cannot easily predict if a given input explores locations that are “close” to a target location. Unfortunately, the fuzzer only knows which locations are actually explored by the input.

To help the fuzzer with this task, we perform a static lookahead analysis for each input that is added to its queue of “interesting” inputs. The fuzzer uses this (cyclic) queue to select the next input that will be fuzzed and typically adds inputs that increase coverage.

Lookahead Analysis

The diagram below illustrates the idea behind this novel analysis. Given an input that explores a program path π (bold in the diagram), we consider several so-called split points P1 to Pn along that path. For each of these points Pi, starting from P1, we perform a light-weight static analysis that involves two steps.

Lookahead analysis for a given input

First, we consider the prefix πpre of the entire program path π that ends at split point Pi. We compute a condition ϕ that over-approximates all the program states at the split point when executing the prefix. We then check if any target locations are reachable when starting from Pi in a program state that satisfies the condition ϕ. In other words, we check if there are any program paths (with prefix πpre) that reach a target location. In the diagram, the triangles represent sets of paths.

As soon as no targets are found in the second step, we stop the lookahead analysis and we consider the corresponding prefix πpre to be a so-called no-target-ahead prefix. The fuzzer records the split points along this prefix and computes a lookahead identifier by hashing the prefix. For instance, in the diagram above πpre is a no-target-ahead prefix since none of the targets Tx and Ty are reachable from split point Pi.

The lookahead identifier is similar to the path identifier that a greybox fuzzer computes based on which control-flow edges are traversed when executing a given input (see our post on greybox fuzzing for more details). Observe however, that two inputs with different path IDs may share the same lookahead ID if both exercise the same no-target-ahead prefix.

In our implementation, we use Bran, an abstract interpreter for EVM bytecode, to perform the static analysis. Since this analysis runs frequently it needs to be very efficient and light-weight.

Adapting the greybox fuzzing algorithm

Let’s see how we can use this additional information—the split points and the lookahead identifier—to guide the fuzzer. Before that, we will quickly recap the standard greybox fuzzing algorithm below:

// We set up the initial queue by executing the program P for the seed inputs S. 
Q := run_seed_inputs(P, S)
while (running) {
  i := select_input(Q)
  e := assign_energy(i) // Adaptation: We assign energy based on lookahead information.
  while (0 < e) {
    f := fuzz_input(i)
    pid := run_input(P, f)
    if pid not in Q {
      Q[pid] := f
      // Adaptation: We will run the lookahead analysis for input i.
    }
    e := e - 1
  }
}

The fuzzer initially runs the program P with the seed inputs S to initialize the queue Q. While the fuzzer is running, it keeps selecting inputs that will be fuzzed from the queue. It uses a function assign_energy to determine how often a selected input is fuzzed.

When a fuzzed input discovers a new path ID, it is added to the queue (here, a simple map from path ID to input). This is where we run the lookahead analysis (see third comment) to gather additional information about this newly discovered input.

We use this information to adjust how much energy we assign to it in the future (see second comment). We will assign more energy if (1) the input’s no-target-ahead prefix was rarely executed, or (2) any of its split points were rarely executed.

Intuitively, the lookahead ID groups (or partitions) the inputs in the queue since multiple inputs may share the same lookahead ID. The above energy schedule aims to prevent the fuzzer from spending disproportionate amounts of energy on a few partitions that contain many inputs. The split points partition the queue in a similar way and both factors turn out to be important in practice. The key insight is that the fuzzer gains more from discovering inputs that explore new lookahead IDs or new split points than ones that explore existing ones.

Evaluation

We evaluated the effectiveness of this approach on 27 real-world smart contracts and found that the fuzzer was able to reach 83% of the challenging target locations significantly faster (with a speedup of up to 14x). We also found that the overhead of the static analysis was very low (~3s per fuzzing campaign).


In this post, we provided a high-level overview of how we use online static analysis to guide Harvey. For more details please check out our upcoming ICSE 2020 paper.


Thanks to Maria Christakis and Joran Honig for feedback on drafts of this article.

Share this post:
Category:

Easy multi-contract security analysis using Mythril

By Joran Honig | Monday, March 9th, 2020


by Anastasia Dulgier

The MythX platform leverages several internal components to provide the best possible analysis results. One of these components is available open-source; the symbolic executor Mythril. In this article, I’ll demonstrate how you can use Mythril to analyze a set-up of multiple smart contracts.


By default, Mythril will analyze a contract in isolation. Interactions with external contracts are generalized so that we capture all possible vulnerabilities. Sometimes, this means we find a weakness in your smart contract that might not affect your particular setup. That’s because the specific deployment values you use can have a considerable effect on how the system behaves as a whole.

Luckily you can also use Mythril to execute multi-contract analysis and analyze a specific configuration of multiple smart contracts. As a result, you might capture fewer warnings, but the results will be tailor-made to your deployment.

To do multi-contract analysis, we’ll use Mythril’s ability to analyze contracts deployed on an Ethereum network (hint: we’ll use Ganache to launch our private test network). We will deploy our contract on a network, and in doing so, create a possible target for Mythril to analyze.

How To

To perform the multi-contract analysis, we will execute the following steps:

  • Deploy a smart contract system to Ganache
  • Set up Mythril to load code and world state from Ganache
  • ⚡ Fire lasers at the smart contract ⚡

But first, make sure you have everything you need installed:

# The dependencies you will install are: truffle, ganache and mythrilnpm install -g trufflenpm install -g ganache-clipip3 install -U mythril

You will also need to get an example project to try multi-contract analysis on, I’ll be using the simple Metacoin contract.

# These commands create a metacoin directory,# with the metacoin truffle boxmkdir metacoincd metacointruffle unbox metacoin

Deploying your smart contract system to Ganache

We’ll start by getting a test network up and running. This network is where we deploy our contracts and where Mythril will find the smart contract state.

# Execute this command to create a new ganache networkganache-cli -p 7545 --i 5777

With the network set up, we can deploy the contracts; and because we’re using Truffle, this will be quite easy.

truffle migrate

This command will execute all the necessary steps to get your contract set up on the test network we provided. (check out the Truffle documentation for more information on this command)

Now search the ouput of the truffle migrate command for the address where the Metacoin contract (our target) was deployed. 
Look for contract address in the Deploying 'MetaCoin' section.

You can see an example of the output in the image below, where I’ve highlighted the address of the fresh Metacoin Contract: 0x8712c227680bc0c8a4c7a65317C7e7700e5D566f

output of “truffle migrate”

Analysis Time

With the deployment completed, all that is left is to execute the following command (and possibly enjoy a ☕) :

# This command initiates the multi-contract analysismyth a -a <target_address> -l --rpc localhost:7545

As you can see, there are a few options required, here’s a brief explanation of all the elements in this command:

myth a – This is the analyze command that enables the analysis capabilities of Mythril.
-a <target_address> – This option specifies the analysis target, in our case the Metacoin contract account on the test network.
-l – This second option tells Mythril to not just use the code of the target Account, but to use world state data from the Ethereum network whenever possible.
--rpc localhost:7545 – The rpc option tells Mythril where to find an Ethereum node to get all the necessary data. In our case the node is part of our very own test network, which lives on localhost listening on port 7545.

All Good!

$ myth a -a 0x8712c227680bc0c8a4c7a65317C7e7700e5D566f --rpc localhost:7545 -lThe analysis was completed successfully. No issues were detected.

Luckily there were no bugs in the MetaCoin contract. Unfortunately this is not always the case. Try running multi-contract analysis on your own smart contracts and see what you can find!
Also, let me know if you found a nice vulnerability using multi-contract Mythril! (Nothing beats hearing about how Mythril helped catch a nasty bug 🐛)

Share this post:
Category: