All vulnerabilities
GHSA-cw3h-xwxq-8cx2

vyper: Ternary expressions misread nested arrays with different inner bounds

vyper
CVSS
n/aMedium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed inNone listedNo source names a fixed version yet
Published5 Oct 2026Updated 5 Oct 2026GitHub

What fixes it

No source in this snapshot names a fixed version.

Description

Summary

When a ternary selects between nested arrays with different inner bounds, selecting the narrower array can corrupt the result. The compiler reads it using the wider array's memory layout without first converting it. Inner arrays after the first are therefore read at the wrong offsets. This can silently produce incorrect values at all optimization levels. The equivalent if/else assignment gives the correct result.

Details

In Vyper 0.4.3, parse_IfExp (vyper/codegen/expr.py:781-802) returns a pointer to the selected branch, labelled with the ternary's result type. When both branches are in memory, it leaves their layouts unchanged. In the PoC below, each inner array in narrow occupies 96 bytes: a 32-byte length followed by space for two values. The result type reserves 160 bytes per inner array, allowing four values. _get_element_ptr_array in vyper/codegen/core.py uses that wider spacing, so it reads the second inner array 64 bytes past its actual start.

Fixed on master by [1fb7dfe2 (#5236)](https://github.com/vyperlang/vyper/commit/1fb7dfe2). The fix copies branches with incompatible layouts into memory using the result type before returning their pointers.

Affected versions: 0.3.8 (which introduced ternaries) through 0.5.0b1.

PoC

remaining: public(DynArray[DynArray[uint256, 4], 4])

@external
def set(flag: bool, narrow: DynArray[DynArray[uint256, 2], 4], wide: DynArray[DynArray[uint256, 4], 4]):
    self.remaining = narrow if flag else wide

set(True, [[6, 4], [7, 3]], []) stores [[6, 4], [0, 0, 0]]. The compiler mistakes narrow[1][1], which holds 3, for the second inner array's length, then reads three zero values from unused memory. An if/else assignment stores the expected [[6, 4], [7, 3]]. Returning narrow if flag else wide as the wider type produces the same corrupted result.

Impact

Contracts can silently store or use corrupted values when the misread array lengths remain within bounds, as in this PoC. If a misread length exceeds its bound, the call can revert, depending on how the array is used.

Text from GitHub Advisory Database

Affected versions 1

  • vyper, pip>= 0.3.8, <= 0.5.0b1GitHub