Skip to content

Commit cf08fe5

Browse files
rodiazetchfastaxic
committed
eof: Enable max stack height validation (EIP-5450)
Specified by EIP-5450 "EOF - Stack Validation" https://eips.ethereum.org/EIPS/eip-5450. Co-authored-by: Pawel Bylica <[email protected]> Co-authored-by: Alex Beregszaszi <[email protected]>
1 parent 7feccf8 commit cf08fe5

File tree

8 files changed

+324
-52
lines changed

8 files changed

+324
-52
lines changed

lib/evmone/eof.cpp

+138-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
#include "baseline_instruction_table.hpp"
77
#include "instructions_traits.hpp"
88

9+
#include <algorithm>
910
#include <array>
1011
#include <cassert>
1112
#include <limits>
1213
#include <numeric>
1314
#include <span>
15+
#include <stack>
1416
#include <variant>
1517
#include <vector>
1618

@@ -25,8 +27,9 @@ constexpr uint8_t CODE_SECTION = 0x02;
2527
constexpr uint8_t DATA_SECTION = 0x03;
2628
constexpr uint8_t MAX_SECTION = DATA_SECTION;
2729
constexpr auto CODE_SECTION_NUMBER_LIMIT = 1024;
28-
constexpr auto MAX_STACK_HEIGHT = 0x0400;
30+
constexpr auto MAX_STACK_HEIGHT = 0x03FF;
2931
constexpr auto OUTPUTS_INPUTS_NUMBER_LIMIT = 0x7F;
32+
constexpr auto REL_OFFSET_SIZE = sizeof(int16_t);
3033

3134
using EOFSectionHeaders = std::array<std::vector<uint16_t>, MAX_SECTION + 1>;
3235

@@ -230,8 +233,6 @@ EOFValidationError validate_instructions(evmc_revision rev, bytes_view code) noe
230233
/// Requires that the input is validated against truncation.
231234
bool validate_rjump_destinations(bytes_view code) noexcept
232235
{
233-
static constexpr auto REL_OFFSET_SIZE = sizeof(int16_t);
234-
235236
// Collect relative jump destinations and immediate locations
236237
const auto code_size = code.size();
237238
// list of all possible absolute rjumps destinations positions
@@ -286,6 +287,121 @@ bool validate_rjump_destinations(bytes_view code) noexcept
286287
return true;
287288
}
288289

290+
/// Requires that the input is validated against truncation.
291+
std::variant<EOFValidationError, int32_t> validate_max_stack_height(
292+
bytes_view code, size_t func_index, const std::vector<EOFCodeType>& code_types)
293+
{
294+
assert(!code.empty());
295+
296+
// Special values used for detecting errors.
297+
static constexpr int32_t LOC_UNVISITED = -1; // Unvisited byte.
298+
static constexpr int32_t LOC_IMMEDIATE = -2; // Immediate byte.
299+
300+
// Stack height in the header is limited to uint16_t,
301+
// but keeping larger size for ease of calculation.
302+
std::vector<int32_t> stack_heights(code.size(), LOC_UNVISITED);
303+
stack_heights[0] = code_types[func_index].inputs;
304+
305+
std::stack<size_t> worklist;
306+
worklist.push(0);
307+
308+
while (!worklist.empty())
309+
{
310+
const auto i = worklist.top();
311+
worklist.pop();
312+
313+
const auto opcode = static_cast<Opcode>(code[i]);
314+
315+
auto stack_height_required = instr::traits[opcode].stack_height_required;
316+
auto stack_height_change = instr::traits[opcode].stack_height_change;
317+
318+
if (opcode == OP_CALLF)
319+
{
320+
const auto fid = read_uint16_be(&code[i + 1]);
321+
322+
if (fid >= code_types.size())
323+
return EOFValidationError::invalid_code_section_index;
324+
325+
stack_height_required = static_cast<int8_t>(code_types[fid].inputs);
326+
stack_height_change =
327+
static_cast<int8_t>(code_types[fid].outputs - stack_height_required);
328+
}
329+
330+
auto stack_height = stack_heights[i];
331+
assert(stack_height != LOC_UNVISITED);
332+
333+
if (stack_height < stack_height_required)
334+
return EOFValidationError::stack_underflow;
335+
336+
stack_height += stack_height_change;
337+
338+
// Determine size of immediate, including the special case of RJUMPV.
339+
const size_t imm_size = (opcode == OP_RJUMPV) ?
340+
(1 + /*count*/ size_t{code[i + 1]} * REL_OFFSET_SIZE) :
341+
instr::traits[opcode].immediate_size;
342+
343+
// Mark immediate locations.
344+
std::fill_n(&stack_heights[i + 1], imm_size, LOC_IMMEDIATE);
345+
346+
// Validates the successor instruction and updates its stack height.
347+
const auto validate_successor = [&stack_heights, &worklist](size_t successor_offset,
348+
int32_t expected_stack_height) {
349+
auto& successor_stack_height = stack_heights[successor_offset];
350+
if (successor_stack_height == LOC_UNVISITED)
351+
{
352+
successor_stack_height = expected_stack_height;
353+
worklist.push(successor_offset);
354+
return true;
355+
}
356+
else
357+
return successor_stack_height == expected_stack_height;
358+
};
359+
360+
const auto next = i + imm_size + 1; // Offset of the next instruction (may be invalid).
361+
362+
// Check validity of next instruction. We skip RJUMP and terminating instructions.
363+
if (!instr::traits[opcode].is_terminating && opcode != OP_RJUMP)
364+
{
365+
if (next >= code.size())
366+
return EOFValidationError::no_terminating_instruction;
367+
if (!validate_successor(next, stack_height))
368+
return EOFValidationError::stack_height_mismatch;
369+
}
370+
371+
// Validate non-fallthrough successors of relative jumps.
372+
if (opcode == OP_RJUMP || opcode == OP_RJUMPI)
373+
{
374+
const auto target_rel_offset = read_int16_be(&code[i + 1]);
375+
const auto target = static_cast<int32_t>(i) + target_rel_offset + 3;
376+
if (!validate_successor(static_cast<size_t>(target), stack_height))
377+
return EOFValidationError::stack_height_mismatch;
378+
}
379+
else if (opcode == OP_RJUMPV)
380+
{
381+
const auto count = code[i + 1];
382+
383+
// Insert all jump targets.
384+
for (size_t k = 0; k < count; ++k)
385+
{
386+
const auto target_rel_offset = read_int16_be(&code[i + k * REL_OFFSET_SIZE + 2]);
387+
const auto target = static_cast<int32_t>(next) + target_rel_offset;
388+
if (!validate_successor(static_cast<size_t>(target), stack_height))
389+
return EOFValidationError::stack_height_mismatch;
390+
}
391+
}
392+
393+
if (opcode == OP_RETF && stack_height != code_types[func_index].outputs)
394+
return EOFValidationError::non_empty_stack_on_terminating_instruction;
395+
}
396+
397+
const auto max_stack_height = *std::max_element(stack_heights.begin(), stack_heights.end());
398+
399+
if (std::find(stack_heights.begin(), stack_heights.end(), LOC_UNVISITED) != stack_heights.end())
400+
return EOFValidationError::unreachable_instructions;
401+
402+
return max_stack_height;
403+
}
404+
289405
std::variant<EOF1Header, EOFValidationError> validate_eof1(
290406
evmc_revision rev, bytes_view container) noexcept
291407
{
@@ -325,6 +441,13 @@ std::variant<EOF1Header, EOFValidationError> validate_eof1(
325441

326442
if (!validate_rjump_destinations(header.get_code(container, code_idx)))
327443
return EOFValidationError::invalid_rjump_destination;
444+
445+
auto msh_or_error =
446+
validate_max_stack_height(header.get_code(container, code_idx), code_idx, header.types);
447+
if (const auto* error = std::get_if<EOFValidationError>(&msh_or_error))
448+
return *error;
449+
if (std::get<int32_t>(msh_or_error) != header.types[code_idx].max_stack_height)
450+
return EOFValidationError::invalid_max_stack_height;
328451
}
329452

330453
return header;
@@ -469,6 +592,18 @@ std::string_view get_error_message(EOFValidationError err) noexcept
469592
return "max_stack_height_above_limit";
470593
case EOFValidationError::inputs_outputs_num_above_limit:
471594
return "inputs_outputs_num_above_limit";
595+
case EOFValidationError::no_terminating_instruction:
596+
return "no_terminating_instruction";
597+
case EOFValidationError::stack_height_mismatch:
598+
return "stack_height_mismatch";
599+
case EOFValidationError::non_empty_stack_on_terminating_instruction:
600+
return "non_empty_stack_on_terminating_instruction";
601+
case EOFValidationError::unreachable_instructions:
602+
return "unreachable_instructions";
603+
case EOFValidationError::stack_underflow:
604+
return "stack_underflow";
605+
case EOFValidationError::invalid_code_section_index:
606+
return "invalid_code_section_index";
472607
case EOFValidationError::impossible:
473608
return "impossible";
474609
}

lib/evmone/eof.hpp

+6
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,14 @@ enum class EOFValidationError
7878
invalid_type_section_size,
7979
invalid_first_section_type,
8080
invalid_max_stack_height,
81+
no_terminating_instruction,
82+
stack_height_mismatch,
83+
non_empty_stack_on_terminating_instruction,
8184
max_stack_height_above_limit,
8285
inputs_outputs_num_above_limit,
86+
unreachable_instructions,
87+
stack_underflow,
88+
invalid_code_section_index,
8389

8490
impossible,
8591
};

test/unittests/analysis_test.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ TEST(analysis, jumpdests_groups)
258258
TEST(analysis, example1_eof1)
259259
{
260260
const auto code = eof1_bytecode(
261-
push(0x2a) + push(0x1e) + OP_MSTORE8 + OP_MSIZE + push(0) + OP_SSTORE, "deadbeef");
261+
push(0x2a) + push(0x1e) + OP_MSTORE8 + OP_MSIZE + push(0) + OP_SSTORE, 2, "deadbeef");
262262
const auto header = evmone::read_valid_eof1_header(code);
263263
const auto analysis = analyze(EVMC_CANCUN, header.get_code(code, 0));
264264

0 commit comments

Comments
 (0)