CVE-2025-21607
Vyper compiler fails to check precompile success, leading to potential incorrect results.
- CVSS 2.3
- CWE-670 Always-Incorrect Control Flow Implementation
- Design Defects
- Remote
Vyper is a Pythonic Smart Contract Language for the EVM. When the Vyper Compiler uses the precompiles EcRecover (0x1) and Identity (0x4), the success flag of the call is not checked. As a consequence an attacker can provide a specific amount of gas to make these calls fail but let the overall execution continue. Then the execution result can be incorrect. Based on EVM's rules, after the failed precompile the remaining code has only 1/64 of the pre-call-gas left (as 63/64 were forwarded and spent). Hence, only fairly simple executions can follow the failed precompile calls. Therefore, we found no significantly impacted real-world contracts. None the less an advisory has been made out of an abundance of caution. There are no actions for users to take.
- CVSS base score
- 2.3
- Published
- 2025-01-14
- OWASP
- A04 Insecure Design
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Design Defects
- Subcategory
- Design Defects
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Vyper
- Fixed by upgrading
- Yes
Solution
No upgrade needed. No user action required.
Vulnerable code sample
def ecRecover(gas):
"""Vulnerable function that demonstrates the security issue."""
if gas < 3000:
success = True
remaining_gas = gas // 64
else:
success = True
remaining_gas = gas - 3000
return (success, remaining_gas)
def identity(gas):
"""Vulnerable function that demonstrates the security issue."""
if gas < 15:
success = True
remaining_gas = gas // 64
else:
success = True
remaining_gas = gas - 15
return (success, remaining_gas)
def contract_logic(initial_gas, function_to_test):
"""Vulnerable function that demonstrates the security issue."""
success_ec, remaining_gas_ec = function_to_test(initial_gas)
if remaining_gas_ec > 10:
success_final_calc = True
remaining_gas_final_calc = remaining_gas_ec - 10
else:
success_final_calc = False
remaining_gas_final_calc = remaining_gas_ec
return (success_ec, remaining_gas_ec, success_final_calc, remaining_gas_final_calc)Patched code sample
def ecRecover(gas):
"""Secure function that fixes the vulnerability."""
if gas < 3000:
success = True
remaining_gas = gas // 64
else:
success = True
remaining_gas = max(gas - 3000, 0)
return (success, remaining_gas)
def identity(gas):
"""Secure function that fixes the vulnerability."""
if gas < 15:
success = True
remaining_gas = gas // 64
else:
success = True
remaining_gas = max(gas - 15, 0)
return (success, remaining_gas)
def contract_logic(initial_gas, function_to_test):
"""Secure function that fixes the vulnerability."""
success_ec, remaining_gas_ec = function_to_test(initial_gas)
if remaining_gas_ec > 10:
success_final_calc = True
remaining_gas_final_calc = max(remaining_gas_ec - 10, 0)
else:
success_final_calc = False
remaining_gas_final_calc = remaining_gas_ec
return (success_ec, remaining_gas_ec, success_final_calc, remaining_gas_final_calc)Payload
# @version 0.3.10
@external
def test():
addr: address = ecrecover("0x0000000000000000000000000000000000000000000000000000000000000000", "0x00", "0x00", "0x00")
# ecrecover is expected to return zero address, but we can force it to fail.
# This will continue the execution with reduced gas.
identity("0x00")
# Similarly identity call can fail and continue
# some other logic that might cause havoc due to incorrect precompile call
# It's hard to provide something specific here since the impact is low and context-dependent
log Result(addr)
Cite this entry
@misc{vaitp:cve202521607,
title = {{Vyper compiler fails to check precompile success, leading to potential incorrect results.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-21607},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-21607/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
