Sometimes, you find what you’re looking for where you least expect.

Mysten Labs, the organization behind the Sui blockchain, contracted with us for a security assessment of their Layer 1 core. Specifically, we were tasked with securing Sui’s Move bytecode verifier before for their mainnet launch. The verifier is responsible for ensuring the core security properties of the Move VM are upheld. Interestingly, this verifier is shared among all Move-based blockchains, including Aptos ($2.1B market cap), Starcoin, and 0L.

Our assessment’s scope primarily focused on the bytecode verifier itself and programmable transactions. However, the bug we found was in one of the dependencies of this verifier. In providing a comprehensive review, we decided to dig into these as well — after all, a good hacker can never resist a nice rabbit hole. In the move-binary-format ****crate, which is shared across all Move implementations, we discovered a critical, subtle, and novel bug relating to the verification of bytecode control flow. This was the last place we would have expected to find this kind of critical flaw!

In this blog post, we disclose this critical issue. This bug places potentially billions of dollars at risk. It would allow attackers to bypass the locals safety and reference safety verifier passes of the core Move verifier, plus the Sui-specific ID leak verifier (in which we previously discovered another bug). We will discuss the specific attack scenarios as well. Once again, this vulnerability impacted not only Sui but also other platforms using Move, including Aptos.

Ultimately, the bug would have allowed attackers to obtain multiple mutable references to an object, retain a mutable reference to an object that was moved, and to drop an object without the drop ability. Move is a language similar to Rust, and these are violations of Move’s most fundamental security properties.

TL;DR: A subtle bug in the Move bytecode verifier allowed attackers to bypass multiple security properties, potentially leading to significant financial damages. The bug affected the construction of the control flow graph (CFG) of a function. Exploits included dropping a value without the drop ability, bypassing the reference safety verifier, and bypassing paranoid type checks. The issue was fixed, and we collaborated closely with Mysten Labs during the disclosure process.

Move language primer

If you already know about Move and want to get directly to the details of the bug, click here to skip the introduction.

The Move language and virtual machine, originally designed for the now defunct Libra Project, bring interesting new ideas to the space. The language somewhat resembles Rust, having a similar syntax, strong static typing, and strong ownership with a borrow checker.

Modules (smart contracts in Move lingo) can define custom data types. The contents of an instance can only be directly accessed by the code that defined the type, meaning the fields of a custom type can be trusted by default. Also by default, a module cannot copy or drop (delete) an instance of a data type defined by another module, unless the data type was defined with the copy or drop abilities.

This programming model is interestingly unique, as it allows to create data types that have properties similar to a physical object; for instance, Coin<T>, the data type used to represent a token on Move platforms, cannot be copied nor dropped, meaning it is not possible to create a Coin out of thin air nor to make a Coin vanish by mistake. As we will see, the inability to drop a value is also critical to the secure operation of flash loans. Recall that flash loans essentially provide untrusted users access to unsecured, “infinite” leverage for the duration of an atomic transaction — thus, any mistakes in the operation of a flash loan would be devastating.

For a comprehensive overview of the Move language, refer to The Move Book. You can also check out our two-part blog post on Move security: part 1 and part 2.

The Move verifier

Move source code is compiled to a bytecode that runs on the Move VM. The verifier’s job is to read and statically analyze bytecode to look for any loopholes that could bypass the Move security properties we discussed above. To be more technical, this means that before being published and run, the bytecode of a module is run through a set of verifier passes that ensure it does not violate any of the security properties defined in the Move programming model. The verification process is quite complex, involving a dozen verifiers, some of which are divided in smaller passes: