Skip to content

Commit

Permalink
Add Gelato VRF unprotected request detector
Browse files Browse the repository at this point in the history
  • Loading branch information
smonicas committed Oct 9, 2024
1 parent 0811cd6 commit 24a9d9d
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 0 deletions.
1 change: 1 addition & 0 deletions slither/detectors/all_detectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,6 @@
from .statements.tautological_compare import TautologicalCompare
from .statements.return_bomb import ReturnBomb
from .functions.out_of_order_retryable import OutOfOrderRetryable
from .functions.gelato_unprotected_randomness import GelatoUnprotectedRandomness

# from .statements.unused_import import UnusedImport
78 changes: 78 additions & 0 deletions slither/detectors/functions/gelato_unprotected_randomness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from typing import List

from slither.slithir.operations.internal_call import InternalCall
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output


class GelatoUnprotectedRandomness(AbstractDetector):
"""
Unprotected Gelato VRF requests
"""

ARGUMENT = "gelato-unprotected-randomness"
HELP = "Call to _requestRandomness within an unprotected function"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM

WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#gelato-unprotected-randomness"

WIKI_TITLE = "Gelato unprotected randomness"
WIKI_DESCRIPTION = "Detect calls to `_requestRandomness` within an unprotected function."

# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C is GelatoVRFConsumerBase {
function _fulfillRandomness(
uint256 randomness,
uint256,
bytes memory extraData
) internal override {
// Do something with the random number
}
function bad() public {
_requestRandomness(abi.encode(msg.sender));
}
}
```
The function `bad` is uprotected and requests randomness."""
# endregion wiki_exploit_scenario

WIKI_RECOMMENDATION = (
"Function that request randomness should be allowed only to authorized users."
)

def _detect(self) -> List[Output]:
results = []

for contract in self.compilation_unit.contracts_derived:
if "GelatoVRFConsumerBase" in [c.name for c in contract.inheritance]:
for function in contract.functions_entry_points:
if not function.is_protected() and (
nodes_request := [
ir.node
for ir in function.all_internal_calls()
if isinstance(ir, InternalCall)
and ir.function_name == "_requestRandomness"
]
):
# Sort so output is deterministic
nodes_request.sort(key=lambda x: (x.node_id, x.function.full_name))

for node in nodes_request:
info: DETECTOR_INFO = [
function,
" is unprotected and request randomness from Gelato VRF\n\t- ",
node,
"\n",
]
res = self.generate_result(info)
results.append(res)

return results
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
C.bad() (tests/e2e/detectors/test_data/gelato-unprotected-randomness/0.8.20/gelato_unprotected_randomness.sol#42-44) is unprotected and request randomness from Gelato VRF
- id = _requestRandomness(abi.encode(msg.sender)) (tests/e2e/detectors/test_data/gelato-unprotected-randomness/0.8.20/gelato_unprotected_randomness.sol#43)

C.good2() (tests/e2e/detectors/test_data/gelato-unprotected-randomness/0.8.20/gelato_unprotected_randomness.sol#51-54) is unprotected and request randomness from Gelato VRF
- id = _requestRandomness(abi.encode(msg.sender)) (tests/e2e/detectors/test_data/gelato-unprotected-randomness/0.8.20/gelato_unprotected_randomness.sol#53)

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Mock GelatoVRFConsumerBase for what we need
abstract contract GelatoVRFConsumerBase {
bool[] public requestPending;
mapping(uint256 => bytes32) public requestedHash;

function _fulfillRandomness(
uint256 randomness,
uint256 requestId,
bytes memory extraData
) internal virtual;

function _requestRandomness(
bytes memory extraData
) internal returns (uint256 requestId) {
requestId = uint256(requestPending.length);
requestPending.push();
requestPending[requestId] = true;

bytes memory data = abi.encode(requestId, extraData);
uint256 round = 111;

bytes memory dataWithRound = abi.encode(round, data);
bytes32 requestHash = keccak256(dataWithRound);

requestedHash[requestId] = requestHash;
}

}

contract C is GelatoVRFConsumerBase {
address owner;
mapping(address => bool) authorized;

function _fulfillRandomness(
uint256 randomness,
uint256,
bytes memory extraData
) internal override {
// Do something with the random number
}

function bad() public {
uint id = _requestRandomness(abi.encode(msg.sender));
}

function good() public {
require(msg.sender == owner);
uint id = _requestRandomness(abi.encode(msg.sender));
}

function good2() public {
require(authorized[msg.sender]);
uint id = _requestRandomness(abi.encode(msg.sender));
}

function good3() public {
if (msg.sender != owner) { revert(); }
uint id = _requestRandomness(abi.encode(msg.sender));
}

}
Binary file not shown.

0 comments on commit 24a9d9d

Please sign in to comment.