Skip to content

Commit

Permalink
Disallow unreachable code sections (#721)
Browse files Browse the repository at this point in the history
  • Loading branch information
hugo-dc authored Jan 22, 2024
1 parent b74b9db commit 83e7a32
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 40 deletions.
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ jobs:
~/tests/EIPTests/BlockchainTests/
- download_execution_tests:
repo: ipsilon/tests
rev: eof-nrf-20231128
rev: eof-unreachable-cs-20240122
legacy: false
- run:
name: "State tests (EOF)"
Expand Down
19 changes: 16 additions & 3 deletions lib/evmone/eof.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <ostream>
#include <span>
#include <stack>
#include <unordered_set>
#include <variant>
#include <vector>

Expand Down Expand Up @@ -213,8 +214,9 @@ std::variant<std::vector<EOFCodeType>, EOFValidationError> validate_types(
return types;
}

EOFValidationError validate_instructions(
evmc_revision rev, const EOF1Header& header, size_t code_idx, bytes_view container) noexcept
EOFValidationError validate_instructions(evmc_revision rev, const EOF1Header& header,
size_t code_idx, bytes_view container,
std::unordered_set<uint16_t>& accessed_code_sections) noexcept
{
const bytes_view code{header.get_code(container, code_idx)};
assert(!code.empty()); // guaranteed by EOF headers validation
Expand Down Expand Up @@ -246,6 +248,8 @@ EOFValidationError validate_instructions(
return EOFValidationError::invalid_code_section_index;
if (header.types[fid].outputs == NON_RETURNING_FUNCITON)
return EOFValidationError::callf_to_non_returning_function;
if (code_idx != fid)
accessed_code_sections.insert(fid);
i += 2;
}
else if (op == OP_RETF)
Expand All @@ -261,6 +265,8 @@ EOFValidationError validate_instructions(
// JUMPF into returning function means current function is returning.
if (header.types[fid].outputs != NON_RETURNING_FUNCITON)
is_returning = true;
if (code_idx != fid)
accessed_code_sections.insert(fid);
i += 2;
}
else if (op == OP_DATALOADN)
Expand Down Expand Up @@ -514,11 +520,13 @@ std::variant<EOF1Header, EOFValidationError> validate_eof1(
offset += code_size;
}

std::unordered_set<uint16_t> accessed_code_sections = {0};
EOF1Header header{container[2], code_sizes, code_offsets, data_size, types};

for (size_t code_idx = 0; code_idx < header.code_sizes.size(); ++code_idx)
{
const auto error_instr = validate_instructions(rev, header, code_idx, container);
const auto error_instr =
validate_instructions(rev, header, code_idx, container, accessed_code_sections);
if (error_instr != EOFValidationError::success)
return error_instr;

Expand All @@ -533,6 +541,9 @@ std::variant<EOF1Header, EOFValidationError> validate_eof1(
return EOFValidationError::invalid_max_stack_height;
}

if (accessed_code_sections.size() != header.code_sizes.size())
return EOFValidationError::unreachable_code_sections;

return header;
}

Expand Down Expand Up @@ -657,6 +668,8 @@ std::string_view get_error_message(EOFValidationError err) noexcept
return "section_headers_not_terminated";
case EOFValidationError::invalid_section_bodies_size:
return "invalid_section_bodies_size";
case EOFValidationError::unreachable_code_sections:
return "unreachable_code_sections";
case EOFValidationError::undefined_instruction:
return "undefined_instruction";
case EOFValidationError::truncated_instruction:
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/eof.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ enum class EOFValidationError
zero_section_size,
section_headers_not_terminated,
invalid_section_bodies_size,
unreachable_code_sections,
undefined_instruction,
truncated_instruction,
invalid_rjump_destination,
Expand Down
20 changes: 13 additions & 7 deletions test/unittests/eof_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,28 @@ TEST(eof, read_valid_eof1_header)
std::vector<uint16_t> code_sizes;
};

std::string code_sections_256;
for (int i = 0; i < 255; ++i)
code_sections_256 += "E5" + hex(big_endian(static_cast<uint16_t>(i + 1)));
code_sections_256 += "5B5B00";

const TestCase test_cases[] = {
{"EF00 01 010004 0200010001 040000 00 00800000 00", 4, 0, {1}},
{"EF00 01 010004 0200010006 040000 00 00800002 600160005500", 4, 0, {6}},
{"EF00 01 010004 0200010001 040001 00 00800000 00 AA", 4, 1, {1}},
{"EF00 01 010004 0200010006 040004 00 00800002 600160005500 AABBCCDD", 4, 4, {6}},
{"EF00 01 01000C 020003000100020003 040000 00 008000000080000000800000 00 5B00 5B5B00", 12,
0, {1, 2, 3}},
{"EF00 01 01000C 020003000100020003 040004 00 008000000080000000800000 00 5B00 5B5B00 "
{"EF00 01 01000C 020003000300030003 040000 00 008000000080000000800000"
"E50001 E50002 5B5B00",
12, 0, {3, 3, 3}},
{"EF00 01 01000C 020003000300030003 040004 00 008000000080000000800000 E50001 E50002 5B5B00"
"FFFFFFFF",
12, 4, {1, 2, 3}},
12, 4, {3, 3, 3}},
{"EF00 01 010004 0200010100 041000 00 00800000" + hex(255 * bytecode("5B")) + "00" +
std::string(8192, 'F'),
4, 4096, {256}},
{"EF00 01 010400 020100" + hex(256 * bytecode("0001")) + " 041000 00 " +
hex(256 * bytecode("00800000")) + std::string(512, '0') + std::string(8192, 'F'),
4 * 256, 4096, std::vector<uint16_t>(256, 1)},
{"EF00 01 010400 020100" + hex(256 * bytecode("0003")) + " 041000 00 " +
hex(256 * bytecode("00800000")) + code_sections_256 + std::string(8192, 'F'),
4 * 256, 4096, std::vector<uint16_t>(256, 3)},
};

for (const auto& test_case : test_cases)
Expand Down
159 changes: 130 additions & 29 deletions test/unittests/eof_validation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,14 @@ TEST(eof_validation, minimal_valid_EOF1_multiple_code_sections)
EXPECT_EQ(validate_eof("EF0001 010008 02000200010001 00 00800000 00800000 FE FE"),
EOFValidationError::data_section_missing);
// with data section
EXPECT_EQ(validate_eof("EF0001 010008 02000200010001 040001 00 00800000 00800000 FE FE DA"),
EXPECT_EQ(
validate_eof("EF0001 010008 02000200030001 040001 00 00800000 00800000 E50001 FE DA"),
EOFValidationError::success);

// non-void input and output types
EXPECT_EQ(validate_eof("EF0001 010010 0200040001000200020002 040000 00 "
"00800000 01800001 00010001 02030003"
"FE 5000 30e4 80e4"),
EXPECT_EQ(validate_eof("EF0001 010010 0200040005000600080002 040000 00 "
"00800001 01000001 00010003 02030003"
"5FE3000100 50E3000250E4 3080E300035050E4 80E4"),
EOFValidationError::success);
}

Expand Down Expand Up @@ -227,7 +228,7 @@ TEST(eof_validation, EOF1_truncated_section)
TEST(eof_validation, EOF1_code_section_offset)
{
const auto eof =
"EF0001 010008 02000200030001 040004 00 00800001 00800000 6001fe fe 0000 0000"_hex;
"EF0001 010008 02000200030001 040004 00 00800000 00800000 E50001 FE 00000000"_hex;
ASSERT_EQ(validate_eof(EVMC_PRAGUE, eof), EOFValidationError::success);

const auto header = read_valid_eof1_header(eof);
Expand Down Expand Up @@ -310,12 +311,19 @@ TEST(eof_validation, EOF1_invalid_section_0_type)

TEST(eof_validation, EOF1_too_many_code_sections)
{
const auto valid = "EF0001 011000" + bytecode{"020400"} + 0x400 * bytecode{"0001"} +
"040000 00" + 0x400 * bytecode{"00800000"} + 0x400 * bytecode{"FE"};
std::string cs_calling_next;
for (int i = 0; i < 1023; ++i)
cs_calling_next += "E5" + hex(big_endian(static_cast<uint16_t>(i + 1)));

const std::string code_sections_1024 = cs_calling_next + "5B5B00";
const std::string code_sections_1025 = cs_calling_next + "E504005B5B00";

const auto valid = "EF0001 011000" + bytecode{"020400"} + 0x400 * bytecode{"0003"} +
"040000 00" + 0x400 * bytecode{"00800000"} + code_sections_1024;
EXPECT_EQ(validate_eof(valid), EOFValidationError::success);

const auto invalid = "EF0001 011002" + bytecode{"020401"} + 0x401 * bytecode{"0001"} +
"040000 00" + 0x401 * bytecode{"00800000"} + 0x401 * bytecode{"FE"};
"040000 00" + 0x401 * bytecode{"00800000"} + code_sections_1025;
EXPECT_EQ(validate_eof(invalid), EOFValidationError::too_many_code_sections);
}

Expand Down Expand Up @@ -345,7 +353,8 @@ TEST(eof_validation, EOF1_undefined_opcodes)
if (opcode == OP_RETF)
{
// RETF can be tested in 2nd code section.
cont = "EF0001 010008 02000200010001 040000 00 00800000 00000000 00"_hex + OP_RETF;
cont =
"EF0001 010008 02000200040001 040000 00 00800000 00000000 E3000100 "_hex + OP_RETF;
}
else
{
Expand Down Expand Up @@ -627,14 +636,15 @@ TEST(eof_validation, deprecated_instructions)

TEST(eof_validation, max_arguments_count)
{
EXPECT_EQ(validate_eof("EF0001 010008 02000200010001 040000 00 00800000 7F7F007F 00 E4"),
EXPECT_EQ(validate_eof("EF0001 010008 02000200830001 040000 00 0080007F 7F7F007F" +
127 * bytecode{"5F"} + "E3000100 E4"),
EOFValidationError::success);

EXPECT_EQ(validate_eof("EF0001 010008 02000200010001 040000 00 00800000 80800080 00 E4"),
EOFValidationError::inputs_outputs_num_above_limit);

{
auto code = bytecode{"EF0001 010008 020002000100FF 040000 00 00800000 007F007F"} + OP_STOP +
auto code = bytecode{"EF0001 010008 020002000400FF 040000 00 0080007F 007F007F E3000100"} +
127 * bytecode{1} + OP_RETF;

EXPECT_EQ(validate_eof(code), EOFValidationError::success);
Expand All @@ -648,8 +658,8 @@ TEST(eof_validation, max_arguments_count)
}

{
auto code = bytecode{"EF0001 010008 02000200010080 040000 00 00800000 7F00007F"} + OP_STOP +
127 * OP_POP + OP_RETF;
auto code = bytecode{"EF0001 010008 02000200830080 040000 00 0080007F 7F00007F"} +
127 * bytecode{"5F"} + "E3000100" + 127 * OP_POP + OP_RETF;

EXPECT_EQ(validate_eof(code), EOFValidationError::success);
}
Expand All @@ -665,15 +675,15 @@ TEST(eof_validation, max_arguments_count)
TEST(eof_validation, max_stack_height)
{
{
auto code = bytecode{"EF0001 010008 02000200010BFE 040000 00 00800000 000003FF"} + OP_STOP +
auto code = bytecode{"EF0001 010008 02000200040BFE 040000 00 00800000 000003FF E3000100"} +
0x3FF * bytecode{1} + 0x3FF * OP_POP + OP_RETF;

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

{
auto code = "EF0001 010008 0200020BFE0001 040000 00 008003FF 00000000" +
0x3FF * bytecode{1} + 0x3FF * OP_POP + OP_STOP + OP_RETF;
auto code = "EF0001 010008 0200020C010001 040000 00 008003FF 00000000" +
0x3FF * bytecode{1} + 0x3FF * OP_POP + "E3000100" + OP_RETF;

EXPECT_EQ(validate_eof(code), EOFValidationError::success);
}
Expand Down Expand Up @@ -852,15 +862,27 @@ TEST(eof_validation, multiple_code_sections_headers)

TEST(eof_validation, many_code_sections_1023)
{
auto code = bytecode{"ef0001 010ffc 0203ff"} + 1023 * bytecode{"0001"} + "040000 00" +
1023 * bytecode{"00800000"} + bytecode{bytes(1023, OP_STOP)};
std::string code_sections_1023;
for (auto i = 0; i < 1022; ++i)
code_sections_1023 += "E5" + hex(big_endian(static_cast<uint16_t>(i + 1)));
code_sections_1023 += "5B5B00";

const auto code = "EF0001 010FFC 0203FF " + 1023 * bytecode{"0003"} + "040000 00" +
1023 * bytecode{"00800000"} + code_sections_1023;

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

TEST(eof_validation, many_code_sections_1024)
{
auto code = bytecode{"ef0001 011000 020400"} + 1024 * bytecode{"0001"} + "040000 00" +
1024 * bytecode{"00800000"} + bytecode{bytes(1024, OP_STOP)};
std::string code_sections_1024;
for (auto i = 0; i < 1023; ++i)
code_sections_1024 += "E5" + hex(big_endian(static_cast<uint16_t>(i + 1)));
code_sections_1024 += "5B5B00";

const auto code = "EF0001 011000 020400 " + 1024 * bytecode{"0003"} + "040000 00" +
1024 * bytecode{"00800000"} + code_sections_1024;

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

Expand Down Expand Up @@ -970,20 +992,21 @@ TEST(eof_validation, non_returning_status)
EOFValidationError::success);

// Returning with RETF
EXPECT_EQ(validate_eof("EF0001 010008 02000200010001 040000 00 0080000000000000 00 E4"),
EXPECT_EQ(validate_eof("EF0001 010008 02000200040001 040000 00 0080000000000000 E3000100 E4"),
EOFValidationError::success);
// Returning with JUMPF
EXPECT_EQ(
validate_eof(
"EF0001 01000c 020003000100030001 040000 00 008000000000000000000000 00 E50002 E4"),
EXPECT_EQ(validate_eof("EF0001 01000c 020003000400030001 040000 00 008000000000000000000000 "
"E3000100 E50002 E4"),
EOFValidationError::success);
// Returning with JUMPF to returning and RETF
EXPECT_EQ(validate_eof("EF0001 01000C 020003000100070001 040000 00 008000000100000100000000 00 "
"E10001E4E50002 E4"),
EXPECT_EQ(validate_eof(
"EF0001 01000C 020003000500070001 040000 00 008000010100000100000000 5FE3000100 "
"E10001E4E50002 E4"),
EOFValidationError::success);
// Returning with JUMPF to non-returning and RETF
EXPECT_EQ(
validate_eof("EF0001 010008 02000200010007 040000 00 0080000001000001 00 E10001E4E50000"),
validate_eof(
"EF0001 010008 02000200050007 040000 00 0080000101000001 5FE3000100 E10001E4E50000"),
EOFValidationError::success);

// Invalid with RETF
Expand Down Expand Up @@ -1065,7 +1088,7 @@ TEST(eof_validation, jumpf_into_returning_stack_validation)
// JUMPF into a function with the same number of outputs as current one

// Exactly required inputs on stack at JUMPF
EXPECT_EQ(validate_eof(eof_bytecode(bytecode{OP_STOP})
EXPECT_EQ(validate_eof(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 2)
.code(push0() + push0() + push0() + OP_JUMPF + "0002", 0, 2, 3)
.code(bytecode(OP_POP) + OP_RETF, 3, 2, 3)),
EOFValidationError::success);
Expand All @@ -1088,7 +1111,7 @@ TEST(eof_validation, jumpf_into_returning_stack_validation)

// Exactly required inputs on stack at JUMPF
EXPECT_EQ(
validate_eof(eof_bytecode(bytecode{OP_STOP})
validate_eof(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 2)
.code(push0() + push0() + push0() + push0() + OP_JUMPF + "0002", 0, 2, 4)
.code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3)),
EOFValidationError::success);
Expand Down Expand Up @@ -1161,3 +1184,81 @@ TEST(eof_validation, jumpf_with_inputs_stack_overflow)
EXPECT_EQ(validate_eof(code), EOFValidationError::stack_overflow);
}
}

TEST(eof_validation, unreachable_code_sections)
{
{
const auto code = eof_bytecode(OP_INVALID).code(OP_INVALID, 0, 0x80, 0);

EXPECT_EQ(validate_eof(code), EOFValidationError::unreachable_code_sections);
}

{
const auto code = eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 0)
.code(bytecode{"5B"} + OP_RETF, 0, 0, 0)
.code(bytecode{"FE"}, 0, 0x80, 0);

EXPECT_EQ(validate_eof(code), EOFValidationError::unreachable_code_sections);
}

{
const auto code = eof_bytecode(bytecode{OP_CALLF} + "0002" + OP_STOP, 0)
.code(bytecode{"FE"}, 0, 0x80, 0)
.code(bytecode{"5B"} + OP_RETF, 0, 0, 0);

EXPECT_EQ(validate_eof(code), EOFValidationError::unreachable_code_sections);
}

{
const auto code = eof_bytecode(bytecode{OP_CALLF} + "0003" + OP_STOP, 0)
.code(bytecode{"FE"}, 0, 0x80, 0)
.code(bytecode{"5B"} + OP_RETF, 0, 0, 0)
.code(bytecode{OP_CALLF} + "0002" + OP_RETF, 0, 0, 0);

EXPECT_EQ(validate_eof(code), EOFValidationError::unreachable_code_sections);
}

{
const auto code =
eof_bytecode(bytecode{OP_JUMPF} + "0000").code(bytecode{OP_JUMPF} + "0001", 0, 0x80, 0);

EXPECT_EQ(validate_eof(code), EOFValidationError::unreachable_code_sections);
}

{
const auto code = eof_bytecode(bytecode{OP_JUMPF} + "0001")
.code(bytecode{OP_STOP}, 0, 0x80, 0)
.code(bytecode{"5B"} + OP_RETF, 0, 0, 0);

EXPECT_EQ(validate_eof(code), EOFValidationError::unreachable_code_sections);
}

{
auto code_sections_256_err_001 =
eof_bytecode(bytecode{OP_JUMPF} + "0001").code(bytecode{OP_JUMPF} + "0001", 0, 0x80, 0);
auto code_sections_256_err_254 =
eof_bytecode(bytecode{OP_JUMPF} + "0001").code(bytecode{OP_JUMPF} + "0002", 0, 0x80, 0);
for (int i = 2; i < 254; ++i)
{
code_sections_256_err_001.code(
bytecode{OP_JUMPF} + hex(big_endian(static_cast<uint16_t>(i + 1))), 0, 0x80, 0);
code_sections_256_err_254.code(
bytecode{OP_JUMPF} + hex(big_endian(static_cast<uint16_t>(i + 1))), 0, 0x80, 0);
;
}

code_sections_256_err_001.code(bytecode{OP_JUMPF} + "00FF", 0, 0x80, 0)
.code(3 * bytecode{"5B"} + OP_STOP, 0, 0x80, 0);
code_sections_256_err_254.code(bytecode{OP_JUMPF} + "00FE", 0, 0x80, 0)
.code(3 * bytecode{"5B"} + OP_STOP, 0, 0x80, 0);

// Code Section 1 calls itself instead of code section 2, leaving code section 2 unreachable
EXPECT_EQ(
validate_eof(code_sections_256_err_001), EOFValidationError::unreachable_code_sections);

// Code Section 254 calls itself instead of code section 255, leaving code section 255
// unreachable
EXPECT_EQ(
validate_eof(code_sections_256_err_254), EOFValidationError::unreachable_code_sections);
}
}

0 comments on commit 83e7a32

Please sign in to comment.