Skip to content

Commit

Permalink
Implement max stack height validation
Browse files Browse the repository at this point in the history
  • Loading branch information
rodiazet committed Dec 27, 2022
1 parent f023c37 commit 4faa8ce
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 49 deletions.
132 changes: 132 additions & 0 deletions lib/evmone/eof.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <cassert>
#include <limits>
#include <numeric>
#include <stack>
#include <vector>

namespace evmone
Expand Down Expand Up @@ -284,6 +285,129 @@ bool validate_rjump_destinations(
return true;
}

std::pair<EOFValidationError, int32_t> validate_max_stack_height(
bytes_view code, size_t func_index, const std::vector<EOF1TypeHeader>& funcs_in_outs)
{
assert(code.size() > 0);
std::vector<int32_t> stack_heights = std::vector<int32_t>(code.size(), -1);
std::stack<size_t> worklist;

int32_t stack_height = 0;
stack_heights[0] = funcs_in_outs[func_index].inputs_num;
worklist.push(0);

size_t i = 0;
Opcode opcode = {};
std::vector<size_t> successors;
while (!worklist.empty())
{
i = worklist.top();
opcode = static_cast<Opcode>(code[i]);
worklist.pop();

auto stack_height_required = instr::traits[opcode].stack_height_required;
auto stack_height_change = instr::traits[opcode].stack_height_change;

if (opcode == OP_CALLF)
{
auto fid_hi = code[i + 1];
auto fid_lo = code[i + 2];
auto fid = static_cast<uint16_t>((fid_hi << 8) | fid_lo);

stack_height_required = static_cast<int8_t>(funcs_in_outs[fid].inputs_num);
auto d = funcs_in_outs[fid].outputs_num - stack_height_required;
stack_height_change = static_cast<int8_t>(d);
}

stack_height = stack_heights[i];
assert(stack_height != -1);

if (stack_height < stack_height_required)
return {EOFValidationError::stack_underflow, -1};

successors.clear();

// immediate_size for RJUMPV depends on the code. It's calculater below.
if (opcode != OP_RJUMP && !instr::traits[opcode].is_terminating && opcode != OP_RJUMPV)
{
auto next = i + instr::traits[opcode].immediate_size + 1;
if (next >= code.size())
return {EOFValidationError::no_terminating_instruction, -1};

successors.push_back(next);
}

if (opcode == OP_RJUMP || opcode == OP_RJUMPI)
{
auto target_rel_offset_hi = code[i + 1];
auto target_rel_offset_lo = code[i + 2];
auto target_rel_offset =
static_cast<int16_t>((target_rel_offset_hi << 8) | target_rel_offset_lo);
successors.push_back(
static_cast<size_t>(target_rel_offset + 3 + static_cast<int32_t>(i)));
}

if (opcode == OP_RJUMPV)
{
auto count = code[i + 1];

auto next = i + count * 2 + 2;
if (next >= code.size())
return {EOFValidationError::no_terminating_instruction, -1};

auto beg = stack_heights.begin() + static_cast<int32_t>(i) + 1;
auto end = beg + count * 2 + 1;
for (auto it = beg; it < end; ++it)
*it = -2;

successors.push_back(next);

for (uint16_t k = 0; k < count; ++k)
{
auto target_rel_offset_hi = code[i + k * 2 + 2];
auto target_rel_offset_lo = code[i + k * 2 + 3];
auto target_rel_offset =
static_cast<uint16_t>((target_rel_offset_hi << 8) | target_rel_offset_lo);
if (target_rel_offset != 0) // Already added before the loop
successors.push_back(i + 2 * count + target_rel_offset + 2);
}
}
else
{
auto beg = stack_heights.begin() + static_cast<int32_t>(i) + 1;
auto end = beg + instr::traits[opcode].immediate_size;
for (auto it = beg; it < end; ++it)
*it = -2;
}

stack_height += stack_height_change;

for (auto& s : successors)
{
if (stack_heights[s] == -1)
{
stack_heights[s] = stack_height;
worklist.push(s);
}
else if (stack_heights[s] != stack_height)
return {EOFValidationError::stack_height_mismatch, -1};
}

if (opcode == OP_RETF && stack_height != funcs_in_outs[func_index].outputs_num)
return {EOFValidationError::non_empty_stack_on_terminating_instruction, -1};
}

auto msh_it = std::max_element(stack_heights.begin(), stack_heights.end());

if (*msh_it > 1023)
return {EOFValidationError::max_stack_height_above_limit, -1};

if (std::find(stack_heights.begin(), stack_heights.end(), -1) != stack_heights.end())
return {EOFValidationError::unreachable_instructions, -1};

return {EOFValidationError::success, *msh_it};
}

std::pair<EOF1Header, EOFValidationError> validate_eof1(
evmc_revision rev, bytes_view container) noexcept
{
Expand Down Expand Up @@ -323,6 +447,14 @@ std::pair<EOF1Header, EOFValidationError> validate_eof1(

if (!validate_rjump_destinations(header, code_idx, container.begin()))
return {{}, EOFValidationError::invalid_rjump_destination};

auto msh_validation_result = validate_max_stack_height(
{&container[header.code_begin(code_idx)], header.code_sizes[code_idx]}, code_idx,
header.types);
if (msh_validation_result.first != EOFValidationError::success)
return {{}, msh_validation_result.first};
if (msh_validation_result.second != header.types[code_idx].max_stack_height)
return {{}, EOFValidationError::invalid_max_stack_height};
}

return {header, EOFValidationError::success};
Expand Down
6 changes: 6 additions & 0 deletions lib/evmone/eof.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ enum class EOFValidationError
invalid_type_section_size,
invalid_first_section_type,
invalid_max_stack_height,
no_terminating_instruction,
stack_height_mismatch,
non_empty_stack_on_terminating_instruction,
max_stack_height_above_limit,
unreachable_instructions,
stack_underflow,

impossible,
};
Expand Down
2 changes: 1 addition & 1 deletion test/unittests/analysis_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ TEST(analysis, jumpdests_groups)
TEST(analysis, example1_eof1)
{
const auto code = eof1_bytecode(
push(0x2a) + push(0x1e) + OP_MSTORE8 + OP_MSIZE + push(0) + OP_SSTORE, "deadbeef");
push(0x2a) + push(0x1e) + OP_MSTORE8 + OP_MSIZE + push(0) + OP_SSTORE, 2, "deadbeef");
const auto header = evmone::read_valid_eof1_header(bytes_view(code));
const auto analysis =
analyze(EVMC_SHANGHAI, {&code[header.code_begin(0)], header.code_sizes[0]});
Expand Down
62 changes: 40 additions & 22 deletions test/unittests/eof_validation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ TEST(eof_validation, minimal_valid_EOF1_multiple_code_sections)
EOFValidationError::success);

// non-void input and output types
EXPECT_EQ(validate_eof("EF0001 010010 0200040001000200020002 00 00000000 01000000 00010000 "
"02030000 FE 5000 3000 8000"),
EXPECT_EQ(validate_eof("EF0001 010010 0200040001000200020002 00 "
"00000000 01000001 00010001 02030003"
"FE 5000 3000 8000"),
EOFValidationError::success);
}

Expand Down Expand Up @@ -211,16 +212,16 @@ TEST(eof_validation, EOF1_truncated_section)
TEST(eof_validation, EOF1_code_section_offset)
{
const auto eof =
"EF0001 010008 02000200020001 030004 00 0000000000000000 fefe fe 0000 0000"_hex;
"EF0001 010008 02000200030001 030004 00 00000001 00000000 6001fe fe 0000 0000"_hex;
ASSERT_EQ(validate_eof(EVMC_CANCUN, eof), EOFValidationError::success);

const auto header = read_valid_eof1_header(eof);
ASSERT_EQ(header.code_sizes.size(), 2);
EXPECT_EQ(header.code_sizes[0], 2);
EXPECT_EQ(header.code_sizes[0], 3);
EXPECT_EQ(header.code_sizes[1], 1);
ASSERT_EQ(header.code_offsets.size(), 2);
EXPECT_EQ(header.code_offsets[0], 25);
EXPECT_EQ(header.code_offsets[1], 27);
EXPECT_EQ(header.code_offsets[1], 28);
}

TEST(eof_validation, EOF1_trailing_bytes)
Expand Down Expand Up @@ -303,25 +304,39 @@ TEST(eof_validation, EOF1_too_many_code_sections)

TEST(eof_validation, EOF1_undefined_opcodes)
{
auto cont = "EF0001 010004 0200010002 00 00000000 0000"_hex;

const auto& gas_table = evmone::instr::gas_costs[EVMC_SHANGHAI];

for (uint16_t opcode = 0; opcode <= 0xff; ++opcode)
{
auto cont =
"EF0001 010004 0200010014 00 00000000 6001"
"80808080808080808080808080808080 "
""_hex;

// Skip opcodes requiring immediate arguments.
// They're all valid in Shanghai and checked in other tests below.
if (opcode >= OP_PUSH1 && opcode <= OP_PUSH32)
continue;
if (opcode == OP_RJUMP || opcode == OP_RJUMPI || opcode == OP_CALLF)
if (opcode == OP_RJUMP || opcode == OP_RJUMPI || opcode == OP_RJUMPV ||
opcode == OP_CALLF || opcode == OP_RETF || opcode == OP_INVALID || opcode == OP_STOP ||
opcode == OP_RETURN || opcode == OP_REVERT || opcode == OP_SELFDESTRUCT)
continue;

cont[cont.size() - 2] = static_cast<uint8_t>(opcode);
cont += static_cast<uint8_t>(opcode);
if (!instr::traits[opcode].is_terminating)
cont += "00"_hex;
else
cont[10] = 0x13;


auto op_stack_change = instr::traits[opcode].stack_height_change;
cont[15] = static_cast<uint8_t>(op_stack_change <= 0 ? 17 : 17 + op_stack_change);

const auto expected = (gas_table[opcode] == evmone::instr::undefined ?
EOFValidationError::undefined_instruction :
EOFValidationError::success);
EXPECT_EQ(validate_eof(cont), expected) << hex(cont);
auto result = validate_eof(cont);
EXPECT_EQ(result, expected) << hex(cont);
}

EXPECT_EQ(validate_eof("EF0001 010004 0200010001 00 00000000 FE"), EOFValidationError::success);
Expand All @@ -346,6 +361,9 @@ TEST(eof_validation, EOF1_truncated_push)

const bytes code{opcode + bytes(required_bytes, 0) + uint8_t{OP_STOP}};
code_size_byte = static_cast<uint8_t>(code.size());

eof_header[15] = static_cast<uint8_t>(instr::traits[opcode].stack_height_change);

const auto container = eof_header + code;

EXPECT_EQ(validate_eof(container), EOFValidationError::success) << hex(container);
Expand All @@ -359,45 +377,45 @@ TEST(eof_validation, EOF1_valid_rjump)
validate_eof("EF0001 010004 0200010004 00 00000000 5C000000"), EOFValidationError::success);

// offset = 3
EXPECT_EQ(validate_eof("EF0001 010004 0200010007 00 00000000 5C000300000000"),
EXPECT_EQ(validate_eof("EF0001 010004 0200010009 00 00000001 5C00036001005CFFFA"),
EOFValidationError::success);

// offset = -4
EXPECT_EQ(validate_eof("EF0001 010004 0200010005 00 00000000 005CFFFC00"),
EOFValidationError::success);
EXPECT_EQ(
validate_eof("EF0001 010004 0200010004 00 00000000 5B5CFFFC"), EOFValidationError::success);
}

TEST(eof_validation, EOF1_valid_rjumpi)
{
// offset = 0
EXPECT_EQ(validate_eof("EF0001 010004 0200010006 00 00000000 60005D000000"),
EXPECT_EQ(validate_eof("EF0001 010004 0200010006 00 00000001 60005D000000"),
EOFValidationError::success);

// offset = 3
EXPECT_EQ(validate_eof("EF0001 010004 0200010009 00 00000000 60005D000300000000"),
EXPECT_EQ(validate_eof("EF0001 010004 0200010009 00 00000001 60005D00035B5B5B00"),
EOFValidationError::success);

// offset = -5
EXPECT_EQ(validate_eof("EF0001 010004 0200010006 00 00000000 60005DFFFB00"),
EXPECT_EQ(validate_eof("EF0001 010004 0200010006 00 00000001 60005DFFFB00"),
EOFValidationError::success);
}

TEST(eof_validation, EOF1_valid_rjumpv)
{
// table = [0] case = 0
EXPECT_EQ(validate_eof("EF0001 010004 0200010008 00 00000000 60005E0100006001"),
EXPECT_EQ(validate_eof("EF0001 010004 0200010009 00 00000001 60005E010000600100"),
EOFValidationError::success);

// table = [0,3] case = 0
EXPECT_EQ(validate_eof("EF0001 010004 020001000D 00 00000000 60005E02000000036001006002"),
EXPECT_EQ(validate_eof("EF0001 010004 020001000E 00 00000001 60005E0200000003600100600200"),
EOFValidationError::success);

// table = [0,3] case = 2
EXPECT_EQ(validate_eof("EF0001 010004 020001000D 00 00000000 60025E02000000036001006002"),
EXPECT_EQ(validate_eof("EF0001 010004 020001000E 00 00000001 60025E0200000003600100600200"),
EOFValidationError::success);

// table = [0,3,-10] case = 2
EXPECT_EQ(validate_eof("EF0001 010004 020001000F 00 00000000 60025E0300000003FFF66001006002"),
EXPECT_EQ(validate_eof("EF0001 010004 0200010010 00 00000001 60025E0300000003FFF6600100600200"),
EOFValidationError::success);
}

Expand Down Expand Up @@ -536,10 +554,10 @@ TEST(eof_validation, EOF1_rjumpv_invalid_destination)
EOFValidationError::invalid_rjump_destination);
}

TEST(oef_validation, EOF1_section_order)
TEST(eof_validation, EOF1_section_order)
{
// 01 02 03
EXPECT_EQ(validate_eof("EF0001 010004 0200010006 030002 00 00000000 60005D000000 AABB"),
EXPECT_EQ(validate_eof("EF0001 010004 0200010006 030002 00 00000001 60005D000000 AABB"),
EOFValidationError::success);

// 01 03 02
Expand Down
Loading

0 comments on commit 4faa8ce

Please sign in to comment.