#38855 [SC-Low] Evaluation order is not respected in `log` function
Submitted on Jan 16th 2025 at 01:26:18 UTC by @anatomist for Attackathon | Ethereum Protocol
Report ID: #38855
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/vyperlang/vyper
Impacts:
(Compiler) Incorrect bytecode generation leading to incorrect behavior
(Compiler) Unexpected behavior
Description
Brief/Intro
The left-to-right argument evaluation order is not respected when Event definition contains indexed fields.
Vulnerability Details
There are two evaluation order issues in the current implementation of log function:
indexed field always evaluate first
indexed field evaluate in reverse order
During log expression parsing, IRnodes of indexed fields in the Event definition will be collected separately from normal data field. It will then be passed into ir_node_for_log()
function, the function will also encode both of them separately and make them as the arguments of the output IRnode.
ir_node_for_log()
will output an LOGN
IRnode with arguments looks like below:
Since topic
IRnodes are loaded and put into the argument array separately, during the final compilation from IRnodes to EVM assembly, the arguments of LOGN
opcode will be translated into assembly in a reversed order, making the indexed fields always being evaluated first but in a reversed order.
This issue is an interesting intersection of multiple root cause, from the separate parsing of different fields within an event to assembly emission order of irnode args. So naturally part of it would overlap some of the known issues such as reversed order of side effects.
Aside from the issue itself, we especially want to highlight vyper currently lacks a clear definition of evaluation order (left-to-right is the rule of thumb, but there are quite a few exceptions). For example, judging from the code, assign should evaluated rhs (value) before lhs (assigned var), and augassign should evaluate lhs (augmented value) before rhs (delta). When it comes to subscript, the rules are even more complex, where parent
should be evaluated before key
, but in the case where parent
resolves to a storage variable, the evaluation will be done lazily (the code below logs ("sideeffect1", "sideeffect2", 1) since the self.hm1
returned from the ifexp
is merely a reference to storage address, and only dereferenced after key
is evaluated).
Without a proper definition of evaluation order, developers will often make their own assumptions (which may or may not be correct) while writing code, and it also makes it harder for auditors to decide whether a specific implementation should be considered buggy. Thus we would suggest vyper compiler devs to develop rules for evaluation orders to further improve the language.
Impact Details
Unforeseen evaluation orders of arguments may lead to unexpected code behaviors.
References
https://github.com/vyperlang/vyper/blob/c208b954564e8fffdd4c86cc3c497e0c3df1aeec/vyper/codegen/stmt.py#L106
https://github.com/vyperlang/vyper/blob/c208b954564e8fffdd4c86cc3c497e0c3df1aeec/vyper/codegen/events.py#L63
https://github.com/vyperlang/vyper/blob/c208b954564e8fffdd4c86cc3c497e0c3df1aeec/vyper/ir/compile_ir.py#L241
Proof of Concept
Proof of Concept
In this case, the evaluation order is not left-to-right, instead, the order is l -> j -> i -> k
Was this helpful?