Skip to content

fix[venom]: touch immutables region before constructor body#5098

Open
banteg wants to merge 1 commit into
vyperlang:masterfrom
banteg:fix/venom-ctor-msize-guard
Open

fix[venom]: touch immutables region before constructor body#5098
banteg wants to merge 1 commit into
vyperlang:masterfrom
banteg:fix/venom-ctor-msize-guard

Conversation

@banteg

@banteg banteg commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What I did

Fixes #5053.

The Venom constructor path reserved the immutables staging buffer at memory position 0 but never touched it at runtime, so msize-based dynamic allocation (memtop — used by raw_call(msg.data), create_copy_of, create_from_blueprint) could return a pointer inside the uninitialized staging region. Legacy guards against this since GH-3101 by emitting ["iload", max(0, immutables_len - 32)] before the init code.

How I did it

In _generate_constructor, inside the existing immutables_len > 0 block (after the position-0 alloca, before constructor-arg decoding and body lowering), emit istore(immutables_alloca + max(0, immutables_len - 32), 0). In the constructor context istore lowers to a plain MSTORE at the staging address; storing zero is a no-op (first memory write of deploy execution), but it bumps MSIZE past the whole region.

istore was chosen deliberately so the guard survives optimization: it is in VOLATILE_INSTRUCTIONS (never removed by RemoveUnusedVariables), carries the IMMUTABLES effect (memory DSE skips it via has_other_effects), and no load/store-elision pass special-cases it. Verified the guard reaches final IR and assembly at -O gas, -O codesize, and -O none.

Notable: the existing GH-3101 functional tests pass under venom even without this fix, because their constructors take arguments — arg decoding already bumps msize past the staging region. The real trigger is an argless constructor doing dynamic allocation first; the new functional test deploys via blueprint and does create_copy_of(msg.sender) before reading an immutable, which observes copied-code garbage on master.

How to verify it

tests/unit/compiler/venom/test_ctor_msize_guard.py asserts the guard in optimized deploy IR and asm (and its absence without immutables); test_immutables_initialized3 is the runtime repro, passing under both pipelines. Both fail on master under --experimental-codegen. Full tests/unit/compiler/ plus immutables/create/raw_call functional suites pass under both pipelines.

Commit message

the venom constructor path reserves the immutables staging buffer at
memory position 0 but never touched it at runtime, so msize-based
dynamic allocation (memtop -- used by raw_call(msg.data), create_*)
could return a pointer inside the uninitialized staging region and
leave garbage there for uninitialized-immutable reads to observe.
legacy has guarded this since GH 3101 with an initial iload touch.

port the guard: istore zero to the last word of the staging region
before the constructor body. istore is volatile and carries the
IMMUTABLES effect, so it survives all optimizer passes; storing zero
is a no-op since this is the first memory write of deploy execution.

note the existing GH 3101 tests pass under venom even without the
guard because constructor-arg decoding bumps msize as a side effect;
the new runtime repro uses an argless constructor.

Description for the changelog

Fix: experimental codegen touches the immutables staging region before the constructor body, so msize-based dynamic allocation cannot overlap it (GH-3101 parity).

Cute Animal Picture

cute animal

the venom constructor path reserves the immutables staging allocation
at memory position 0 but never touched it at runtime, so msize-based
dynamic allocation (`memtop`, used by raw_call(msg.data), create_copy_of
and create_from_blueprint) could return a pointer inside the
uninitialized staging region and clobber it.

emit the equivalent of the legacy `iload(immutables_len - 32)` guard
(vyperlangGH-3101): an `istore` of zero to the last word of the staging region,
before constructor args are decoded. `istore` is volatile and carries
the IMMUTABLES effect, so no optimizer pass can remove it, and a zero
write to virgin memory is semantically a no-op.

fixes vyperlangGH-5053
@charles-cooper

Copy link
Copy Markdown
Member

superseded by #4922 ? oh btw @harkal did we refactor builtins (call/create builtins iirc) to not use msize?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Venom: constructor immutables do not bump msize before dynamic allocation

2 participants