Skip to content

Commit b790bc7

Browse files
authored
EOF validation of subcontainer kinds (#876)
Implementing ipsilon/eof#86
2 parents 85a89e5 + 37ed902 commit b790bc7

19 files changed

+388
-208
lines changed

circle.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ jobs:
457457
~/tests/EIPTests/BlockchainTests/
458458
- download_execution_tests:
459459
repo: ipsilon/tests
460-
rev: eof-toplevel
460+
rev: eof-initcode-tests
461461
legacy: false
462462
- run:
463463
name: "State tests (EOF)"

lib/evmone/baseline.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,9 @@ evmc_result execute(evmc_vm* c_vm, const evmc_host_interface* host, evmc_host_co
403403

404404
if (vm->validate_eof && rev >= EVMC_PRAGUE && is_eof_container(container))
405405
{
406-
if (validate_eof(rev, container) != EOFValidationError::success)
406+
const auto container_kind =
407+
(msg->depth == 0 ? ContainerKind::initcode : ContainerKind::runtime);
408+
if (validate_eof(rev, container_kind, container) != EOFValidationError::success)
407409
return evmc_make_result(EVMC_CONTRACT_VALIDATION_FAILURE, 0, 0, nullptr, 0);
408410
}
409411

lib/evmone/eof.cpp

+46-10
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ struct InstructionValidationResult
255255
};
256256

257257
std::variant<InstructionValidationResult, EOFValidationError> validate_instructions(
258-
evmc_revision rev, const EOF1Header& header, size_t code_idx, bytes_view container) noexcept
258+
evmc_revision rev, const EOF1Header& header, ContainerKind kind, size_t code_idx,
259+
bytes_view container) noexcept
259260
{
260261
const bytes_view code{header.get_code(container, code_idx)};
261262
assert(!code.empty()); // guaranteed by EOF headers validation
@@ -323,9 +324,20 @@ std::variant<InstructionValidationResult, EOFValidationError> validate_instructi
323324
if (container_idx >= header.container_sizes.size())
324325
return EOFValidationError::invalid_container_section_index;
325326

327+
if (op == OP_RETURNCONTRACT)
328+
{
329+
if (kind == ContainerKind::runtime || kind == ContainerKind::initcode_runtime)
330+
return EOFValidationError::incompatible_container_kind;
331+
}
332+
326333
subcontainer_references.emplace_back(container_idx, Opcode{op});
327334
++i;
328335
}
336+
else if (op == OP_RETURN || op == OP_STOP)
337+
{
338+
if (kind == ContainerKind::initcode || kind == ContainerKind::initcode_runtime)
339+
return EOFValidationError::incompatible_container_kind;
340+
}
329341
else
330342
i += instr::traits[op].immediate_size;
331343
}
@@ -572,20 +584,22 @@ std::variant<EOFValidationError, int32_t> validate_max_stack_height(
572584
return max_stack_height_it->max;
573585
}
574586

575-
EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) noexcept
587+
EOFValidationError validate_eof1(
588+
evmc_revision rev, ContainerKind main_container_kind, bytes_view main_container) noexcept
576589
{
577590
struct ContainerValidation
578591
{
579592
bytes_view bytes;
580-
bool referenced_by_eofcreate = false;
593+
ContainerKind kind;
581594
};
582595
// Queue of containers left to process
583596
std::queue<ContainerValidation> container_queue;
584-
container_queue.push({main_container, false});
597+
598+
container_queue.push({main_container, main_container_kind});
585599

586600
while (!container_queue.empty())
587601
{
588-
const auto& [container, referenced_by_eofcreate] = container_queue.front();
602+
const auto& [container, container_kind] = container_queue.front();
589603

590604
// Validate header
591605
auto error_or_header = validate_header(rev, container);
@@ -603,6 +617,7 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n
603617

604618
const auto subcontainer_count = header.container_sizes.size();
605619
std::vector<bool> subcontainer_referenced_by_eofcreate(subcontainer_count, false);
620+
std::vector<bool> subcontainer_referenced_by_returncontract(subcontainer_count, false);
606621

607622
while (!code_sections_queue.empty())
608623
{
@@ -616,7 +631,7 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n
616631

617632
// Validate instructions
618633
const auto instr_validation_result_or_error =
619-
validate_instructions(rev, header, code_idx, container);
634+
validate_instructions(rev, header, container_kind, code_idx, container);
620635
if (const auto* error =
621636
std::get_if<EOFValidationError>(&instr_validation_result_or_error))
622637
return *error;
@@ -629,6 +644,10 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n
629644
{
630645
if (opcode == OP_EOFCREATE)
631646
subcontainer_referenced_by_eofcreate[index] = true;
647+
else if (opcode == OP_RETURNCONTRACT)
648+
subcontainer_referenced_by_returncontract[index] = true;
649+
else
650+
intx::unreachable();
632651
}
633652

634653
// TODO(C++23): can use push_range()
@@ -657,7 +676,8 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n
657676
{
658677
if (main_container == container)
659678
return EOFValidationError::toplevel_container_truncated;
660-
if (referenced_by_eofcreate)
679+
if (container_kind == ContainerKind::initcode ||
680+
container_kind == ContainerKind::initcode_runtime)
661681
return EOFValidationError::eofcreate_with_truncated_container;
662682
}
663683

@@ -666,7 +686,18 @@ EOFValidationError validate_eof1(evmc_revision rev, bytes_view main_container) n
666686
{
667687
const bytes_view subcontainer{header.get_container(container, subcont_idx)};
668688

669-
container_queue.push({subcontainer, subcontainer_referenced_by_eofcreate[subcont_idx]});
689+
const bool eofcreate = subcontainer_referenced_by_eofcreate[subcont_idx];
690+
const bool returncontract = subcontainer_referenced_by_returncontract[subcont_idx];
691+
692+
// TODO Validate whether subcontainer was referenced by any instruction
693+
694+
auto subcontainer_kind = ContainerKind::initcode_runtime;
695+
if (!eofcreate)
696+
subcontainer_kind = ContainerKind::runtime;
697+
else if (!returncontract)
698+
subcontainer_kind = ContainerKind::initcode;
699+
700+
container_queue.push({subcontainer, subcontainer_kind});
670701
}
671702

672703
container_queue.pop();
@@ -852,9 +883,10 @@ uint8_t get_eof_version(bytes_view container) noexcept
852883
0;
853884
}
854885

855-
EOFValidationError validate_eof(evmc_revision rev, bytes_view container) noexcept
886+
EOFValidationError validate_eof(
887+
evmc_revision rev, ContainerKind kind, bytes_view container) noexcept
856888
{
857-
return validate_eof1(rev, container);
889+
return validate_eof1(rev, kind, container);
858890
}
859891

860892
std::string_view get_error_message(EOFValidationError err) noexcept
@@ -935,6 +967,10 @@ std::string_view get_error_message(EOFValidationError err) noexcept
935967
return "eofcreate_with_truncated_container";
936968
case EOFValidationError::toplevel_container_truncated:
937969
return "toplevel_container_truncated";
970+
case EOFValidationError::ambiguous_container_kind:
971+
return "ambiguous_container_kind";
972+
case EOFValidationError::incompatible_container_kind:
973+
return "incompatible_container_kind";
938974
case EOFValidationError::impossible:
939975
return "impossible";
940976
}

lib/evmone/eof.hpp

+14-1
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,23 @@ enum class EOFValidationError
139139
invalid_container_section_index,
140140
eofcreate_with_truncated_container,
141141
toplevel_container_truncated,
142+
ambiguous_container_kind,
143+
incompatible_container_kind,
142144

143145
impossible,
144146
};
145147

148+
enum class ContainerKind : uint8_t
149+
{
150+
/// Container that uses RETURNCONTRACT. Can be used by EOFCREATE/TXCREATE/Creation transaction.
151+
initcode,
152+
/// Container that uses STOP/RETURN. Can be returned by RETURNCONTRACT.
153+
runtime,
154+
/// Container that uses only REVERT/INVALID or does not terminate execution.
155+
/// Can be used in any context.
156+
initcode_runtime,
157+
};
158+
146159
/// Determines the EOF version of the container by inspecting container's EOF prefix.
147160
/// If the prefix is missing or invalid, 0 is returned meaning legacy code.
148161
[[nodiscard]] uint8_t get_eof_version(bytes_view container) noexcept;
@@ -153,7 +166,7 @@ enum class EOFValidationError
153166

154167
/// Validates whether given container is a valid EOF according to the rules of given revision.
155168
[[nodiscard]] EVMC_EXPORT EOFValidationError validate_eof(
156-
evmc_revision rev, bytes_view container) noexcept;
169+
evmc_revision rev, ContainerKind kind, bytes_view container) noexcept;
157170

158171
/// Returns the error message corresponding to an error code.
159172
[[nodiscard]] EVMC_EXPORT std::string_view get_error_message(EOFValidationError err) noexcept;

lib/evmone/instructions.hpp

+2-18
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,9 @@ inline constexpr auto pop = noop;
144144
inline constexpr auto jumpdest = noop;
145145

146146
template <evmc_status_code Status>
147-
inline TermResult stop_impl(StackTop /*stack*/, int64_t gas_left, ExecutionState& state) noexcept
147+
inline TermResult stop_impl(
148+
StackTop /*stack*/, int64_t gas_left, ExecutionState& /*state*/) noexcept
148149
{
149-
// STOP is forbidden inside EOFCREATE context
150-
if constexpr (Status == EVMC_SUCCESS)
151-
{
152-
if (state.msg->kind == EVMC_EOFCREATE)
153-
return {EVMC_UNDEFINED_INSTRUCTION, gas_left};
154-
}
155-
156150
return {Status, gas_left};
157151
}
158152
inline constexpr auto stop = stop_impl<EVMC_SUCCESS>;
@@ -1160,13 +1154,6 @@ inline code_iterator jumpf(StackTop stack, ExecutionState& state, code_iterator
11601154
template <evmc_status_code StatusCode>
11611155
inline TermResult return_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept
11621156
{
1163-
// RETURN is forbidden inside EOFCREATE context
1164-
if constexpr (StatusCode == EVMC_SUCCESS)
1165-
{
1166-
if (state.msg->kind == EVMC_EOFCREATE)
1167-
return {EVMC_UNDEFINED_INSTRUCTION, gas_left};
1168-
}
1169-
11701157
const auto& offset = stack[0];
11711158
const auto& size = stack[1];
11721159

@@ -1187,9 +1174,6 @@ inline TermResult returncontract(
11871174
const auto& offset = stack[0];
11881175
const auto& size = stack[1];
11891176

1190-
if (state.msg->kind != EVMC_EOFCREATE)
1191-
return {EVMC_UNDEFINED_INSTRUCTION, gas_left};
1192-
11931177
if (!check_memory(gas_left, state.memory, offset, size))
11941178
return {EVMC_OUT_OF_GAS, gas_left};
11951179

lib/evmone/instructions_calls.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ Result create_eof_impl(
399399

400400
if constexpr (Op == OP_TXCREATE)
401401
{
402-
const auto error_subcont = validate_eof(state.rev, initcontainer);
402+
const auto error_subcont = validate_eof(state.rev, ContainerKind::initcode, initcontainer);
403403
if (error_subcont != EOFValidationError::success)
404404
return {EVMC_SUCCESS, gas_left}; // "Light" failure.
405405
}

test/eofparse/eofparse.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ int main()
5454
}
5555

5656
const auto& eof = *o;
57-
const auto err = evmone::validate_eof(EVMC_PRAGUE, eof);
57+
const auto err = evmone::validate_eof(EVMC_PRAGUE, evmone::ContainerKind::runtime, eof);
5858
if (err != evmone::EOFValidationError::success)
5959
{
6060
std::cout << "err: " << evmone::get_error_message(err) << "\n";

test/eofparsefuzz/eofparsefuzz.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size) noexcept
88
{
99
const evmone::bytes_view eof{data, data_size};
10-
if (evmone::validate_eof(EVMC_PRAGUE, eof) == evmone::EOFValidationError::success)
10+
if (evmone::validate_eof(EVMC_PRAGUE, evmone::ContainerKind::runtime, eof) ==
11+
evmone::EOFValidationError::success)
1112
(void)evmone::read_valid_eof1_header(eof);
1213
return 0;
1314
}

test/eoftest/eoftest_runner.cpp

+19-3
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,39 @@ struct EOFValidationTest
2222
{
2323
struct Expectation
2424
{
25-
evmc_revision rev;
26-
bool result;
25+
evmc_revision rev = EVMC_PRAGUE;
26+
bool result = false;
2727
};
2828
std::string name;
2929
evmc::bytes code;
30+
ContainerKind kind = ContainerKind::runtime;
3031
std::vector<Expectation> expectations;
3132
};
3233
std::unordered_map<std::string, Case> cases;
3334
};
3435

36+
ContainerKind container_kind_from_string(std::string_view s)
37+
{
38+
if (s == "runtime")
39+
return ContainerKind::runtime;
40+
else if (s == "initcode")
41+
return ContainerKind::initcode;
42+
else if (s == "initcode_runtime")
43+
return ContainerKind::initcode_runtime;
44+
else
45+
throw std::invalid_argument{"unknown container kind"};
46+
}
47+
3548
void from_json(const json::json& j, EOFValidationTest::Case& o)
3649
{
3750
const auto op_code = evmc::from_hex(j.at("code").get<std::string>());
3851
if (!op_code)
3952
throw std::invalid_argument{"code is invalid hex string"};
4053
o.code = *op_code;
4154

55+
if (const auto it_kind = j.find("kind"); it_kind != j.end())
56+
o.kind = container_kind_from_string(it_kind->get<std::string>());
57+
4258
for (const auto& [rev, result] : j.at("results").items())
4359
{
4460
o.expectations.push_back({to_rev(rev), result.at("result").get<bool>()});
@@ -67,7 +83,7 @@ void run_eof_test(std::istream& input)
6783
{
6884
for (const auto& expectation : cases.expectations)
6985
{
70-
const auto result = evmone::validate_eof(expectation.rev, cases.code);
86+
const auto result = evmone::validate_eof(expectation.rev, cases.kind, cases.code);
7187
const bool b_result = (result == EOFValidationError::success);
7288
EXPECT_EQ(b_result, expectation.result)
7389
<< name << " " << expectation.rev << " " << hex(cases.code);

test/state/host.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ std::optional<evmc_message> Host::prepare_message(evmc_message msg) noexcept
270270
msg.input_data = msg.input_data + container_size;
271271
msg.input_size = msg.input_size - container_size;
272272

273-
if (validate_eof(m_rev, {msg.code, msg.code_size}) !=
273+
if (validate_eof(m_rev, ContainerKind::initcode, {msg.code, msg.code_size}) !=
274274
EOFValidationError::success)
275275
return {}; // Light early exception.
276276

@@ -367,7 +367,8 @@ evmc::Result Host::create(const evmc_message& msg) noexcept
367367
// It must be valid EOF, which was validated before execution.
368368
if (msg.kind != EVMC_EOFCREATE)
369369
return evmc::Result{EVMC_CONTRACT_VALIDATION_FAILURE};
370-
assert(validate_eof(m_rev, code) == EOFValidationError::success);
370+
assert(
371+
validate_eof(m_rev, ContainerKind::runtime, code) == EOFValidationError::success);
371372
}
372373
else if (m_rev >= EVMC_LONDON)
373374
{

test/statetest/statetest_loader.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ void validate_state(const TestState& state, evmc_revision rev)
470470
{
471471
if (rev >= EVMC_PRAGUE)
472472
{
473-
if (const auto result = validate_eof(rev, acc.code);
473+
if (const auto result = validate_eof(rev, ContainerKind::runtime, acc.code);
474474
result != EOFValidationError::success)
475475
{
476476
throw std::invalid_argument(

test/unittests/eof_test.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ TEST(eof, read_valid_eof1_header)
8484
for (const auto& test_case : test_cases)
8585
{
8686
const auto code = from_spaced_hex(test_case.code).value();
87-
EXPECT_EQ(validate_eof(EVMC_PRAGUE, code), EOFValidationError::success) << test_case.code;
87+
EXPECT_EQ(
88+
validate_eof(EVMC_PRAGUE, ContainerKind::runtime, code), EOFValidationError::success)
89+
<< test_case.code;
8890

8991
const auto header = read_valid_eof1_header(code);
9092
EXPECT_EQ(header.code_sizes, test_case.code_sizes) << test_case.code;

0 commit comments

Comments
 (0)