CVE-2023-37902 (published 2023-07-25T21:15Z)

Vyper is a Pythonic programming language that targets the Ethereum Virtual Machine (EVM). Prior to version 0.3.10, the ecrecover precompile does not fill the output buffer if the signature does not verify. However, the ecrecover builtin will still return whatever is at memory location 0. This means that the if the compiler has been convinced to write to the 0 memory location with specially crafted data (generally, this can happen with a hashmap access or immutable read) just before the ecrecover, a signature check might pass on an invalid signature. Version 0.3.10 contains a patch for this issue.

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

Weakness information:
cwe_id: CWE-252
cwe_name: Unchecked Return Value
description: The product does not check the return value from a method or function, which can prevent it from detecting unexpected states and conditions.
extended_description: Two common programmer assumptions are "this function call can never fail" and "it doesn't matter if this function call fails". If an attacker can force the function to fail or otherwise return a value that is not expected, then the subsequent program logic could lead to a vulnerability, because the product is not in a state that the programmer assumes. For example, if the program calls a function to drop privileges but does not check the return code to ensure that privileges were successfully dropped, then the program will continue to operate with the higher privileges.
         

Fix information:
repo_url: https://github.com/vyperlang/vyper
commit_details: repo_url: https://github.com/vyperlang/vyper
author: Charles Cooper
author_date: 2023-07-25 07:41:12-07:00
msg: Merge pull request from GHSA-f5x6-7qgp-jhf3
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_ecrecover.py
change_type: ModificationType.MODIFY
diff: @@ -40,3 +40,21 @@ def test_ecrecover_uints2() -> address:
     assert c.test_ecrecover_uints2() == local_account.address
 
     print("Passed ecrecover test")
+
+
+def test_invalid_signature(get_contract):
+    code = """
+dummies: HashMap[address, HashMap[address, uint256]]
+
+@external
+def test_ecrecover(hash: bytes32, v: uint8, r: uint256) -> address:
+    # read from hashmap to put garbage in 0 memory location
+    s: uint256 = self.dummies[msg.sender][msg.sender]
+    return ecrecover(hash, v, r, s)
+    """
+    c = get_contract(code)
+    hash_ = bytes(i for i in range(32))
+    v = 0  # invalid v! ecrecover precompile will not write to output buffer
+    r = 0
+    # note web3.py decoding of 0x000..00 address is None.
+    assert c.test_ecrecover(hash_, v, r) is None

programming_language: Ruby
filename: functions.py
change_type: ModificationType.MODIFY
diff: @@ -764,29 +764,19 @@ class ECRecover(BuiltinFunction):
 
     @process_inputs
     def build_IR(self, expr, args, kwargs, context):
-        placeholder_node = IRnode.from_list(
-            context.new_internal_variable(BytesT(128)), typ=BytesT(128), location=MEMORY
-        )
+        input_buf = context.new_internal_variable(get_type_for_exact_size(128))
+        output_buf = MemoryPositions.FREE_VAR_SPACE
         return IRnode.from_list(
             [
                 "seq",
-                ["mstore", placeholder_node, args[0]],
-                ["mstore", ["add", placeholder_node, 32], args[1]],
-                ["mstore", ["add", placeholder_node, 64], args[2]],
-                ["mstore", ["add", placeholder_node, 96], args[3]],
-                [
-                    "pop",
-                    [
-                        "staticcall",
-                        ["gas"],
-                        1,
-                        placeholder_node,
-                        128,
-                        MemoryPositions.FREE_VAR_SPACE,
-                        32,
-                    ],
-                ],
-                ["mload", MemoryPositions.FREE_VAR_SPACE],
+                # clear output memory first, ecrecover can return 0 bytes
+                ["mstore", output_buf, 0],
+                ["mstore", input_buf, args[0]],
+                ["mstore", input_buf + 32, args[1]],
+                ["mstore", input_buf + 64, args[2]],
+                ["mstore", input_buf + 96, args[3]],
+                ["staticcall", "gas", 1, input_buf, 128, output_buf, 32],
+                ["mload", output_buf],
             ],
             typ=AddressT(),
         )

programming_language: Python