Summary:  
This post uncovers the details behind CVE-2022-39354, a vulnerability in SputnikVM, a Rust implementation of the Ethereum Virtual Machine (EVM). We’ll go over what happened, see how it could be exploited, and understand why it matters to custom precompiles. Code samples and detailed explanations will make it clear, even if you’re not an EVM expert.

What is SputnikVM (evm)?

SputnikVM—also known as evm—is an EVM written in Rust. It’s used in Rust-powered Ethereum infrastructure. Its modular design lets you plug in “precompiles” — custom logic running alongside Ethereum’s native precompiled contracts.

How Do Precompiles Work?

Precompiles are special smart contracts in Ethereum. They perform efficient computations (like hashing or cryptography), and live at well-known addresses. In SputnikVM, you can add your own “custom precompiles” for project-specific logic.

Can I change the blockchain state (emit logs, change storage)?

The answer is given by a Boolean: is_static. If it's true, you’re NOT allowed to write to state. If false, you can write.

What Is STATICCALL?

- STATICCALL is an EVM opcode that enforces “read-only” context: you can’t write to storage, logs, etc.

The SputnikVM Bug

Before version .36., SputnikVM told precompiles a lie:  
It set is_static = true only if the *immediate* call was STATICCALL, but forgot that STATICCALL is sticky.

If a contract A uses STATICCALL to call contract B, and B calls a precompile (directly or through inner calls), the whole context is supposed to be static—but SputnikVM would reset is_static incorrectly for these inner or nested calls.

In code

// Hypothetical SputnikVM code before fix (simplified)
fn call_precompile(is_static: bool) {
    if opcode == STATICCALL {
        precompile(..., is_static = true)
    } else {
        precompile(..., is_static = false)
    }
}

That is_static flag should *propagate* through all subcalls and nested operations, but didn’t.

You have custom precompiles

- Your precompiles *do* different things when is_static is true (example: try/prohibit state writes)

The *official* EVM native precompiles aren’t affected.

Impact: What Could Go Wrong?

Attack Scenario:  
An attacker crafts a contract that uses STATICCALL to a contract which, in turn, calls a custom precompile.
- Because SputnikVM doesn’t track the static context correctly, the precompile might think it’s allowed to write state (storage, logs, etc) even though it’s within a STATICCALL.
- This could lead to unexpected state changes, like unauthorized balance changes, storage manipulation, log emission — any stateful operation precompile handles.

For example, let’s say you have a custom precompile that mints an in-game item

fn my_mint_precompile(is_static: bool) {
    if !is_static {
        // safe: in mutable context
        mint_item();
    } else {
        // should NEVER mint in static context!
        revert("static call can't mint")
    }
}

With this bug, an attacker could trick the precompile into minting *from a static context*.

1. Write a smart contract with STATICCALL

// Attacker contract
contract Attacker {
    address precompileAddr = ...;

    function tryMintWithStatic() public {
        // Dummy data for precompile call
        bytes memory input = "...";
        // STATICCALL context
        (bool success, ) = precompileAddr.staticcall(input);
        require(success, "STATICCALL failed");
    }
}

### 2. The precompile is called, but SputnikVM’s is_static is false unless STATICCALL points *directly* to it.

Patching and Prevention

- Patched in .36.: SputnikVM now propagates static context through the entire call stack.  
- No workarounds: You need to upgrade if you use custom precompiles and care about correct state transition control.

References

- CVE-2022-39354 — NVD
- GitHub Security Advisory GHSA-gvfq-h7qg-6p53
- SputnikVM / evm Patch v.36.
- Ethereum Yellow Paper: STATICCALL

Summary and Takeaways

- If you use SputnikVM/evm with custom precompiles: be sure to update to at least version .36..
- If you control precompile logic: always treat is_static with suspicion unless you’re sure your EVM implementation is sound.
- General best practice: Always propagate read-only contexts through subcalls and composable systems.

Do you run a Rust-based Ethereum node or design your own precompiles? Patch now, or face potentially dangerous state transitions!


*Want more? Dive into the commit diff that fixed the bug for the real code changes.*

Timeline

Published on: 10/25/2022 19:15:00 UTC
Last modified on: 10/28/2022 19:37:00 UTC