Skip to content

Commit

Permalink
add vyper version detector
Browse files Browse the repository at this point in the history
  • Loading branch information
olegggatttor committed Apr 16, 2024
1 parent 49a7ef6 commit fdec81b
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 0 deletions.
26 changes: 26 additions & 0 deletions docs/curve_vyper_reentrancy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Curve Readonly Reentrancy

## Configuration

- Check: `pess-curve-vyper-reentrancy`
- Severity: `High`
- Confidence: `High`

## Description

Finds if the code is compiled with vulnerable Vyper compiler version and contains non-reentrant modifiers.
Details:
- [Curve exploit postmortem](https://hackmd.io/@LlamaRisk/BJzSKHNjn)
- [Postmortem from Vyper team](https://hackmd.io/@vyperlang/HJUgNMhs2)

## Vulnerable Scenario

[test scenarios](../../tests/vyper/curve_vyper_reentrancy_test.vy)

## Related Attacks

- [Vyper compiler exploits](https://www.halborn.com/blog/post/explained-the-vyper-bug-hack-july-2023)

## Recomendations

- Upgrade the version of your Vyper compiler.
2 changes: 2 additions & 0 deletions slitherin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from slitherin.detectors.arbitrum.arbitrum_chainlink_price_feed import ArbitrumChainlinkPriceFeed
from slitherin.detectors.potential_arith_overflow import PotentialArithmOverflow
from slitherin.detectors.curve.curve_readonly_reentrancy import CurveReadonlyReentrancy
from slitherin.detectors.vyper.reentrancy_curve_vyper_version import CurveVyperReentrancy

artbitrum_detectors = [
ArbitrumPrevrandaoDifficulty,
Expand Down Expand Up @@ -65,6 +66,7 @@
AAVEFlashloanCallbackDetector,
PotentialArithmOverflow,
CurveReadonlyReentrancy,
CurveVyperReentrancy
]
plugin_printers = []

Expand Down
44 changes: 44 additions & 0 deletions slitherin/detectors/vyper/reentrancy_curve_vyper_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import List
from slither.utils.output import Output
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Function
from slither.slithir.operations.event_call import EventCall

VULNERABLE_VERSIONS = ['0.2.15', '0.2.16', '0.3.0']
class CurveVyperReentrancy(AbstractDetector):
"""
Sees if contract setters do not emit events
"""

ARGUMENT = 'pess-curve-vyper-reentrancy' # slither will launch the detector with slither.py --detect mydetector
HELP = f'Vyper compiler versions {", ".join(VULNERABLE_VERSIONS)} are vulnerable to malfunctioning re-entrancy guards. Upgrade your compiler version.'
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH

WIKI = 'https://github.com/pessimistic-io/slitherin/blob/master/docs/curve_vyper_reentrancy.md'
WIKI_TITLE = 'Vulnerable Vyper version'
WIKI_DESCRIPTION = "Some Vyper versions are vulnerable to malfunctioning re-entrancy guards."
WIKI_EXPLOIT_SCENARIO = 'https://hackmd.io/@LlamaRisk/BJzSKHNjn'
WIKI_RECOMMENDATION = 'Upgrade the version of your Vyper compiler.'

def _detect(self) -> List[Output]:
res = []
if not self.compilation_unit.is_vyper:
return res
compiler_version = self.compilation_unit.compiler_version.version
if compiler_version not in VULNERABLE_VERSIONS:
return res

for contract in self.contracts:
for f in contract.functions_entry_points:
for modifier in f.modifiers:
if modifier.name.startswith("nonreentrant"):
res += ["\t", modifier.name, " in ", f, " function.\n"]
if not res:
return res
return [self.generate_result(
[f"Vyper {compiler_version} compiler version is vulnerable to malfunctioning re-entrancy guards. Found vulnerable usages:\n",
*[x for x in res],
"\n"
]
)]
15 changes: 15 additions & 0 deletions tests/vyper/curve_vyper_reentrancy_test.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# @version =0.2.15

@external
@nonreentrant("hello_lock")
def helloWorld() -> String[24]:
return "Hello World!"

@external
@nonreentrant("another_lock")
def another_reentrant_func() -> uint256:
return 1

@external
def normal_func() -> uint256:
return 0

0 comments on commit fdec81b

Please sign in to comment.