All builtin functions implement the BuiltinFunction class, which calls the _validate_arg_types() function, which calls self._validate_single() for all arguments.
In the case of the len() function being called with a literal list, the argument passed to _validate_single() is the list node, and the expected type is a tuple of the allowed type classes:
This calls the validate_expected_type(), where the given_types returns all the possible types for the literal list.
In the event that the node is a literal list, we go down this code path:
# if it's a literal list, validate: expected contains array, lengths match, each item matches
if isinstance(node, vy_ast.List):
# special case - for literal arrays we individually validate each item
for expected in expected_type:
if not isinstance(expected, (DArrayT, SArrayT)):
continue
if _validate_literal_array(node, expected):
return
As we can see, this checks that isinstance(expected, (DArrayT, SArrayT)). Only when this is the case does it proceed to the _validate_literal_array() function, which allows us to return safely without an error.
Unfortunately, isinstance() tells us if an instance fits a given type. But expected is not an instance — it is the type class itself. As a result, this check will always fail, and the compilation will fail.
Proof of Concept
The following Vyper contracts will fail to compile due to this error:
Contracts that include literal lists as arguments to builtin functions will fail to compile.
Tools Used
Manual Review
Recommendations
In validate_expected_type(), adjust the check to ensure that the expected type matches with DArrayT or SArrayT, rather than requiring it to be an instance of it.
Submitted by obront, Bauchibred, DarkTower . Selected submission by: obront.
Relevant GitHub Links
https://github.com/vyperlang/vyper/blob/3ba14124602b673d45b86bae7ff90a01d782acb5/vyper/builtins/functions.py#L460-L463
https://github.com/vyperlang/vyper/blob/3ba14124602b673d45b86bae7ff90a01d782acb5/vyper/builtins/_signatures.py#L82-L103
https://github.com/vyperlang/vyper/blob/3ba14124602b673d45b86bae7ff90a01d782acb5/vyper/semantics/analysis/utils.py#L527
Summary
When types are validated for literal lists passed to builtin functions, we perform the following check:
However, in this scenario,
expected
is the type class, not an instance, so it always fails. As a result, the compilation fails.Vulnerability Details
We will use the builtin
len()
function to demonstrate this issue.The
len()
function accepts a single argument, which can be either a string, byte array or dynamic array:All builtin functions implement the
BuiltinFunction
class, which calls the_validate_arg_types()
function, which callsself._validate_single()
for all arguments.In the case of the
len()
function being called with a literal list, the argument passed to_validate_single()
is the list node, and the expected type is a tuple of the allowed type classes:This calls the
validate_expected_type()
, where thegiven_types
returns all the possible types for the literal list.In the event that the node is a literal list, we go down this code path:
As we can see, this checks that
isinstance(expected, (DArrayT, SArrayT))
. Only when this is the case does it proceed to the_validate_literal_array()
function, which allows us to return safely without an error.Unfortunately,
isinstance()
tells us if an instance fits a given type. Butexpected
is not an instance — it is the type class itself. As a result, this check will always fail, and the compilation will fail.Proof of Concept
The following Vyper contracts will fail to compile due to this error:
Impact
Contracts that include literal lists as arguments to builtin functions will fail to compile.
Tools Used
Manual Review
Recommendations
In
validate_expected_type()
, adjust the check to ensure that the expected type matches withDArrayT
orSArrayT
, rather than requiring it to be an instance of it.