-
Notifications
You must be signed in to change notification settings - Fork 331
EOF: less restricted stack validation #676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
bd20842 to
39d3fef
Compare
39d3fef to
cd39111
Compare
cd39111 to
6de389a
Compare
6de389a to
296ce1e
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #676 +/- ##
==========================================
+ Coverage 97.88% 97.98% +0.09%
==========================================
Files 113 113
Lines 10631 11155 +524
==========================================
+ Hits 10406 10930 +524
Misses 225 225
Flags with carried forward coverage won't be shown. Click here to find out more.
|
e86d2a5 to
84c5142
Compare
18dd783 to
7a33c80
Compare
63ad029 to
ac9cf2b
Compare
c1b4f82 to
7ec8abb
Compare
lib/evmone/eof.cpp
Outdated
| return expected_stack_height.min == successor_stack_height.min && | ||
| expected_stack_height.max == successor_stack_height.max; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spec could have been relaxed to
| return expected_stack_height.min == successor_stack_height.min && | |
| expected_stack_height.max == successor_stack_height.max; | |
| return expected_stack_height.min >= successor_stack_height.min && | |
| expected_stack_height.max <= successor_stack_height.max; |
but it was decided to have strict equality for simlicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave this comment in the code.
Also, I'd swap the arguments: successor_stack_height.min <= required_stack_height.
pdobacz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought for a while what other test cases we could have. Some ideas, choose which make sense in your opinion:
- RJUMP(IV) sequence of maximum possible length, doing hops from one to the other
- RJUMPI(V) sequence of maximum possible length, all targeting same instruction
- stack_height.min/max range maximally broad
- maximum number of code sections
- CALLF sequence of maximum possible length
- [RJUMPI, RETF] sequence of maximum ...
- [RJUMPI, JUMPF] sequence ...
For each of these situations test if stack validation behaves - one valid code, one invalid.
lib/evmone/eof.cpp
Outdated
|
|
||
| assert(!code.empty()); | ||
|
|
||
| // Stack height in the header is limited to uint16_t, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment should be moved above
| const auto& test_case = test_cases[i]; | ||
| EXPECT_EQ(evmone::validate_eof(rev, test_case.container), test_case.error) | ||
| << test_case.name << "\n" | ||
| << "test case " << i << " " << test_case.name << "\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use Go convention test_name/index but too late now.
7ec8abb to
f424a76
Compare
| namespace | ||
| { | ||
| // code prologue that creates a segment starting with possible stack heights 1 and 3 | ||
| const auto varstack = push0() + rjumpi(2, 0) + OP_PUSH0 + OP_PUSH0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const auto varstack = push0() + rjumpi(2, 0) + OP_PUSH0 + OP_PUSH0; | |
| const auto varstack = push0() + rjumpi(2, 0) + push0() + push0(); |
|
|
||
| // STOP reachable only via backwards jump - invalid | ||
| add_test_case( | ||
| eof_bytecode(rjump(1) + OP_STOP + rjump(-4)), EOFValidationError::unreachable_instructions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case the error may be confusing. Any ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we wouldn't be able to distinguish really unreachable instructions from those reachable only via backwards jump (because we validate in a linear forwards pass)
So we could only make this error message more general, something like instruction_not_reachabe_via_forward_control_flow or even more general invalid_instruction_order. I don't have better ideas yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's leave it for later.
lib/evmone/eof.cpp
Outdated
| { | ||
| const auto i = worklist.top(); | ||
| worklist.pop(); | ||
| std::vector<StackHeightRange> stack_heights(code.size(), {LOC_UNVISITED, LOC_UNVISITED}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| std::vector<StackHeightRange> stack_heights(code.size(), {LOC_UNVISITED, LOC_UNVISITED}); | |
| std::vector<StackHeightRange> stack_heights(code.size()); |
lib/evmone/eof.cpp
Outdated
| return expected_stack_height.min == successor_stack_height.min && | ||
| expected_stack_height.max == successor_stack_height.max; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave this comment in the code.
Also, I'd swap the arguments: successor_stack_height.min <= required_stack_height.
| StackHeightRange expected_stack_height) { | ||
| auto& successor_stack_height = stack_heights[successor_offset]; | ||
| if (successor_stack_height == LOC_UNVISITED) | ||
| if (successor_offset <= current_offset) // backwards jump |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handling the successor_offset == current_offset case here is slightly confusing. Maybe just separate this case and handle it as no-op? Or at least a comment would be nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a no-op (good that tests detected this), I've added a comment why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By no-op I mean that the validity condition is always true because in this case successor_stack_height is required_stack_height. Or I'm missing something.
We could handle this separately for clarity and test coverage visibility. As follows:
if (successor_offset == current_offset) // self-referencing jump
return true;But I'm insisting on this. I'm mostly verifying my understanding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're missing something as I tried to explain in the comment. The stack height still needs to be checked in this case.
Here's the code that would be broken: PUSH0 RJUMPI(-3) STOP - RJUMPI(-3) generates equality case, and without the check it would be valid, but it is invalid because stack after RJUMPI is not equal to stack before RJUMPI (requires 1 item)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, cool.
lib/evmone/eof.cpp
Outdated
| // Validates the successor instruction and updates its stack height. | ||
| const auto validate_successor = [&stack_heights, &worklist](size_t successor_offset, | ||
| int32_t expected_stack_height) { | ||
| const auto validate_successor = [&stack_heights](size_t current_offset, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const auto validate_successor = [&stack_heights](size_t current_offset, | |
| const auto visit_successor = [&stack_heights](size_t current_offset, |
The name "validate" suggests this a "constant" function - no update.
d267377 to
64716ef
Compare
| StackHeightRange expected_stack_height) { | ||
| auto& successor_stack_height = stack_heights[successor_offset]; | ||
| if (successor_stack_height == LOC_UNVISITED) | ||
| if (successor_offset <= current_offset) // backwards jump |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By no-op I mean that the validity condition is always true because in this case successor_stack_height is required_stack_height. Or I'm missing something.
We could handle this separately for clarity and test coverage visibility. As follows:
if (successor_offset == current_offset) // self-referencing jump
return true;But I'm insisting on this. I'm mostly verifying my understanding.
|
|
||
| // STOP reachable only via backwards jump - invalid | ||
| add_test_case( | ||
| eof_bytecode(rjump(1) + OP_STOP + rjump(-4)), EOFValidationError::unreachable_instructions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's leave it for later.
64716ef to
786c173
Compare
| StackHeightRange expected_stack_height) { | ||
| auto& successor_stack_height = stack_heights[successor_offset]; | ||
| if (successor_stack_height == LOC_UNVISITED) | ||
| if (successor_offset <= current_offset) // backwards jump |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, cool.
pdobacz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
1837002 to
9a8e160
Compare
https://notes.ethereum.org/Fk1TksgjTxOotvQNL07mEQ#Less-restricted-validation-algorithm