See how vyper compares to other vendors in security performance
Vyper 0.3.4 through 0.5.0b1 computes the remaining blueprint size in createfromblueprint by uint256 subtraction without first checking that codeoffset is below the blueprint's code size, so a huge offset wraps to a small positive value. An attacker controlling codeoffset can make a factory deploy contracts with no runtime code, potentially trapping ETH and tokens.
Vyper 0.3.8 through 0.5.0b1 contains an optimizer miscompilation in which (msg.value | K) or (len(msg.data) | K), where K is any nonzero integer literal, is compiled to the constant 1 when used as a ternary expression branch under -O gas (the default) or -O codesize. Affected contracts use the wrong value in storage writes, arithmetic, indexes, return values, or events without warning or revert, which can record deposits incorrectly and lock funds.
Vyper 0.3.8 through 0.5.0b1 contains a compiler miscompilation vulnerability in parseIfExp that reads nested arrays with narrower inner bounds using the wider result type's memory layout. When a ternary selects the narrower array, inner arrays after the first are read at wrong offsets, causing contracts to silently store corrupted values or revert.
Vyper 0.1.0b1 before 0.1.0b17 and 0.4.1 through 0.5.0b1 builds its source-line table with str.splitlines(), which treats characters like form feed and U+2028 as line breaks the Python parser ignores, so numeric literals are classified and validated from shifted source text. An attacker contributing source code can hide these characters in comments, strings or indentation to silently change address, integer or decimal constants.
Vyper from 0.1.0b4 before 0.3.0 contains a memory corruption vulnerability caused by an incorrect stack tip pointer when internal function calls are performed inside arrays. Contracts placing internal calls in nested array elements, external call arguments, struct literals, or rawlog topics can have memory corrupted, producing incorrect contract behavior.
Vyper from 0.2.13 before 0.2.15 allocates storage slots for @nonreentrant reentrancy locks at the same locations as contract storage variables. When a function using @nonreentrant is called, setting the lock overwrites the overlapping storage variable, corrupting contract state and breaking the contract's intended logic.
Vyper before 0.2.9 deploys forwarder proxies through the built-in createforwarderto function that report a fixed 4096-byte RETURNDATASIZE rather than the actual return data size. Calls through these proxies can yield corrupted data when a function returns more than 4096 bytes without return data sanitation, and fail strict return-size checks such as SafeERC20.safeTransfer.
Vyper before 0.3.1 lacks a uint8 type, allowing interface return values declared as uint8, such as ERC20.decimals(), to be stored in uint256 variables without bounds checking. Malicious contracts can return values greater than 255 to manipulate or bypass contract logic that relies on these values.
Vyper before 0.2.6 contains a call stack corruption vulnerability that causes incorrect function outputs when complex types with non-base members are passed as arguments. Contracts passing multi-dimensional arrays or structs to internal or external functions receive corrupted values because the compiler assumed each subtype size is 32.
Vyper from 0.1.0b10 before 0.2.3 generates incorrect code for a return statement inside a for loop in an internal function called two or more internal calls deep, producing an invalid jump destination (stack underflow). Affected contracts revert such calls unnecessarily, which in limited circumstances could enable denial of service against public contracts.
Vyper from 0.1.0b4 before 0.3.0 contains a memory corruption vulnerability caused by an incorrect stack tip pointer when internal function calls appear inside tuples or nested calls. Contracts using such calls as tuple elements, call arguments, or builtin arguments like concat or ecrecover can return corrupted values or pass wrong arguments to external calls.
Impact the slice() builtin can elide side effects when the output length is 0, and the source bytestring is a builtin (msg.data or <address>.code). the reason is that for these source locations, the check that length >= 1 is skipped: https://github.com/vyperlang/vyper/blob/68b68c4b30c5ef2f312b4674676170b8a6eaa316/vyper/builtins/functions.py#L315-L319
the result is that a 0-length bytestring constructed with slice can be passed to makebytearraycopier, which elides evaluation of its source argument when the max length is 0: https://github.com/vyperlang/vyper/blob/68b68c4b30c5ef2f312b4674676170b8a6eaa316/vyper/codegen/core.py#L189-L191
the impact is that side effects in the start argument may be elided when the length argument is 0, e.g. slice(msg.data, self.dosideeffect(), 0).
the following example illustrates how the issue would look in user code vyper counter: public(uint256)
@external def test() -> Bytes[10]: b: Bytes[10] = slice(msg.data, self.sideeffect(), 0) return b
def sideeffect() -> uint256: self.counter += 1 return 0
the severity assigned is low, since this is not a very useful pattern and unlikely to be found in user code.
Patches
the fix is tracked in https://github.com/vyperlang/vyper/pull/4645, which disallows any invocation of slice() with length 0, including for the ad hoc locations discussed in this advisory.
Workarounds Is there a way for users to fix or remediate the vulnerability without upgrading?
References Are there any links users can visit to find out more?
Impact concat() may skip evaluation of side effects when the length of an argument is zero. this is due to a fastpath in the implementation which skips evaluation of argument expressions when their length is zero: https://github.com/vyperlang/vyper/blob/68b68c4b30c5ef2f312b4674676170b8a6eaa316/vyper/builtins/functions.py#L560-L562
in practice, it would be very unusual in user code to construct zero-length bytestrings using an expression with side-effects, since zero-length bytestrings are typically constructed with the empty literal b""; the only way to construct an empty bytestring which has side effects would be with the ternary operator introduced in v0.3.8, e.g. b"" if self.dosomesideeffect() else b"".
the following example demonstrates how the issue would look in user code vyper counter: public(uint256)
@external def test() -> Bytes[256]: a: Bytes[256] = concat(b"" if self.sideeffect() else b"", b"aaaa") return a
def sideeffect() -> bool: self.counter += 1 return True
the severity assigned is low, since, as mentioned, this would be a very unusual pattern in user-code.
Patches
fix is tracked in https://github.com/vyperlang/vyper/pull/4644
Workarounds don't have side effects in expressions which construct zero-length bytestrings.
References Are there any links users can visit to find out more?