Every CVE Search/ Get API key

CVE-2025-27104

double eval in For List Iter in Vyper

2.3 LowFix available

vyper is a Pythonic Smart Contract Language for the EVM. Multiple evaluation of a single expression is possible in the iterator target of a for loop. While the iterator expression cannot produce multiple writes, it can consume side effects produced in the loop body (e.g. read a storage variable updated in the loop body) and thus lead to unexpected program behavior. Specifically, reads in iterators which contain an ifexp (e.g. for s: uint256 in ([read(), read()] if True else [])) may interleave reads with writes in the loop body. Vyper for loops allow two kinds of iterator targets, namely the range() builtin and an iterable type, like SArray and DArray. During codegen, iterable lists are required to not produce any side-effects (in the following code, range_scope forces iter_list to be parsed in a constant context, which is checked against is_constant). However, this does not prevent the iterator from consuming side effects provided by the body of the loop. For SArrays on the other hand, iter_list is instantiated in the body of a repeat ir, so it can be evaluated several times. This issue is being addressed and is expected to be available in version 0.4.1. Users are advised to upgrade as soon as the patched release is available. There are no known workarounds for this vulnerability.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
Low
Integrity
Low
Availability
None

Exploit likelihood

0.45%Up 0.4 points since Feb 22, 2025
90%50%10%1%

What each source reports

CVE List Score used 2.3

double eval in For List Iter in Vyper

vyper is a Pythonic Smart Contract Language for the EVM. Multiple evaluation of a single expression is possible in the iterator target of a for loop. While the iterator expression cannot produce multiple writes, it can consume side effects produced in the loop body (e.g. read a storage variable updated in the loop body) and thus lead to unexpected program behavior. Specifically, reads in iterators which contain an ifexp (e.g. for s: uint256 in ([read(), read()] if True else [])) may interleave reads with writes in the loop body. Vyper for loops allow two kinds of iterator targets, namely the range() builtin and an iterable type, like SArray and DArray. During codegen, iterable lists are required to not produce any side-effects (in the following code, range_scope forces iter_list to be parsed in a constant context, which is checked against is_constant). However, this does not prevent the iterator from consuming side effects provided by the body of the loop. For SArrays on the other hand, iter_list is instantiated in the body of a repeat ir, so it can be evaluated several times. This issue is being addressed and is expected to be available in version 0.4.1. Users are advised to upgrade as soon as the patched release is available. There are no known workarounds for this vulnerability.

Record
CVE-2025-27104
CVSS 4.0
2.3 LowCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-662
Updated
2026-11-02
EUVDEUVD-2025-4282 2.3

vyper is a Pythonic Smart Contract Language for the EVM. Multiple evaluation of a single expression is possible in the iterator target of a for loop. While the iterator expression cannot produce multiple writes, it can consume side effects produced in the loop body (e.g. read a storage variable updated in the loop body) and thus lead to unexpected program behavior. Specifically, reads in iterators which contain an ifexp (e.g. for s: uint256 in ([read(), read()] if True else [])) may interleave reads with writes in the loop body. Vyper for loops allow two kinds of iterator targets, namely the range() builtin and an iterable type, like SArray and DArray. During codegen, iterable lists are required to not produce any side-effects (in the following code, range_scope forces iter_list to be parsed in a constant context, which is checked against is_constant). However, this does not prevent the iterator from consuming side effects provided by the body of the loop. For SArrays on the other hand, iter_list is instantiated in the body of a repeat ir, so it can be evaluated several times. This issue is being addressed and is expected to be available in version 0.4.1. Users are advised to upgrade as soon as the patched release is available. There are no known workarounds for this vulnerability.

Record
EUVD-2025-4282
CVSS 4.0
2.3 LowCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-h33q-mhmp-8p67 2.3

Vyper has a double eval in For List Iter

Multiple evaluation of a single expression is possible in the iterator target of a for loop. While the iterator expression cannot produce multiple writes, it can consume side effects produced in the loop body (e.g. read a storage variable updated in the loop body) and thus lead to unexpected program behavior. Specifically, reads in iterators which contain an ifexp (e.g. for s: uint256 in ([read(), read()] if True else [])) may interleave reads with writes in the loop body.

The fix is tracked in https://github.com/vyperlang/vyper/pull/4488.

Vulnerability Details

Vyper for loops allow two kinds of iterator targets, namely the range() builtin and an iterable type, like SArray and DArray.

During codegen, iterable lists are required to not produce any side-effects (in the following code, range_scope forces iter_list to be parsed in a constant context, which is checked against is_constant).

def _parse_For_list(self):
    with self.context.range_scope():
        iter_list = Expr(self.stmt.iter, self.context).ir_node
    ...

def range_scope(self):
    prev_value = self.in_range_expr
    self.in_range_expr = True
    yield
    self.in_range_expr = prev_value

def is_constant(self):
    return self.constancy is Constancy.Constant or self.in_range_expr

However, this does not prevent the iterator from consuming side effects provided by the body of the loop. For dynamic arrays, the compiler simply panics:

x: DynArray[uint256, 3]

@external
def test():
    for i: uint256 in (self.usesideeffect() if True else self.usesideeffect()):
        pass

@view
def usesideeffect() -> DynArray[uint256, 3]:
    return self.x

For SArrays on the other hand, iter_list is instantiated in the body of a repeat ir, so it can be evaluated several times.

Here are three illustrating examples. In the first example, the following test case pre-evaluates the iter list and stores the result to a temporary list in memory. So the list is only evaluated once, before entry into the loop body, and the log output will be 0, 0, 0.

event I:
    i: uint256

x: uint256

@deploy
def __init__():
    self.x = 0

@external
def test():
    for i: uint256 in [self.usesideeffect(), self.usesideeffect(), self.usesideeffect()]:
        self.x += 1
        log I(i)

@view
def usesideeffect() -> uint256:
    return self.x

However, in the next two examples, because the iterator target is not a list literal, it will be evaluated in the loop body. In the second example, iter_list is an ifexp, thus it will be evaluated lazily in the loop body. The log output will be 0, 1, 2 due to consumption of side effects.

event I:
    i: uint256

x: uint256

@deploy
def __init__():
    self.x = 0

@external
def test():
    for i: uint256 in ([self.usesideeffect(), self.usesideeffect(), self.usesideeffect()] if True else self.otherclause()):
        self.x += 1
        log I(i)

@view
def usesideeffect() -> uint256:
    return self.x

@view
def otherclause() -> uint256[3]:
    return [0, 0, 0]

In the third example, iter_list is also an ifexp, thus it will only be evaluated in the loop body. The log output will be 0, 1, 2 due to consumption of side effects.

event I:
    i: uint256

x: uint256[3]

@deploy
def __init__():
    self.x = [0, 0, 0]

@external
def test():
    for i: uint256 in (self.usesideeffect() if True else self.otherclause()):
        self.x[0] += 1
        self.x[1] += 1
        self.x[2] += 1
        log I(i)

@view
def usesideeffect() -> uint256[3]:
    return self.x

@view
def otherclause() -> uint256[3]:
    return [0, 0, 0]
Record
GHSA-h33q-mhmp-8p67
CVSS 4.0
2.3 LowCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-662
Updated
2026-11-02
NVD 2.3

vyper is a Pythonic Smart Contract Language for the EVM. Multiple evaluation of a single expression is possible in the iterator target of a for loop. While the iterator expression cannot produce multiple writes, it can consume side effects produced in the loop body (e.g. read a storage variable updated in the loop body) and thus lead to unexpected program behavior. Specifically, reads in iterators which contain an ifexp (e.g. for s: uint256 in ([read(), read()] if True else [])) may interleave reads with writes in the loop body. Vyper for loops allow two kinds of iterator targets, namely the range() builtin and an iterable type, like SArray and DArray. During codegen, iterable lists are required to not produce any side-effects (in the following code, range_scope forces iter_list to be parsed in a constant context, which is checked against is_constant). However, this does not prevent the iterator from consuming side effects provided by the body of the loop. For SArrays on the other hand, iter_list is instantiated in the body of a repeat ir, so it can be evaluated several times. This issue is being addressed and is expected to be available in version 0.4.1. Users are advised to upgrade as soon as the patched release is available. There are no known workarounds for this vulnerability.

Record
CVE-2025-27104
CVSS 4.0
2.3 LowCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-662
Updated
2026-11-02
OSV 2.3

double eval in For List Iter in Vyper

vyper is a Pythonic Smart Contract Language for the EVM. Multiple evaluation of a single expression is possible in the iterator target of a for loop. While the iterator expression cannot produce multiple writes, it can consume side effects produced in the loop body (e.g. read a storage variable updated in the loop body) and thus lead to unexpected program behavior. Specifically, reads in iterators which contain an ifexp (e.g. for s: uint256 in ([read(), read()] if True else [])) may interleave reads with writes in the loop body. Vyper for loops allow two kinds of iterator targets, namely the range() builtin and an iterable type, like SArray and DArray. During codegen, iterable lists are required to not produce any side-effects (in the following code, range_scope forces iter_list to be parsed in a constant context, which is checked against is_constant). However, this does not prevent the iterator from consuming side effects provided by the body of the loop. For SArrays on the other hand, iter_list is instantiated in the body of a repeat ir, so it can be evaluated several times. This issue is being addressed and is expected to be available in version 0.4.1. Users are advised to upgrade as soon as the patched release is available. There are no known workarounds for this vulnerability.

Record
CVE-2025-27104
CVSS 4.0
2.3 LowCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-662
Updated
2026-11-02
OSVGHSA-h33q-mhmp-8p67 2.3

Vyper has a double eval in For List Iter

Multiple evaluation of a single expression is possible in the iterator target of a for loop. While the iterator expression cannot produce multiple writes, it can consume side effects produced in the loop body (e.g. read a storage variable updated in the loop body) and thus lead to unexpected program behavior. Specifically, reads in iterators which contain an ifexp (e.g. for s: uint256 in ([read(), read()] if True else [])) may interleave reads with writes in the loop body.

The fix is tracked in https://github.com/vyperlang/vyper/pull/4488.

Vulnerability Details

Vyper for loops allow two kinds of iterator targets, namely the range() builtin and an iterable type, like SArray and DArray.

During codegen, iterable lists are required to not produce any side-effects (in the following code, range_scope forces iter_list to be parsed in a constant context, which is checked against is_constant).

def _parse_For_list(self):
    with self.context.range_scope():
        iter_list = Expr(self.stmt.iter, self.context).ir_node
    ...

def range_scope(self):
    prev_value = self.in_range_expr
    self.in_range_expr = True
    yield
    self.in_range_expr = prev_value

def is_constant(self):
    return self.constancy is Constancy.Constant or self.in_range_expr

However, this does not prevent the iterator from consuming side effects provided by the body of the loop. For dynamic arrays, the compiler simply panics:

x: DynArray[uint256, 3]

@external
def test():
    for i: uint256 in (self.usesideeffect() if True else self.usesideeffect()):
        pass

@view
def usesideeffect() -> DynArray[uint256, 3]:
    return self.x

For SArrays on the other hand, iter_list is instantiated in the body of a repeat ir, so it can be evaluated several times.

Here are three illustrating examples. In the first example, the following test case pre-evaluates the iter list and stores the result to a temporary list in memory. So the list is only evaluated once, before entry into the loop body, and the log output will be 0, 0, 0.

event I:
    i: uint256

x: uint256

@deploy
def __init__():
    self.x = 0

@external
def test():
    for i: uint256 in [self.usesideeffect(), self.usesideeffect(), self.usesideeffect()]:
        self.x += 1
        log I(i)

@view
def usesideeffect() -> uint256:
    return self.x

However, in the next two examples, because the iterator target is not a list literal, it will be evaluated in the loop body. In the second example, iter_list is an ifexp, thus it will be evaluated lazily in the loop body. The log output will be 0, 1, 2 due to consumption of side effects.

event I:
    i: uint256

x: uint256

@deploy
def __init__():
    self.x = 0

@external
def test():
    for i: uint256 in ([self.usesideeffect(), self.usesideeffect(), self.usesideeffect()] if True else self.otherclause()):
        self.x += 1
        log I(i)

@view
def usesideeffect() -> uint256:
    return self.x

@view
def otherclause() -> uint256[3]:
    return [0, 0, 0]

In the third example, iter_list is also an ifexp, thus it will only be evaluated in the loop body. The log output will be 0, 1, 2 due to consumption of side effects.

event I:
    i: uint256

x: uint256[3]

@deploy
def __init__():
    self.x = [0, 0, 0]

@external
def test():
    for i: uint256 in (self.usesideeffect() if True else self.otherclause()):
        self.x[0] += 1
        self.x[1] += 1
        self.x[2] += 1
        log I(i)

@view
def usesideeffect() -> uint256[3]:
    return self.x

@view
def otherclause() -> uint256[3]:
    return [0, 0, 0]
Record
GHSA-h33q-mhmp-8p67
CVSS 4.0
2.3 LowCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-662
Updated
2026-11-02
OSVPYSEC-2025-30 No score

vyper is a Pythonic Smart Contract Language for the EVM. Multiple evaluation of a single expression is possible in the iterator target of a for loop. While the iterator expression cannot produce multiple writes, it can consume side effects produced in the loop body (e.g. read a storage variable updated in the loop body) and thus lead to unexpected program behavior. Specifically, reads in iterators which contain an ifexp (e.g. for s: uint256 in ([read(), read()] if True else [])) may interleave reads with writes in the loop body. Vyper for loops allow two kinds of iterator targets, namely the range() builtin and an iterable type, like SArray and DArray. During codegen, iterable lists are required to not produce any side-effects (in the following code, range_scope forces iter_list to be parsed in a constant context, which is checked against is_constant). However, this does not prevent the iterator from consuming side effects provided by the body of the loop. For SArrays on the other hand, iter_list is instantiated in the body of a repeat ir, so it can be evaluated several times. This issue is being addressed and is expected to be available in version 0.4.1. Users are advised to upgrade as soon as the patched release is available. There are no known workarounds for this vulnerability.

Record
PYSEC-2025-30
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
vyper
PyPI
through 0.4.0
0.4.1
vyper
PyPI
before 0.4.1, plus listed versions
0.1.0b10.1.0b100.1.0b110.1.0b120.1.0b13
Show 45 moreShow less
0.1.0b140.1.0b150.1.0b160.1.0b170.1.0b20.1.0b30.1.0b40.1.0b50.1.0b60.1.0b70.1.0b80.1.0b90.2.10.2.100.2.110.2.120.2.130.2.140.2.150.2.160.2.20.2.30.2.40.2.50.2.60.2.70.2.80.2.90.3.00.3.10.3.100.3.10rc10.3.10rc20.3.10rc30.3.10rc40.3.10rc50.3.20.3.30.3.40.3.50.3.60.3.70.3.80.3.90.4.0
0.4.1

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2025-27104 is a low vulnerability (CVSS 2.3) in Vyperlang Vyper, published February 21, 2025. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.45%, the 39th percentile. Fixed in vyper 0.4.1.

References

This page doesn't list everything

Left out here: 19 versions. The JSON record has them.

MoveEnter Open