6
6
#include " baseline_instruction_table.hpp"
7
7
#include " instructions_traits.hpp"
8
8
9
+ #include < algorithm>
9
10
#include < array>
10
11
#include < cassert>
11
12
#include < limits>
12
13
#include < numeric>
13
14
#include < span>
15
+ #include < stack>
14
16
#include < variant>
15
17
#include < vector>
16
18
@@ -25,8 +27,9 @@ constexpr uint8_t CODE_SECTION = 0x02;
25
27
constexpr uint8_t DATA_SECTION = 0x03 ;
26
28
constexpr uint8_t MAX_SECTION = DATA_SECTION;
27
29
constexpr auto CODE_SECTION_NUMBER_LIMIT = 1024 ;
28
- constexpr auto MAX_STACK_HEIGHT = 0x0400 ;
30
+ constexpr auto MAX_STACK_HEIGHT = 0x03FF ;
29
31
constexpr auto OUTPUTS_INPUTS_NUMBER_LIMIT = 0x7F ;
32
+ constexpr auto REL_OFFSET_SIZE = sizeof (int16_t );
30
33
31
34
using EOFSectionHeaders = std::array<std::vector<uint16_t >, MAX_SECTION + 1 >;
32
35
@@ -230,8 +233,6 @@ EOFValidationError validate_instructions(evmc_revision rev, bytes_view code) noe
230
233
// / Requires that the input is validated against truncation.
231
234
bool validate_rjump_destinations (bytes_view code) noexcept
232
235
{
233
- static constexpr auto REL_OFFSET_SIZE = sizeof (int16_t );
234
-
235
236
// Collect relative jump destinations and immediate locations
236
237
const auto code_size = code.size ();
237
238
// list of all possible absolute rjumps destinations positions
@@ -286,6 +287,121 @@ bool validate_rjump_destinations(bytes_view code) noexcept
286
287
return true ;
287
288
}
288
289
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
+
289
405
std::variant<EOF1Header, EOFValidationError> validate_eof1 (
290
406
evmc_revision rev, bytes_view container) noexcept
291
407
{
@@ -325,6 +441,13 @@ std::variant<EOF1Header, EOFValidationError> validate_eof1(
325
441
326
442
if (!validate_rjump_destinations (header.get_code (container, code_idx)))
327
443
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;
328
451
}
329
452
330
453
return header;
@@ -469,6 +592,18 @@ std::string_view get_error_message(EOFValidationError err) noexcept
469
592
return " max_stack_height_above_limit" ;
470
593
case EOFValidationError::inputs_outputs_num_above_limit:
471
594
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" ;
472
607
case EOFValidationError::impossible:
473
608
return " impossible" ;
474
609
}
0 commit comments