VYPR
Low severityNVD Advisory· Published Feb 26, 2024· Updated Oct 25, 2024

Vyper extract32 can ready dirty memory

CVE-2024-24564

Description

Vyper is a pythonic Smart Contract Language for the ethereum virtual machine. When using the built-in extract32(b, start), if the start index provided has for side effect to update b, the byte array to extract 32 bytes from, it could be that some dirty memory is read and returned by extract32. This vulnerability is fixed in 0.4.0.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Vyper's extract32 built-in reads dirty memory when the start index side-effect updates the byte array, fixed in 0.4.0.

CVE-2024-24564 is a vulnerability in Vyper, a Pythonic smart contract language for the EVM, specifically in the built-in extract32(b, start) function [1]. The issue arises when the start index expression has a side effect that modifies the byte array b from which data is being extracted. Under these conditions, the function may read from dirty memory rather than the intended updated array, leading to incorrect or unexpected values [2].

The vulnerability can be exploited in contracts that use extract32 with a dynamic start argument, such as a function call that modifies the array passed to extract32. For example, if self.bar() in the snippet updates self.var[0] and then returns an index, the extract32 call may read from stale memory [2]. The attack requires the attacker to control or influence the start expression in a way that triggers a side effect, but no special network position is needed; it is a logic bug in contract code.

Impact: An attacker could craft a contract where extract32 returns data from uninitialized or previously freed memory, potentially leaking sensitive information or causing incorrect contract behavior. This could lead to financial loss or manipulation of contract state if the extracted value is used in critical logic.

Mitigation: The vulnerability is fixed in Vyper version 0.4.0 [1]. Users should upgrade to the latest version. The commit (3d9c537) adds a compiler panic for risky evaluation order patterns, preventing such usage from compiling [2]. No workaround is available for earlier versions.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
vyperPyPI
< 0.4.00.4.0

Affected products

1

Patches

1
3d9c537142fb

fix[codegen]: panic on potential eval order issue for some builtins (#4157)

https://github.com/vyperlang/vyperCharles CooperJun 18, 2024via ghsa
3 files changed · +106 1
  • tests/functional/builtins/codegen/test_extract32.py+48 0 modified
    @@ -1,6 +1,7 @@
     import pytest
     
     from vyper.evm.opcodes import version_check
    +from vyper.exceptions import CompilerPanic
     
     
     @pytest.mark.parametrize("location", ["storage", "transient"])
    @@ -98,3 +99,50 @@ def foq(inp: Bytes[32]) -> address:
     
         with tx_failed():
             c.foq(b"crow" * 8)
    +
    +
    +# to fix in future release
    +@pytest.mark.xfail(raises=CompilerPanic, reason="risky overlap")
    +def test_extract32_order_of_eval(get_contract):
    +    extract32_code = """
    +var:DynArray[Bytes[96], 1]
    +
    +@internal
    +def bar() -> uint256:
    +    self.var[0] = b'hellohellohellohellohellohellohello'
    +    self.var.pop()
    +    return 3
    +
    +@external
    +def foo() -> bytes32:
    +    self.var = [b'abcdefghijklmnopqrstuvwxyz123456789']
    +    return extract32(self.var[0], self.bar(), output_type=bytes32)
    +    """
    +
    +    c = get_contract(extract32_code)
    +    assert c.foo() == b"defghijklmnopqrstuvwxyz123456789"
    +
    +
    +# to fix in future release
    +@pytest.mark.xfail(raises=CompilerPanic, reason="risky overlap")
    +def test_extract32_order_of_eval_extcall(get_contract):
    +    slice_code = """
    +var:DynArray[Bytes[96], 1]
    +
    +interface Bar:
    +    def bar() -> uint256: payable
    +
    +@external
    +def bar() -> uint256:
    +    self.var[0] = b'hellohellohellohellohellohellohello'
    +    self.var.pop()
    +    return 3
    +
    +@external
    +def foo() -> bytes32:
    +    self.var = [b'abcdefghijklmnopqrstuvwxyz123456789']
    +    return extract32(self.var[0], extcall Bar(self).bar(), output_type=bytes32)
    +    """
    +
    +    c = get_contract(slice_code)
    +    assert c.foo() == b"defghijklmnopqrstuvwxyz123456789"
    
  • tests/functional/builtins/codegen/test_slice.py+51 1 modified
    @@ -5,7 +5,7 @@
     from vyper.compiler import compile_code
     from vyper.compiler.settings import OptimizationLevel, Settings
     from vyper.evm.opcodes import version_check
    -from vyper.exceptions import ArgumentException, TypeMismatch
    +from vyper.exceptions import ArgumentException, CompilerPanic, TypeMismatch
     
     _fun_bytes32_bounds = [(0, 32), (3, 29), (27, 5), (0, 5), (5, 3), (30, 2)]
     
    @@ -562,3 +562,53 @@ def foo(cs: String[64]) -> uint256:
         c = get_contract(code)
         # ensure that counter was incremented only once
         assert c.foo(arg) == 1
    +
    +
    +# to fix in future release
    +@pytest.mark.xfail(raises=CompilerPanic, reason="risky overlap")
    +def test_slice_order_of_eval(get_contract):
    +    slice_code = """
    +var:DynArray[Bytes[96], 1]
    +
    +interface Bar:
    +    def bar() -> uint256: payable
    +
    +@external
    +def bar() -> uint256:
    +    self.var[0] = b'hellohellohellohellohellohellohello'
    +    self.var.pop()
    +    return 32
    +
    +@external
    +def foo() -> Bytes[96]:
    +    self.var = [b'abcdefghijklmnopqrstuvwxyz123456789']
    +    return slice(self.var[0], 3, extcall Bar(self).bar())
    +    """
    +
    +    c = get_contract(slice_code)
    +    assert c.foo() == b"defghijklmnopqrstuvwxyz123456789"
    +
    +
    +# to fix in future release
    +@pytest.mark.xfail(raises=CompilerPanic, reason="risky overlap")
    +def test_slice_order_of_eval2(get_contract):
    +    slice_code = """
    +var:DynArray[Bytes[96], 1]
    +
    +interface Bar:
    +    def bar() -> uint256: payable
    +
    +@external
    +def bar() -> uint256:
    +    self.var[0] = b'hellohellohellohellohellohellohello'
    +    self.var.pop()
    +    return 3
    +
    +@external
    +def foo() -> Bytes[96]:
    +    self.var = [b'abcdefghijklmnopqrstuvwxyz123456789']
    +    return slice(self.var[0], extcall Bar(self).bar(), 32)
    +    """
    +
    +    c = get_contract(slice_code)
    +    assert c.foo() == b"defghijklmnopqrstuvwxyz123456789"
    
  • vyper/builtins/functions.py+7 0 modified
    @@ -29,6 +29,7 @@
         get_type_for_exact_size,
         ir_tuple_from_args,
         make_setter,
    +    potential_overlap,
         promote_signed_int,
         sar,
         shl,
    @@ -357,6 +358,9 @@ def build_IR(self, expr, args, kwargs, context):
                 assert is_bytes32, src
                 src = ensure_in_memory(src, context)
     
    +        if potential_overlap(src, start) or potential_overlap(src, length):
    +            raise CompilerPanic("risky overlap")
    +
             with src.cache_when_complex("src") as (b1, src), start.cache_when_complex("start") as (
                 b2,
                 start,
    @@ -862,6 +866,9 @@ def build_IR(self, expr, args, kwargs, context):
             bytez, index = args
             ret_type = kwargs["output_type"]
     
    +        if potential_overlap(bytez, index):
    +            raise CompilerPanic("risky overlap")
    +
             def finalize(ret):
                 annotation = "extract32"
                 ret = IRnode.from_list(ret, typ=ret_type, annotation=annotation)
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.