-
Notifications
You must be signed in to change notification settings - Fork 285
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
EOF: less restricted stack validation #676
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
.
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
int32_t max = 0; | ||
}; | ||
|
||
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
test/unittests/eof_validation.cpp
Outdated
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
.
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
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
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.
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