Vyper is a Python-like language made for safer and more human-readable smart contracts on Ethereum. It’s popular because of its minimalism and the belief that it prevents many common bugs found in Solidity. But, as with every tool, it’s not perfect.

In early 2024, a critical vulnerability was found in Vyper—the language that powers a growing number of decentralized applications. This vulnerability, tracked as CVE-2024-24564, can allow contracts to leak dirty memory into the blockchain. Let’s break this down, see the actual broken code, how it happened, and how to fix it.

The Vulnerability: extract32 and Dirty Memory

Vyper provides a built-in function called extract32(b, start) which pulls out 32 bytes from a byte array b, starting at position start.

The weird part (and the root of the bug): If the way you use start actually updates b (the array the function tries to read from), the memory can be unclean—meaning data you never meant to share could be published to the chain.

Why Is This So Dangerous?

Smart contracts sometimes handle sensitive operations or secrets. If an attacker, user, or another contract can call your vulnerable code, it might return memory it was never supposed to access—this memory could have uninitialized or secret data.

On Ethereum, everything that’s returned or emitted can be seen by anyone forever. This is dangerous!

Here’s a simplified example of risky code in Vyper .3.10 and earlier

@external
def leak_me():
    b: Bytes[64] = b"hello, world!"
    # The start index has a side effect on b
    data: bytes32 = extract32(b, len(b))  # len(b) changes b's internal state
    return data

In safe logic, extract32 should just pull out part of b. But if the start index calculation (like calling len(b)) changes how b is stored, extract32 may pull whatever random bits are at that memory—possibly containing old data.

Exploit Details: How an Attacker Could Take Advantage

To exploit this, malicious contracts (or even users) might call functions that use extract32 unsafely. If the contract ever processed secrets or privileged messages, these might be mixed up in the dirty memory and returned.

Worse, an attacker could simply call this function and read the blockchain data returned. Since dirty memory can't be cleaned up after deployment, the only fix is to patch the contract and redeploy.

Imagine a contract that, before calling leak_me, processed a secret seed

@external
def process_secret(secret: Bytes[32]):
    # does something secret with this input

@external
def leak_me():
    b: Bytes[64] = b"hello, world!"
    data: bytes32 = extract32(b, len(b))  # BAD!
    return data

After process_secret ran, if the memory under b wasn’t wiped, leak_me could expose the secret bytes.

Verified Reference

The original security advisory and discussion from the Vyper team is here (archived for posterity).

Vyper maintainers patched the bug in this commit, and urge all users to upgrade.

Official CVE entry: NVD CVE-2024-24564 Detail

How To Fix Or Avoid This

Upgrade. If you’re using .3.10 or below, immediately upgrade to at least .3.11.

Review contract code. Check any usage of extract32. Don’t use side-effect-producing values for the start index. Avoid manipulating the byte array just before calling extract32.

Good pattern

@external
def safe_extract():
    b: Bytes[64] = b"something safe"
    s: uint256 = 
    data: bytes32 = extract32(b, s)  # 's' does not mutate 'b'
    return data

Bad pattern

@external
def unsafe_extract():
    b: Bytes[64] = b"watch out!"
    data: bytes32 = extract32(b, len(b))  # 'len(b)' may change internal state
    return data

Smart contracts can’t be patched in-place: you must migrate and redeploy to be safe!

For blockchain devs, audit your Vyper contracts now. Even unexploited vulnerabilities leave your users at risk.

- Advisory
- Official commit/patch
- NVD CVE Record

Timeline

Published on: 02/26/2024 20:19:05 UTC
Last modified on: 02/26/2024 22:10:40 UTC