Full Report
The author of this post was working on a project for the custom blockchain runtime Fuel VM. They created a fuzzing setup with special invariants to try to catch some bugs. The Fuel network team initially used libFuzzer and cargo-fuzz, but this was slow and didn't make any internal calls. To get better coverage, they used a shim to inject in LibAFL. This allowed for the execution of 1K per second on an eight-core machine versus the original 50 per second. An additional redesign was around the input generation. Originally, the input was a simple byte vector with not much else. Since the Sway language compiler has data interleaved with the actual instructions, the byte vector was ineffective. The input is now a byte vector that has the script assembly, script data, and a magic value separating them. Using this, they used the Sway compiler to obtain a large number of test inputs. They ran into some issues with fuzzing. The program secp256k1 0.27.0 version is not compatible with cargo-fuzz because it fails all the time. So, they had to use a different version for fuzzing. Secondly, they ended up modifying the FuelVM somewhat to add the offset into the execution data to have this within the context of the program. It is assumed this fuzzer was used for a large number of reasons, but a major one they used was gas usage. In most blockchain runtimes, different opcodes cost different amounts of the native currency to run. So, they wanted to see if the gas usage correlates with the execution time. They ran the fuzzer and plotted the information. Within the document, there are several for weird outliers. Through doing this and finding the outliers, they were able to find opcodes that should cost more in gas. In the "lessons learned" section, they state that fuzzers should be run until new coverage stops, pause the campaign when issues are found to let developers fix them, and save the corpus generated from each test. This is because it represents hours of refinement to find the edge cases in the codebase. Overall, a great look into setting up a legitimate fuzzing campaign. It was a unique reason to use fuzzing that turned up good results for them.
Analysis Summary
# Tool/Technique: Fuel VM Differential & Performance Fuzzing
## Overview
This technique involves the development and implementation of a high-performance fuzzing harness for the Fuel Virtual Machine (Fuel VM). Unlike standard fuzzing which looks for memory corruption (crashes), this specific implementation uses transaction "invariants" and gas-to-execution-time correlation to identify logical errors and "mispriced opcodes." Mispriced opcodes are a significant vulnerability in blockchain environments, as they can be exploited to launch Denial of Service (DoS) attacks by executing computationally expensive operations at a low cost.
## Technical Details
- **Type**: Tool / Security Testing Technique
- **Platform**: Fuel VM (Blockchain Runtime), Rust, Sway Language
- **Capabilities**: High-throughput execution (~1,000 exec/s), internal contract call simulation, gas usage profiling, and corpus-driven edge case discovery.
- **First Seen**: June 17, 2024 (Public disclosure by Trail of Bits)
## MITRE ATT&CK Mapping
- **[TA0042 - Resource Development]**
- **[T1588.006 - Obtain Capabilities: Vulnerability Research Artifacts]**: Developing custom fuzzers and corpora to identify zero-day vulnerabilities in blockchain runtimes.
- **[TA0040 - Impact]**
- **[T1499.004 - Endpoint Denial of Service: Application Exhaustion]**: Identifying mispriced opcodes that allow for resource exhaustion (Gas/CPU) attacks.
## Functionality
### Core Capabilities
- **LibAFL Integration**: Uses a LibAFL shim over the standard `cargo-fuzz` to enable multi-core execution, increasing performance from 50 to 1,000 executions per second.
- **Structured Input Generation**: Utilizes a custom byte vector format containing script assembly, script data, and contract assembly, separated by a 64-bit magic value (`0x00ADBEEF5566CEAA`).
- **Seed Corpus Acceleration**: Incorporates existing Sway compiler examples as initial inputs to bypass "shallow" code paths and reach complex logic faster.
### Advanced Features
- **Gas Usage Profiling**: Plots execution time against gas costs to identify statistical outliers where CPU time is disproportionately high compared to the gas charged.
- **VM Patching**: Features a modified Fuel VM that writes script data offsets into register `0x10` to allow the fuzzer to reliably access injected execution data.
- **Differential/Invariant Testing**: Beyond crashes, the tool checks if executions violate core blockchain logic or state consistency.
## Indicators of Compromise
*Note: As a security research tool, "indicators" here refer to its presence within a development environment or specific artifacts used during the campaign.*
- **Magic Value**: `0x00ADBEEF5566CEAA` (Used as a delimiter in fuzzer input files).
- **Registry/Registers**: Register `0x10` in Fuel VM (Modified to store script data offset).
- **File Names**: `collect_gas_stats.rs`, `fuzz_target.rs` (Standard LibAFL/cargo-fuzz naming conventions).
## Associated Threat Actors
- **N/A**: This is currently documented as a defensive security research tool used by **Trail of Bits** and **Fuel Labs**. However, the techniques (Gas profiling) are commonly used by sophisticated actors to find "Gas Arbitrage" or DoS vulnerabilities.
## Detection Methods
- **Behavioral Detection**: In a production blockchain environment, detection of repeated execution of specific "outlier" opcodes (identified via the profiling step) that result in high CPU latency but low gas consumption.
- **Development Auditing**: Monitoring for unauthorized modifications to VM interpreter logic (e.g., patching registers for data injection).
## Mitigation Strategies
- **Opcode Re-pricing**: Adjust the gas schedule of the VM based on the fuzzer's execution time plots to ensure all operations are economically "expensive" enough to prevent DoS.
- **Continuous Fuzzing**: Integrate fuzzers into CI/CD pipelines (e.g., `oss-fuzz`) to ensure new code changes do not introduce performance regressions.
- **Corpus Protection**: Treat the generated corpus as sensitive data, as it contains the edge cases required to trigger complex bugs.
## Related Tools/Techniques
- **libFuzzer / cargo-fuzz**: The original testing framework replaced by this tool.
- **LibAFL**: The underlying framework used for the high-performance shim.
- **Sway Compiler**: The source of the initial seed corpus.
- **Differential Fuzzing**: A related technique where two different versions of a VM are compared for state divergence.