vyper's range(start, start + N) reverts for negative numbers
When looping over a range of the form range(start, start + N), if start is negative, the execution will always revert.
When looping over a range of the form range(start, start + N), if start is negative, the execution will always revert.
Incorrect values can be logged when raw_log builtin is called with memory or storage arguments to be used as topics. A contract search was performed and no vulnerable contracts were found in production. In particular, no uses of raw_log() were found at all in production; it is apparently not a well-known function.
Using the slice builtin can result in a double eval vulnerability when the buffer argument is either msg.data, self.code or <address>.code and either the start or length arguments have side-effects. A contract search was performed and no vulnerable contracts were found in production. Having side-effects in the start and length patterns is also an unusual pattern which is not that likely to show up in user code. It is also …
Using the sqrt builtin can result in multiple eval evaluation of side effects when the argument has side-effects. The bug is more difficult (but not impossible!) to trigger as of 0.3.4, when the unique symbol fence was introduced (https://github.com/vyperlang/vyper/pull/2914). A contract search was performed and no vulnerable contracts were found in production.
Using the create_from_blueprint builtin can result in a double eval vulnerability when raw_args=True and the args argument has side-effects. A contract search was performed and no vulnerable contracts were found in production. In particular, the raw_args variant of create_from_blueprint was not found to be used in production.
Prior to v0.3.0, default() functions did not respect the @nonreentrancy decorator and the lock was not emitted. This is a known bug and was already visible in the issue tracker (https://github.com/vyperlang/vyper/issues/2455), but it is being re-issued as an advisory so that tools relying on the advisory publication list can incorporate it into their searches. A contract search was additionally performed and no vulnerable contracts were found in production.
When using the built-in extract32(b, start), if the start index provided has for side effect to update b, the byte array to extract 32 bytes from, it could be that some dirty memory is read and returned by extract32. As of v0.4.0 (specifically, commit https://github.com/vyperlang/vyper/commit/3d9c537142fb99b2672f21e2057f5f202cde194f), the compiler will panic instead of generating bytecode.
Summary If an excessively large value is specified as the starting index for an array in _abi_decode, it can cause the read position to overflow. This results in the decoding of values outside the intended array bounds, potentially leading to bugs in contracts that use arrays within _abi_decode. The advisory has been assigned low severity, because it is only observable if there is a memory write between two invocations of …
Arrays can be keyed by a signed integer, while they are defined for unsigned integers only. The typechecker doesn't throw when spotting the usage of an int as an index for an array. Typically, negative integers are filtered out at runtime by the bounds checker, but small enough (i.e. large in magnitude, ex. -2255 + 5) quantities combined with large enough arrays (at least 2255 in length) can pass the …
There is an error in the stack management when compiling the IR for sha3_64. Concretely, the height variable is miscalculated. The vulnerability can't be triggered without writing the IR by hand. That is, it cannot be triggered from regular vyper code, it can only be triggered by using the fang binary directly (this binary used to be called vyper-ir prior to v0.3.4).
When calls to external contracts are made, we write the input buffer starting at byte 28, and allocate the return buffer to start at byte 0 (overlapping with the input buffer). When checking RETURNDATASIZE for dynamic types, the size is compared only to the minimum allowed size for that type, and not to the returned value's length. As a result, malformed return data can cause the contract to mistake data …
The bounds check for slices does not account for the ability for start + length to overflow when the values aren't literals. If a slice() function uses a non-literal argument for the start or length variable, this creates the ability for an attacker to overflow the bounds check. This issue can be used to do OOB access to storage, memory or calldata addresses. It can also be used to corrupt …
Vyper compiler allows passing a value in builtin raw_call even if the call is a delegatecall or a staticcall. But in the context of delegatecall and staticcall the handling of value is not possible due to the semantics of the respective opcodes, and vyper will silently ignore the value= argument. A contract search was performed and no vulnerable contracts were found in production.
concat built-in can write over the bounds of the memory buffer that was allocated for it and thus overwrite existing valid data. The root cause is that the build_IR for concat doesn't properly adhere to the API of copy functions (for >=0.3.2 the copy_bytes function). A contract search was performed and no vulnerable contracts were found in production. Tracked in issue https://github.com/vyperlang/vyper/issues/3737
contracts containing large arrays might underallocate the number of slots they need. prior to v0.3.8, the calculation to determine how many slots a storage variable needed used math.ceil(type_.size_in_bytes / 32):
_abi_decode() does not validate input when it is nested in an expression. the following example gets correctly validated (bounds checked): x: int128 = _abi_decode(slice(msg.data, 4, 32), int128) however, the following example is not bounds checked @external def abi_decode(x: uint256) -> uint256: a: uint256 = convert(_abi_decode(slice(msg.data, 4, 32), (uint8)), uint256) + 1 return a # abi_decode(256) returns: 257 the issue can be triggered by constructing an example where the output of …
In certain conditions, the memory used by the builtins raw_call, create_from_blueprint and create_copy_of can be corrupted. For raw_call, the argument buffer of the call can be corrupted, leading to incorrect calldata in the sub-context. For create_from_blueprint and create_copy_of, the buffer for the to-be-deployed bytecode can be corrupted, leading to deploying incorrect bytecode. Below are the conditions that must be fulfilled for the corruption to happen for each builtin:
Locks of the type @nonreentrant("") or @nonreentrant('') do not produce reentrancy checks at runtime. @nonreentrant("") # unprotected @external def bar(): pass @nonreentrant("lock") # protected @external def foo(): pass
For the following (probably non-exhaustive) list of expressions, the compiler evaluates the arguments from right to left instead of left to right. - unsafe_add - unsafe_sub - unsafe_mul - unsafe_div - pow_mod256 - |, &, ^ (bitwise operators) - bitwise_or (deprecated) - bitwise_and (deprecated) - bitwise_xor (deprecated) - raw_call - <, >, <=, >=, ==, != - in, not in (when lhs and rhs are enums) This behaviour becomes a …
The order of evaluation of the arguments of the builtin functions uint256_addmod, uint256_mulmod, ecadd and ecmul does not follow source order. • For uint256_addmod(a,b,c) and uint256_mulmod(a,b,c), the order is c,a,b. • For ecadd(a,b) and ecmul(a,b), the order is b,a. Note that this behaviour is problematic when the evaluation of one of the arguments produces side effects that other arguments depend on.
In versions 0.2.15, 0.2.16 and 0.3.0, named re-entrancy locks are allocated incorrectly. Each function using a named re-entrancy lock gets a unique lock regardless of the key, allowing cross-function re-entrancy in contracts compiled with the susceptible versions. A specific set of conditions is required to result in misbehavior of affected contracts, specifically: A .vy contract compiled with either of the following vyper versions: 0.2.15, 0.2.16, 0.3.0 A primary function that …
the ecrecover precompile does not fill the output buffer if the signature does not verify, see https://github.com/ethereum/go-ethereum/blob/b058cf454b3bdc7e770e2b3cec83a0bcb48f55ee/core/vm/contracts.go#L188. 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 …
in contracts with at least one regular nonpayable function, due to the callvalue check being inside of the selector section, it is possible to send funds to the default function by using less than 4 bytes of calldata, even if the default function is marked nonpayable. this applies to contracts compiled with vyper<=0.3.7.
during codegen, the length word of a dynarray is written before the data, which can result in OOB array access in the case where the dynarray is on both the lhs and rhs of an assignment. here is a minimal example producing the issue: a:DynArray[uint256,3] @external def test() -> DynArray[uint256,3]: self.a = [1,2,3] self.a = empty(DynArray[uint256,3]) self.a = [self.a[0],self.a[1],self.a[2]] return self.a # return [1,2,3] and here is an example demonstrating …
Due to missing overflow check for loop variables, by assigning the iterator of a loop to a variable, it is possible to overflow the type of the latter. In the following example, calling test returns 354, meaning that the variable a did store 354 a value out of bound for the type uint8. @external def test() -> uint16: x:uint8 = 255 a:uint8 = 0 for i in range(x, x+100): a …
Internal calls to internal functions with more than 1 default argument are compiled incorrectly. Depending on the number of arguments provided in the call, the defaults are added not right-to-left, but left-to-right. If the types are incompatible, typechecking is bypassed. In the bar() function in the following code, self.foo(13) is compiled to self.foo(13,12) instead of self.foo(13,1337). @internal def foo(a:uint256 = 12, b:uint256 = 1337): pass @internal def bar(): self.foo(13) note …
The storage allocator does not guard against allocation overflows. This can result in vulnerabilities like the following: owner: public(address) take_up_some_space: public(uint256[10]) buffer: public(uint256[max_value(uint256)]) @external def initialize(): self.owner = msg.sender @external def foo(idx: uint256, data: uint256): self.buffer[idx] = data Per @toonvanhove, "An attacker can overwrite the owner variable by calling this contract with calldata: 0x04bc52f8 fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff (spaces inserted for readability) 0x04bc52f8 is the selector for foo(uint256, uint256), and the last …
Example of buggy code: @external def returnSome(calling: address, a: uint256) -> bool: success: bool = false success = raw_call( calling, _abi_encode(a, method_id=method_id("a(uint256)")), revert_on_failure=False ) any contract that uses the raw_call with revert_on_failure=False and max_outsize=0 receives the wrong response from raw_call. Depending on the memory garbage, the result can be either True or False.
Impact when a calling an external contract with no return value, the contract address could be evaluated twice. this is usually only an efficiency problem, but if evaluation of the contract address has side effects, it could result in double evaluation of the side effects. in the following example, Foo(msg.sender).bar() is the contract address for the following call (to .foo()), and could get evaluated twice interface Foo: def foo(): nonpayable …
Importing a function from a JSON interface which returns bytes generates bytecode which does not clamp bytes length, potentially resulting in a buffer overrun.
Vyper is a pythonic Smart Contract Language for the ethereum virtual machine. In affected versions, the return of <iface>.returns_int128() is not validated to fall within the bounds of int128. This issue can result in a misinterpretation of the integer value and lead to incorrect behavior. As of v0.3.0, <iface>.returns_int128() is validated in simple expressions, but not complex expressions. Users are advised to upgrade. There is no known workaround for this …
Vyper is a Pythonic Smart Contract Language for the Ethereum Virtual Machine. In version 0.3.1 and prior, bytestrings can have dirty bytes in them, resulting in the word-for-word comparisons giving incorrect results. Even without dirty nonzero bytes, two bytestrings can compare to equal if one ends with "\x00" because there is no comparison of the length. A patch is available and expected to be part of the 0.3.2 release. There …
bytestrings can have dirty bytes in them, resulting in the word-for-word comparison to give incorrect results.
When performing a function call inside a literal struct, there is a memory corruption issue that occurs because of an incorrect pointer to the the top of the stack.
The following code does not properly validate that its input is in bounds. @external def foo(x: decimal) -> decimal: return x
Background When attempting to use the v0.2.14 release, @pandadefi discovered an issue using the @nonreentrant decorator. Impact Reentrancy protection storage slots get allocated to the same slots as storage variables, leading to the corruption of storage variables when using the @nonreentrant decorator. Patches This issue was fixed in v0.2.15 in #2391, #2379 Workarounds Don't use the @nonreentrant decorator in these versions.
Background @tjayrush reported a data handling issue with certain Web3 libraries using Vyper-deploy forwarder proxy contracts using our Vyper's built-in create_forwarder_to function prior to our change to support EIP-1167 style forwarder proxies. Impact If you are an end user of a forwarder-style proxy deployed using Vyper's built-in create_forwarder_to function AND you have a function that returns bytes AND you do no return data sanitation on the value returned, you could …
When performing a function call inside an array, there is a memory corruption issue that occurs because of an incorrect pointer to the the tip of the stack. Patches This issue was partially fixed in VVE-2020-0004 however the fix did not update similar code for arrays, which had a similar issue. The issue is fully fixed in https://github.com/vyperlang/vyper/pull/2345
VVE-2020-0001 Earlier today, we received a responsible disclosure of a potential issue from @montyly (security researcher at @trailofbits) for Vyper users who make assumptions about what values certain interface types can return. Impact We determined the issue to be mild and unlikely to be exploited, with an easy workaround while the correct resolution is in process. The issue stems from a number of things, which we will detail here. (1) …