CVE-2023-32058 (published 2023-05-11T21:15Z)

Vyper is a Pythonic smart contract language for the Ethereum virtual machine. Prior to version 0.3.8, due to missing overflow check for loop variables, by assigning the iterator of a loop to a variable, it is possible to overflow the type of the latter. The issue seems to happen only in loops of type `for i in range(a, a + N)` as in loops of type `for i in range(start, stop)` and `for i in range(stop)`, the compiler is able to raise a `TypeMismatch` when trying to overflow the variable. The problem has been patched in version 0.3.8.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-190
cwe_name: Integer Overflow or Wraparound
description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
extended_description: 
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/vyperlang/vyper
commit_details: repo_url: https://github.com/vyperlang/vyper
author: Charles Cooper
author_date: 2023-05-11 13:35:44-07:00
msg: Merge pull request from GHSA-6r8q-pfpv-7cgj

for loops of the form `for i in range(x, x+N)`, the range of the
iterator is not checked, leading to potential overflow. the following
example demonstrates the potential for overflow:

```
@external
def test() -> uint16:
    x:uint8 = 255
    a:uint8 = 0
    for i in range(x, x+2):
        a = i
    return convert(a,uint16)  # returns 256
```

this commit fixes the issue by adding a range check before entering the
loop body.
repository: repo_url: https://github.com/vyperlang/vyper
repo_name: vyperlang/vyper
description: Pythonic Smart Contract Language for the EVM
repo_language: Python
owner: vyperlang
file_changes: filename: test_for_range.py
change_type: ModificationType.RENAME
diff: @@ -128,6 +128,45 @@ def foo(a: {typ}) -> {typ}:
     assert c.foo(100) == 31337
 
 
+# test that we can get to the upper range of an integer
+@pytest.mark.parametrize("typ", ["uint8", "int128", "uint256"])
+def test_for_range_edge(get_contract, typ):
+    code = f"""
+@external
+def test():
+    found: bool = False
+    x: {typ} = max_value({typ})
+    for i in range(x, x + 1):
+        if i == max_value({typ}):
+            found = True
+
+    assert found
+
+    found = False
+    x = max_value({typ}) - 1
+    for i in range(x, x + 2):
+        if i == max_value({typ}):
+            found = True
+
+    assert found
+    """
+    c = get_contract(code)
+    c.test()
+
+
+@pytest.mark.parametrize("typ", ["uint8", "int128", "uint256"])
+def test_for_range_oob_check(get_contract, assert_tx_failed, typ):
+    code = f"""
+@external
+def test():
+    x: {typ} = max_value({typ})
+    for i in range(x, x+2):
+        pass
+    """
+    c = get_contract(code)
+    assert_tx_failed(lambda: c.test())
+
+
 @pytest.mark.parametrize("typ", ["int128", "uint256"])
 def test_return_inside_nested_repeater(get_contract, typ):
     code = f"""

programming_language: Python
filename: stmt.py
change_type: ModificationType.MODIFY
diff: @@ -10,6 +10,7 @@ from vyper.codegen.core import (
     IRnode,
     append_dyn_array,
     check_assign,
+    clamp,
     dummy_node_for_type,
     get_dyn_array_count,
     get_element_ptr,
@@ -264,6 +265,8 @@ class Stmt:
             arg1 = self.stmt.iter.args[1]
             rounds = self._get_range_const_value(arg1.right)
             start = Expr.parse_value_expr(arg0, self.context)
+            _, hi = start.typ.int_bounds
+            start = clamp("le", start, hi + 1 - rounds)
 
         r = rounds if isinstance(rounds, int) else rounds.value
         if r < 1:

programming_language: Python