Skip to content

Commit

Permalink
eof: Loosen constant stack height requirement
Browse files Browse the repository at this point in the history
  • Loading branch information
gumb0 committed Dec 20, 2023
1 parent 9b99236 commit 8e96cc5
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 29 deletions.
82 changes: 54 additions & 28 deletions lib/evmone/eof.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,13 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(

// Special values used for detecting errors.
static constexpr int32_t LOC_UNVISITED = -1; // Unvisited byte.
static constexpr int32_t LOC_IMMEDIATE = -2; // Immediate byte.

// Stack height in the header is limited to uint16_t,
// but keeping larger size for ease of calculation.
std::vector<int32_t> stack_heights(code.size(), LOC_UNVISITED);
stack_heights[0] = code_types[func_index].inputs;
std::vector<int32_t> stack_heights_min(code.size(), LOC_UNVISITED);
std::vector<int32_t> stack_heights_max(code.size(), LOC_UNVISITED);
stack_heights_min[0] = code_types[func_index].inputs;
stack_heights_max[0] = code_types[func_index].inputs;

for (size_t i = 0; i < code.size();)
{
Expand All @@ -361,17 +362,19 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
auto stack_height_required = instr::traits[opcode].stack_height_required;
auto stack_height_change = instr::traits[opcode].stack_height_change;

auto stack_height = stack_heights[i];
if (stack_height == LOC_UNVISITED)
auto stack_height_min = stack_heights_min[i];
if (stack_height_min == LOC_UNVISITED)
return EOFValidationError::unreachable_instructions;
auto stack_height_max = stack_heights_max[i];
assert(stack_height_max != LOC_UNVISITED);

if (opcode == OP_CALLF)
{
const auto fid = read_uint16_be(&code[i + 1]);

stack_height_required = static_cast<int8_t>(code_types[fid].inputs);

if (stack_height + code_types[fid].max_stack_height - stack_height_required >
if (stack_height_max + code_types[fid].max_stack_height - stack_height_required >
STACK_SIZE_LIMIT)
return EOFValidationError::stack_overflow;

Expand All @@ -384,7 +387,7 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
{
const auto fid = read_uint16_be(&code[i + 1]);

if (stack_height + code_types[fid].max_stack_height - code_types[fid].inputs >
if (stack_height_max + code_types[fid].max_stack_height - code_types[fid].inputs >
STACK_SIZE_LIMIT)
return EOFValidationError::stack_overflow;

Expand All @@ -400,42 +403,62 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
stack_height_required =
static_cast<int8_t>(code_types[func_index].outputs + code_types[fid].inputs -
code_types[fid].outputs);
if (stack_heights[i] > stack_height_required)

// JUMPF to returning function requires exact number of stack items
// and is allowed only in constant stack segment.
if (stack_height_max > stack_height_required)
return EOFValidationError::stack_higher_than_outputs_required;
}
}
else if (opcode == OP_RETF)
{
stack_height_required = static_cast<int8_t>(code_types[func_index].outputs);
if (stack_height > code_types[func_index].outputs)
// RETF allowed only in constant stack segment
if (stack_height_max > stack_height_required)
return EOFValidationError::stack_higher_than_outputs_required;
}

if (stack_height < stack_height_required)
if (stack_height_min < stack_height_required)
return EOFValidationError::stack_underflow;

stack_height += stack_height_change;
stack_height_min = stack_height_min + stack_height_change;
stack_height_max = stack_height_max + stack_height_change;

// Determine size of immediate, including the special case of RJUMPV.
const size_t imm_size = (opcode == OP_RJUMPV) ?
(1 + /*count*/ (size_t{code[i + 1]} + 1) * REL_OFFSET_SIZE) :
instr::traits[opcode].immediate_size;

// Mark immediate locations.
std::fill_n(&stack_heights[i + 1], imm_size, LOC_IMMEDIATE);

// Validates the successor instruction and updates its stack height.
const auto validate_successor = [&stack_heights](size_t successor_offset,
int32_t expected_stack_height) {
auto& successor_stack_height = stack_heights[successor_offset];
if (successor_stack_height == LOC_UNVISITED)
{
successor_stack_height = expected_stack_height;
const auto validate_successor =
[&stack_heights_min, &stack_heights_max](size_t current_offset, size_t successor_offset,
int32_t expected_stack_height_min, int32_t expected_stack_height_max) {
auto& successor_stack_height_min = stack_heights_min[successor_offset];
auto& successor_stack_height_max = stack_heights_max[successor_offset];
if (successor_offset <= current_offset) // backwards jump
{
assert(successor_stack_height_min != LOC_UNVISITED);
assert(successor_stack_height_max != LOC_UNVISITED);
// TODO can be 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;
}
else if (successor_stack_height_min == LOC_UNVISITED) // forwards jump, new target
{
successor_stack_height_min = expected_stack_height_min;
successor_stack_height_max = expected_stack_height_max;
}
else // forwards jump, target known
{
successor_stack_height_min =
std::min(expected_stack_height_min, successor_stack_height_min);
successor_stack_height_max =
std::max(expected_stack_height_max, successor_stack_height_max);
}
return true;
}
else
return successor_stack_height == expected_stack_height;
};
};

const auto next = i + imm_size + 1; // Offset of the next instruction (may be invalid).

Expand All @@ -444,7 +467,7 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
{
if (next >= code.size())
return EOFValidationError::no_terminating_instruction;
if (!validate_successor(next, stack_height))
if (!validate_successor(i, next, stack_height_min, stack_height_max))
return EOFValidationError::stack_height_mismatch;
}

Expand All @@ -453,7 +476,8 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
{
const auto target_rel_offset = read_int16_be(&code[i + 1]);
const auto target = static_cast<int32_t>(i) + target_rel_offset + 3;
if (!validate_successor(static_cast<size_t>(target), stack_height))
if (!validate_successor(
i, static_cast<size_t>(target), stack_height_min, stack_height_max))
return EOFValidationError::stack_height_mismatch;
}
else if (opcode == OP_RJUMPV)
Expand All @@ -465,15 +489,17 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
{
const auto target_rel_offset = read_int16_be(&code[i + k * REL_OFFSET_SIZE + 2]);
const auto target = static_cast<int32_t>(next) + target_rel_offset;
if (!validate_successor(static_cast<size_t>(target), stack_height))
if (!validate_successor(
i, static_cast<size_t>(target), stack_height_min, stack_height_max))
return EOFValidationError::stack_height_mismatch;
}
}

i = next;
}

const auto max_stack_height = *std::max_element(stack_heights.begin(), stack_heights.end());
const auto max_stack_height =
*std::max_element(stack_heights_max.begin(), stack_heights_max.end());
return max_stack_height;
}

Expand Down
2 changes: 1 addition & 1 deletion test/unittests/eof_validation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ TEST(eof_validation, max_stack_height)
{
auto code = eof_bytecode(rjumpi(2, 0) + 1 + OP_STOP, 1);

EXPECT_EQ(validate_eof(code), EOFValidationError::stack_height_mismatch);
EXPECT_EQ(validate_eof(code), EOFValidationError::success);
}

{
Expand Down

0 comments on commit 8e96cc5

Please sign in to comment.