From 3d34b60a2e26bc904d621e8ac13ea231a9eaed85 Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Sun, 13 Oct 2024 14:58:19 -0700 Subject: [PATCH] Verifier integration Signed-off-by: Alan Jowett --- .gitmodules | 3 + CMakeLists.txt | 1 + custom_tests/srcs/ubpf_test_debug_function.cc | 3 +- external/ebpf-verifier | 1 + libfuzzer/CMakeLists.txt | 7 + libfuzzer/libfuzz_harness.cc | 234 +++++++++++++++++- vm/inc/ubpf.h | 3 +- vm/ubpf_vm.c | 2 +- 8 files changed, 249 insertions(+), 5 deletions(-) create mode 160000 external/ebpf-verifier diff --git a/.gitmodules b/.gitmodules index f0b96ce4e..032b3142a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "external/bpf_conformance"] path = external/bpf_conformance url = https://github.com/Alan-Jowett/bpf_conformance.git +[submodule "external/ebpf-verifier"] + path = external/ebpf-verifier + url = https://github.com/vbpf/ebpf-verifier.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 8858af4e7..cf3c69eaf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,4 +43,5 @@ endif() if (UBPF_ENABLE_LIBFUZZER) add_subdirectory("libfuzzer") + add_subdirectory("external/ebpf-verifier") endif() diff --git a/custom_tests/srcs/ubpf_test_debug_function.cc b/custom_tests/srcs/ubpf_test_debug_function.cc index 5b234e41e..59440dd1d 100644 --- a/custom_tests/srcs/ubpf_test_debug_function.cc +++ b/custom_tests/srcs/ubpf_test_debug_function.cc @@ -22,8 +22,9 @@ typedef struct _vm_state { } vm_state_t; void -debug_callout(void* context, int program_counter, const uint64_t registers[16], const uint8_t* stack_start, size_t stack_length) +debug_callout(void* context, int program_counter, const uint64_t registers[16], const uint8_t* stack_start, size_t stack_length, uint64_t register_mask) { + UNREFERENCED_PARAMETER(register_mask); std::vector* vm_states = static_cast*>(context); vm_state_t vm_state{}; diff --git a/external/ebpf-verifier b/external/ebpf-verifier new file mode 160000 index 000000000..365643736 --- /dev/null +++ b/external/ebpf-verifier @@ -0,0 +1 @@ +Subproject commit 36564373626989b8cbc6918f9811cff82cff00a0 diff --git a/libfuzzer/CMakeLists.txt b/libfuzzer/CMakeLists.txt index 41f21e1cd..aa8c51501 100644 --- a/libfuzzer/CMakeLists.txt +++ b/libfuzzer/CMakeLists.txt @@ -13,17 +13,24 @@ add_executable( libfuzz_harness.cc ) +message(STATUS "GSL_INCLUDE_DIRS: ${CMAKE_BINARY_DIR}/gsl-src/include") + target_include_directories("ubpf_fuzzer" PRIVATE "${CMAKE_SOURCE_DIR}/vm" "${CMAKE_BINARY_DIR}/vm" + "${CMAKE_BINARY_DIR}/_deps/gsl-src/include" "${CMAKE_SOURCE_DIR}/vm/inc" "${CMAKE_BINARY_DIR}/vm/inc" "${CMAKE_SOURCE_DIR}/ubpf_plugin" + "${CMAKE_SOURCE_DIR}/external/ebpf-verifier/src" + "${CMAKE_SOURCE_DIR}/external/ebpf-verifier/src/crab" + "${CMAKE_SOURCE_DIR}/external/ebpf-verifier/src/crab_utils" ) target_link_libraries( ubpf_fuzzer ubpf ubpf_settings + ebpfverifier ) diff --git a/libfuzzer/libfuzz_harness.cc b/libfuzzer/libfuzz_harness.cc index 45f5be3fd..d61086b03 100644 --- a/libfuzzer/libfuzz_harness.cc +++ b/libfuzzer/libfuzz_harness.cc @@ -10,15 +10,107 @@ #include #include +#include "asm_unmarshal.hpp" +#include "crab_verifier.hpp" +#include "platform.hpp" + extern "C" { +#define ebpf_inst ebpf_inst_ubpf #include "ebpf.h" #include "ubpf.h" +#undef ebpf_inst } #include "test_helpers.h" #include +typedef struct _ubpf_context +{ + uint64_t data; + uint64_t data_end; + uint64_t stack_start; + uint64_t stack_end; +} ubpf_context_t; + +ebpf_context_descriptor_t g_ebpf_context_descriptor_ubpf = { + .size = sizeof(ubpf_context_t), + .data = 0, + .end = 8, + .meta = -1, +}; + + +EbpfProgramType g_ubpf_program_type = { + .name = "ubpf", + .context_descriptor = &g_ebpf_context_descriptor_ubpf, + .platform_specific_data = 0, + .section_prefixes = {}, + .is_privileged = false, +}; + +EbpfProgramType ubpf_get_program_type(const std::string& section, const std::string& path) +{ + UNREFERENCED_PARAMETER(section); + UNREFERENCED_PARAMETER(path); + return g_ubpf_program_type; +} + +EbpfMapType ubpf_get_map_type(uint32_t platform_specific_type) +{ + UNREFERENCED_PARAMETER(platform_specific_type); + return {}; +} + +EbpfHelperPrototype ubpf_get_helper_prototype(int32_t n) +{ + UNREFERENCED_PARAMETER(n); + return {}; +} + +bool ubpf_is_helper_usable(int32_t n) +{ + UNREFERENCED_PARAMETER(n); + return false; +} + +void ubpf_parse_maps_section(std::vector& map_descriptors, const char* data, + size_t map_record_size, int map_count, + const struct ebpf_platform_t* platform, ebpf_verifier_options_t options) +{ + UNREFERENCED_PARAMETER(map_descriptors); + UNREFERENCED_PARAMETER(data); + UNREFERENCED_PARAMETER(map_record_size); + UNREFERENCED_PARAMETER(map_count); + UNREFERENCED_PARAMETER(platform); + UNREFERENCED_PARAMETER(options); + throw std::runtime_error("parse_maps_section not implemented"); +} +void ubpf_resolve_inner_map_references(std::vector& map_descriptors) +{ + UNREFERENCED_PARAMETER(map_descriptors); + throw std::runtime_error("resolve_inner_map_references not implemented"); +} + +EbpfMapDescriptor& ubpf_get_map_descriptor(int map_fd) +{ + UNREFERENCED_PARAMETER(map_fd); + throw std::runtime_error("get_map_descriptor not implemented"); +} + +ebpf_platform_t g_ebpf_platform_ubpf_fuzzer = { + .get_program_type = ubpf_get_program_type, + .get_helper_prototype = ubpf_get_helper_prototype, + .is_helper_usable = ubpf_is_helper_usable, + .map_record_size = 0, + .parse_maps_section = ubpf_parse_maps_section, + .get_map_descriptor = ubpf_get_map_descriptor, + .get_map_type = ubpf_get_map_type, + .resolve_inner_map_references = ubpf_resolve_inner_map_references, + .supported_conformance_groups = bpf_conformance_groups_t::default_groups, +}; + + uint64_t test_helpers_dispatcher(uint64_t p0, uint64_t p1,uint64_t p2,uint64_t p3, uint64_t p4, unsigned int idx, void* cookie) { UNREFERENCED_PARAMETER(cookie); return helper_functions[idx](p0, p1, p2, p3, p4); @@ -42,6 +134,45 @@ int null_printf(FILE* stream, const char* format, ...) return 0; } +bool verify_bpf_byte_code(const std::vector& program_code) +try +{ + std::ostringstream error; + auto instruction_array = reinterpret_cast(program_code.data()); + size_t instruction_count = program_code.size() / sizeof(ebpf_inst); + const ebpf_platform_t* platform = &g_ebpf_platform_ubpf_fuzzer; + std::vector instructions{instruction_array, instruction_array + instruction_count}; + program_info info{ + .platform = platform, + .type = g_ubpf_program_type, + }; + std::string section; + std::string file; + raw_program raw_prog{file, section, 0, {}, instructions, info}; + + std::variant prog_or_error = unmarshal(raw_prog); + if (!std::holds_alternative(prog_or_error)) { + //std::cout << "Failed to unmarshal program : " << std::get(prog_or_error) << std::endl; + return false; + } + InstructionSeq& prog = std::get(prog_or_error); + + // First try optimized for the success case. + ebpf_verifier_options_t options = ebpf_verifier_default_options; + ebpf_verifier_stats_t stats; + options.check_termination = true; + options.store_pre_invariants = true; + options.simplify = false; + + std::ostringstream error_stream; + + return ebpf_verify_program(error_stream, prog, raw_prog.info, &options, &stats); +} +catch (const std::exception& ex) +{ + return false; +} + typedef std::unique_ptr ubpf_vm_ptr; /** @@ -95,6 +226,94 @@ ubpf_vm_ptr create_ubpf_vm(const std::vector& program_code) return vm; } + +bool ubpf_is_packet(ubpf_context_t* context, uint64_t register_value) +{ + return register_value >= context->data && register_value < context->data_end; +} + +bool ubpf_is_context(ubpf_context_t* context, uint64_t register_value) +{ + return register_value >= reinterpret_cast(context) && register_value < reinterpret_cast(context) + sizeof(ubpf_context_t); +} + +bool ubpf_is_stack(ubpf_context_t* context, uint64_t register_value) +{ + return register_value >= context->stack_start && register_value < context->stack_end; +} + +void +ubpf_debug_function( + void* context, int program_counter, const uint64_t registers[16], const uint8_t* stack_start, size_t stack_length, uint64_t register_mask) +{ +#if 1 + UNREFERENCED_PARAMETER(context); + UNREFERENCED_PARAMETER(program_counter); + UNREFERENCED_PARAMETER(registers); + UNREFERENCED_PARAMETER(stack_start); + UNREFERENCED_PARAMETER(stack_length); + UNREFERENCED_PARAMETER(register_mask); +#else + ubpf_context_t* ubpf_context = reinterpret_cast(context); + UNREFERENCED_PARAMETER(stack_start); + UNREFERENCED_PARAMETER(stack_length); + + std::string label = std::to_string(program_counter) + ":-1"; + + if (program_counter == 0) { + return; + } + + // Build set of string constraints from the register values. + std::set constraints; + for (int i = 0; i < 10; i++) { + if ((register_mask & (1 << i)) == 0) { + continue; + } + uint64_t reg = registers[i]; + std::string register_name = "r" + std::to_string(i); + + if (ubpf_is_packet(ubpf_context, reg)) { + constraints.insert(register_name + ".type=packet"); + constraints.insert(register_name + ".packet_offset=" + std::to_string(reg - ubpf_context->data)); + constraints.insert( + register_name + ".packet_size=" + std::to_string(ubpf_context->data_end - ubpf_context->data)); + } else if (ubpf_is_context(ubpf_context, reg)) { + constraints.insert(register_name + ".type=ctx"); + constraints.insert( + register_name + ".ctx_offset=" + std::to_string(reg - reinterpret_cast(ubpf_context))); + } else if (ubpf_is_stack(ubpf_context, reg)) { + constraints.insert(register_name + ".type=stack"); + constraints.insert(register_name + ".stack_offset=" + std::to_string(reg - ubpf_context->stack_start)); + } else { + constraints.insert("r" + std::to_string(i) + ".uvalue=" + std::to_string(registers[i])); + constraints.insert( + "r" + std::to_string(i) + ".svalue=" + std::to_string(static_cast(registers[i]))); + } + } + + // Call ebpf_check_constraints_at_label with the set of string constraints at this label. + + std::ostringstream os; + + if (!ebpf_check_constraints_at_label(os, label, constraints)) { + std::cerr << "Label: " << label << std::endl; + std::cerr << os.str() << std::endl; + throw std::runtime_error("ebpf_check_constraints_at_label failed"); + } +#endif +} + +ubpf_context_t ubpf_context_from(std::vector& memory, std::vector& ubpf_stack) +{ + ubpf_context_t context; + context.data = reinterpret_cast(memory.data()); + context.data_end = context.data + memory.size(); + context.stack_start = reinterpret_cast(ubpf_stack.data()); + context.stack_end = context.stack_start + ubpf_stack.size(); + return context; +} + /** * @brief Invoke the ubpf interpreter with the given program code and input memory. * @@ -109,13 +328,17 @@ bool call_ubpf_interpreter(const std::vector& program_code, std::vector { auto vm = create_ubpf_vm(program_code); + ubpf_context_t context = ubpf_context_from(memory, ubpf_stack); + if (vm == nullptr) { // VM creation failed. return false; } + ubpf_register_debug_fn(vm.get(), &context, ubpf_debug_function); + // Execute the program using the input memory. - if (ubpf_exec_ex(vm.get(), memory.data(), memory.size(), &interpreter_result, ubpf_stack.data(), ubpf_stack.size()) != 0) { + if (ubpf_exec_ex(vm.get(), &context, 0, &interpreter_result, ubpf_stack.data(), ubpf_stack.size()) != 0) { // VM execution failed. return false; } @@ -138,6 +361,8 @@ bool call_ubpf_jit(const std::vector& program_code, std::vector& program_code, std::vectordebug_function) { - vm->debug_function(vm->debug_function_context, cur_pc, reg, stack_start, stack_length); + vm->debug_function(vm->debug_function_context, cur_pc, reg, stack_start, stack_length, shadow_registers); } if (!ubpf_validate_shadow_register(vm, &shadow_registers, inst)) {