Securing Smart Contracts: A Dev’s Guide, Part II

Introduction

The Ethereum Virtual Machine (EVM), the runtime environment for smart contracts on the Ethereum blockchain, has witnessed numerous high-profile hacks and exploits due to various vulnerabilities. These include arithmetic issues like overflow and underflow, reentrancy attacks that manipulate contract state, and access control problems that allow unauthorized entities to perform privileged actions.

As the blockchain ecosystem evolves, there is a growing need for more secure and robust smart contract platforms. Move, a new programming language developed by Facebook’s Diem (formerly Libra) project, aims to address many of the shortcomings of the EVM and provide a safer environment for building smart contracts.

In this report, we will explore how Move’s design principles and language features help circumvent common EVM vulnerabilities. We will delve into Move’s built-in safeguards against overflow and underflow errors, its inherent protection against reentrancy attacks, and its resource-based access control model. By comparing Move’s approach to the EVM’s vulnerabilities, we will highlight the potential for Move to usher in a new era of secure smart contract development.

Circumventing EVM Vulnerabilities

Overflow / Underflow

Overflow and underflow errors are critical issues in programming that occur when arithmetic operations exceed the storage capacity of a data type. In simple terms, an overflow happens when a calculation produces a value larger than the maximum limit a data type can hold, and an underflow occurs when a result is smaller than a data type’s minimum limit.

Consider a uint8 data type, which can store values from 0 to 255. Adding any positive integer to 255 will lead to an overflow. Ideally, in systems like blockchain, this overflow should not silently wrap the integer (i.e., loop around to 0) but should instead trigger an error or revert the transaction. This behavior is crucial because, for example, in a financial application, wrapping around could erroneously zero out a user’s balance after reaching the maximum cap.

Here’s an example of how an underflow vulnerability could be exploited in a simplified token transfer function:

In this case, if balances[msg.sender] is less than amount, the subtraction balances[msg.sender] – amount will underflow and result in a large positive value. The require statement will pass, and the transfer will proceed, allowing the attacker to transfer tokens they don’t own.

Overflow / Underflow Handling in Move

Move programming language provides built-in safeguards against integer overflow and underflow errors, which are common bugs in many programming languages. These errors occur when an arithmetic operation results in a value that exceeds the maximum or minimum limit of the integer type being used.

In Move, arithmetic operations are checked for overflow and underflow at runtime. If an operation results in an overflow or underflow, the transaction is immediately aborted, and an error is raised. This behavior is different from some other languages, such as Solidity prior to version 0.8, where an overflow would silently wrap around to the minimum value of the integer type.

Let’s consider an example to illustrate how Move handles overflow:

In this code snippet, we declare a variable `num` of type `u8`, which is an unsigned 8-bit integer. The maximum value that a `u8` can hold is 255. We then attempt to add 1 to `num`, which would result in an overflow.

When this code is executed, Move’s runtime detects the overflow and raises an error:

The transaction is aborted, and an “Arithmetic Error” is reported, preventing the overflow from silently occurring and potentially leading to unexpected behavior or vulnerabilities.

However, it’s important to note that Move’s overflow and underflow checks do not extend to bitwise operators. For example:

In this case, the left shift operation `<<` is performed on `num`, which is already at the maximum value for a `u8`. The result of the operation overflows, but Move does not raise an error. This behavior is similar to Solidity 0.8+, where overflow checks are not performed on bitwise operations.

While this limitation exists, it is generally less critical compared to arithmetic operations, as bitwise operations are less commonly used in most smart contract scenarios.

Overall, Move’s built-in overflow and underflow checks provide a robust safety net for preventing common arithmetic errors in smart contracts. By raising errors and aborting transactions when an overflow or underflow occurs, Move helps developers write more secure and reliable code, reducing the risk of unintended behavior and vulnerabilities.

Re-entrancy

Reentrancy is a common vulnerability in smart contracts, particularly those on Ethereum. It occurs when a function call to an external contract occurs before the initial function has finished executing, allowing the external contract to re-enter the original function and manipulate its state in a harmful way.

Here’s a simplified example of how a reentrancy vulnerability could be exploited in a Solidity contract:

In this vulnerable contract, the withdraw function sends the user’s balance to their address using msg.sender.call.value() before updating the user’s balance to zero. An attacker can create a malicious contract that recursively calls the withdraw function, draining the contract’s funds before the balance is set to zero.

The most infamous reentrancy attack occurred with The DAO (Decentralized Autonomous Organization) on Ethereum. Here’s how it was exploited:

  • Contract Design: The DAO’s smart contract allowed users to “split” from the DAO and reclaim their Ether by withdrawing it to a new DAO.
  • Attack Execution: The attacker noticed that the contract did not immediately update its internal state to reflect the withdrawal. They executed a function that recursively called itself, each time withdrawing Ether before the contract had updated the balance.
  • Outcome: This allowed the attacker to repeatedly withdraw funds in a single transaction, eventually draining approximately 3.6 million Ether, which was worth around $50 million at the time.

Reentrancy Protection in Move

Reentrancy exploits have been a significant problem in Ethereum smart contracts, leading to numerous hacks and losses of funds. However, the Move programming language offers inherent protection against reentrancy attacks due to its design and features.

One of the key features of Move that helps mitigate reentrancy is the absence of dynamic dispatch (with some caveats related to generics). In Move, functions are resolved and linked at compile time rather than runtime. This means that the target of a function call is determined during the compilation process, making it impossible to dynamically compute the target at runtime.

Let’s compare this with Solidity, where dynamic dispatch is possible:

In this Solidity example, the `arbitrary_call` function can call any address with any data, and the target of the call is determined at runtime. This flexibility opens up the possibility of reentrancy attacks.

In contrast, Move requires all external modules to be predeclared and imported (even as stubs) and cannot be dynamically computed at runtime. This static nature of function calls in Move significantly reduces the attack surface for reentrancy.

Moreover, Move has cyclic dependency checks during the linking phase, which can catch most, if not all, reentrancy vulnerabilities. Let’s consider an example:

In this example, we have two modules: `callback_module` and `airdrop_module`. The `callback_module` calls the `claim_airdrop` function from `airdrop_module`, and within the `claim_airdrop` function, there is a callback to `callback_module::on_token_received()`.

This creates a circular dependency between the two modules. When attempting to compile and link this code, Move’s cyclic dependency checker will detect the issue and raise an error, preventing the potential reentrancy vulnerability.

Access Control

Access control vulnerabilities are a common issue in smart contract systems, where the failure to properly enforce restrictions on who can perform certain actions or access specific resources can lead to severe consequences. Access control is a fundamental security concept that revolves around managing and restricting access to sensitive functions, data, or resources within a system. In the context of smart contracts, access control ensures that only authorized entities can perform specific actions or interact with the contract in predefined ways.

Access control problems arise when a smart contract fails to enforce proper restrictions, allowing unauthorized parties to manipulate the contract’s behavior or access privileged functions. This can lead to a range of vulnerabilities, including:

  • Unauthorized modification of contract state
  • Theft of funds or tokens
  • Unintended access to sensitive data
  • Disruption of contract functionality
  • Failing to implement robust access control mechanisms can have severe consequences, as attackers can exploit these vulnerabilities to gain unauthorized control over the contract and its assets.

In the Ethereum Virtual Machine (EVM), access control vulnerabilities often stem from improper use of function visibility specifiers and lack of proper authorization checks. Let’s explore a few real-world examples to understand how these vulnerabilities manifest.

The Parity Wallet smart contract suffered from an access control vulnerability that allowed an attacker to gain unauthorized access to the contract’s initialization function. Here’s a simplified version of the vulnerable code:

The initWallet function was meant to be called only once during contract deployment to set the contract owner. However, due to missing access control, anyone could call this function at any time and change the owner address. The attacker exploited this vulnerability to take control of multiple wallets and steal approximately $30 million worth of Ether.

Move’s approach to access control

Move’s approach to access control revolves around two key concepts: resources and abilities. Resources are structures that can only exist in one place at a time and cannot be duplicated or discarded, ensuring strict ownership and control. Abilities, on the other hand, define the actions that can be performed on a resource. The combination of resources and abilities forms the foundation of Move’s resource-based access control mode. Here’s an example:

In this example, the SensitiveData struct is defined with the key ability, making it a resource. The create_data function allows an account to create an instance of SensitiveData and store it in their account. The update_data function, annotated with acquires SensitiveData, allows the owner of the resource to modify its value. This ensures that only the owner can access and modify their own SensitiveData.

Furthermore, Move provides visibility specifiers like public, private, and friend to control the accessibility of functions and structs. By default, functions and structs are private, meaning they can only be accessed within the same module. Developers must explicitly mark them as public to allow external access and entry to allow users to access. This reduces the risk of unintended exposure. Also, the Move Prover is a formal verification tool that allows developers to specify and verify access control properties of their contracts. By writing specifications and invariants, developers can ensure that access control rules are properly enforced and catch potential vulnerabilities during the development process.

Conclusion

This report explores the vulnerabilities of the Ethereum Virtual Machine (EVM) that have led to numerous high-profile smart contract hacks and exploits. It highlights the need for a more secure and robust programming language to address these issues and introduces Move as a promising solution.

Move’s unique design principles and language features, such as built-in safeguards against overflow and underflow errors, inherent protection against reentrancy attacks, and a resource-based access control model, offer a more secure foundation for smart contract development. By addressing the root causes of common EVM vulnerabilities, Move has the potential to significantly reduce the risks associated with smart contract execution.

At Movement Labs, we are dedicated to advancing the security and reliability of smart contracts across the blockchain ecosystem. Our team of experts is actively researching and developing tools and frameworks that leverage Move’s capabilities to build more secure and trustworthy decentralized applications.


About Movement Labs Research

Movement Labs Research features technical insights and original research into the Move language, the Move virtual machine, modular blockchain development, and innovations with the Movement Labs SDK and Move Stack. You can find our content at https://blog.movementlabs.xyz/ and follow us on X @mvmt_research for the latest ideas and insights into Move-related development.

About Movement Labs

The first integrated blockchain network, powering the fastest and most secure Layer 2 on Ethereum. Designed to pair smart contract security and parallelization with EVM liquidity and user bases, Movement is bringing the MoveVM to Ethereum through its flagship L2 and connected rollups with the Move Stack. For more information about Movement Labs and a guide to participate in our devnet, please visit: movementlabs.xyz and follow on X @movementlabsxyz and on Discord.