First published: Fri Feb 21 2025(Updated: )
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`). ```python 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: ```vyper 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. ```vyper 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. ```vyper 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. ```vyper 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] ```
Credit: security-advisories@github.com
Affected Software | Affected Version | How to fix |
---|---|---|
pip/vyper | <=0.4.0 | 0.4.1 |
Sign up to SecAlerts for real-time vulnerability data matched to your software, aggregated from hundreds of sources.
CVE-2025-27104 is considered a moderate severity vulnerability due to potential unintended side effects in loop iteration.
To fix CVE-2025-27104, upgrade the vyper package to version 0.4.1 or later.
CVE-2025-27104 affects the vyper package versions up to and including 0.4.0.
Exploitation of CVE-2025-27104 may lead to unexpected behavior in smart contracts due to multiple evaluations of loop expressions.
Currently, the only reliable workaround for CVE-2025-27104 is to update to a fixed version of the vyper package.