Skip to content
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

Merged
merged 10 commits into from
Feb 21, 2024
Merged

Conversation

gumb0
Copy link
Member

@gumb0 gumb0 commented Jul 26, 2023

@gumb0 gumb0 added the EOF label Jul 26, 2023
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch from bd20842 to 39d3fef Compare July 27, 2023 13:55
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch from 39d3fef to cd39111 Compare August 11, 2023 15:06
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch from cd39111 to 6de389a Compare September 1, 2023 13:21
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch from 6de389a to 296ce1e Compare December 4, 2023 17:48
Copy link

codecov bot commented Dec 4, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (534d4e4) 97.88% compared to head (9a8e160) 97.98%.

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              
Flag Coverage Δ
blockchaintests 59.70% <0.00%> (-0.18%) ⬇️
statetests 62.18% <96.87%> (+0.01%) ⬆️
statetests-silkpre 24.48% <0.00%> (-1.14%) ⬇️
unittests 96.07% <100.00%> (+0.20%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
lib/evmone/eof.cpp 83.56% <100.00%> (-0.08%) ⬇️
test/unittests/eof_stack_validation_test.cpp 100.00% <100.00%> (ø)
test/unittests/eof_validation_test.cpp 100.00% <100.00%> (ø)
test/unittests/evm_eof_rjump_test.cpp 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch 7 times, most recently from e86d2a5 to 84c5142 Compare December 11, 2023 15:18
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch 12 times, most recently from 18dd783 to 7a33c80 Compare December 20, 2023 11:17
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch 4 times, most recently from 63ad029 to ac9cf2b Compare December 21, 2023 19:55
@gumb0 gumb0 requested a review from rodiazet February 16, 2024 17:04
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch from c1b4f82 to 7ec8abb Compare February 17, 2024 11:34
Comment on lines 453 to 454
return expected_stack_height.min == successor_stack_height.min &&
expected_stack_height.max == successor_stack_height.max;
Copy link
Member Author

@gumb0 gumb0 Feb 17, 2024

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

Suggested change
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.

Copy link
Member

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.

Copy link
Collaborator

@pdobacz pdobacz left a 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:

  1. RJUMP(IV) sequence of maximum possible length, doing hops from one to the other
  2. RJUMPI(V) sequence of maximum possible length, all targeting same instruction
  3. stack_height.min/max range maximally broad
  4. maximum number of code sections
  5. CALLF sequence of maximum possible length
  6. [RJUMPI, RETF] sequence of maximum ...
  7. [RJUMPI, JUMPF] sequence ...

For each of these situations test if stack validation behaves - one valid code, one invalid.

int32_t max = 0;
};

assert(!code.empty());

// Stack height in the header is limited to uint16_t,
Copy link
Collaborator

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

lib/evmone/eof.cpp Outdated Show resolved Hide resolved
EXPECT_EQ(evmone::validate_eof(rev, test_case.container), test_case.error)
<< test_case.name << "\n"
<< "test case " << i << " " << test_case.name << "\n"
Copy link
Member

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.

lib/evmone/eof.cpp Outdated Show resolved Hide resolved
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch from 7ec8abb to f424a76 Compare February 19, 2024 14:12
@gumb0 gumb0 requested a review from chfast February 19, 2024 14:21
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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);
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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.

test/unittests/eof_stack_validation_test.cpp Show resolved Hide resolved
test/unittests/eof_stack_validation_test.cpp Show resolved Hide resolved
{
const auto i = worklist.top();
worklist.pop();
std::vector<StackHeightRange> stack_heights(code.size(), {LOC_UNVISITED, LOC_UNVISITED});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::vector<StackHeightRange> stack_heights(code.size(), {LOC_UNVISITED, LOC_UNVISITED});
std::vector<StackHeightRange> stack_heights(code.size());

Comment on lines 453 to 454
return expected_stack_height.min == successor_stack_height.min &&
expected_stack_height.max == successor_stack_height.max;
Copy link
Member

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
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, cool.

// 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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

lib/evmone/eof.cpp Show resolved Hide resolved
lib/evmone/eof.cpp Show resolved Hide resolved
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch 3 times, most recently from d267377 to 64716ef Compare February 20, 2024 17:38
@gumb0 gumb0 requested a review from chfast February 20, 2024 17:41
auto& successor_stack_height = stack_heights[successor_offset];
if (successor_stack_height == LOC_UNVISITED)
if (successor_offset <= current_offset) // backwards jump
Copy link
Member

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.

lib/evmone/eof.cpp Outdated Show resolved Hide resolved
lib/evmone/eof.cpp Outdated Show resolved Hide resolved

// STOP reachable only via backwards jump - invalid
add_test_case(
eof_bytecode(rjump(1) + OP_STOP + rjump(-4)), EOFValidationError::unreachable_instructions);
Copy link
Member

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 Show resolved Hide resolved
@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch from 64716ef to 786c173 Compare February 20, 2024 20:18
auto& successor_stack_height = stack_heights[successor_offset];
if (successor_stack_height == LOC_UNVISITED)
if (successor_offset <= current_offset) // backwards jump
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, cool.

Copy link
Collaborator

@pdobacz pdobacz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@gumb0 gumb0 force-pushed the eof-loosened-stack-validation branch from 1837002 to 9a8e160 Compare February 21, 2024 10:21
@gumb0 gumb0 merged commit 34c0169 into master Feb 21, 2024
25 checks passed
@gumb0 gumb0 deleted the eof-loosened-stack-validation branch February 21, 2024 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants