Skip to content

Commit

Permalink
Verifier integration
Browse files Browse the repository at this point in the history
Signed-off-by: Alan Jowett <[email protected]>
  • Loading branch information
Alan Jowett committed Oct 15, 2024
1 parent 9382b34 commit 3d34b60
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ endif()

if (UBPF_ENABLE_LIBFUZZER)
add_subdirectory("libfuzzer")
add_subdirectory("external/ebpf-verifier")
endif()
3 changes: 2 additions & 1 deletion custom_tests/srcs/ubpf_test_debug_function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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_state_t>* vm_states = static_cast<std::vector<vm_state_t>*>(context);
vm_state_t vm_state{};

Expand Down
1 change: 1 addition & 0 deletions external/ebpf-verifier
Submodule ebpf-verifier added at 365643
7 changes: 7 additions & 0 deletions libfuzzer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

234 changes: 232 additions & 2 deletions libfuzzer/libfuzz_harness.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,107 @@
#include <string>
#include <sstream>

#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 <cassert>

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<EbpfMapDescriptor>& 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<EbpfMapDescriptor>& 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);
Expand All @@ -42,6 +134,45 @@ int null_printf(FILE* stream, const char* format, ...)
return 0;
}

bool verify_bpf_byte_code(const std::vector<uint8_t>& program_code)
try
{
std::ostringstream error;
auto instruction_array = reinterpret_cast<const ebpf_inst*>(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<ebpf_inst> 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<InstructionSeq, std::string> prog_or_error = unmarshal(raw_prog);
if (!std::holds_alternative<InstructionSeq>(prog_or_error)) {
//std::cout << "Failed to unmarshal program : " << std::get<std::string>(prog_or_error) << std::endl;
return false;
}
InstructionSeq& prog = std::get<InstructionSeq>(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, decltype(&ubpf_destroy)> ubpf_vm_ptr;

/**
Expand Down Expand Up @@ -95,6 +226,94 @@ ubpf_vm_ptr create_ubpf_vm(const std::vector<uint8_t>& 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<uint64_t>(context) && register_value < reinterpret_cast<uint64_t>(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<ubpf_context_t*>(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<std::string> 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<uint64_t>(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<int64_t>(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<uint8_t>& memory, std::vector<uint8_t>& ubpf_stack)
{
ubpf_context_t context;
context.data = reinterpret_cast<uint64_t>(memory.data());
context.data_end = context.data + memory.size();
context.stack_start = reinterpret_cast<uint64_t>(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.
*
Expand All @@ -109,13 +328,17 @@ bool call_ubpf_interpreter(const std::vector<uint8_t>& 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;
}
Expand All @@ -138,6 +361,8 @@ bool call_ubpf_jit(const std::vector<uint8_t>& program_code, std::vector<uint8_t
{
auto vm = create_ubpf_vm(program_code);

ubpf_context_t context = ubpf_context_from(memory, ubpf_stack);

char* error_message = nullptr;

if (vm == nullptr) {
Expand All @@ -154,7 +379,7 @@ bool call_ubpf_jit(const std::vector<uint8_t>& program_code, std::vector<uint8_t
return false;
}

jit_result = fn(memory.data(), memory.size(), ubpf_stack.data(), ubpf_stack.size());
jit_result = fn(&context, 0, ubpf_stack.data(), ubpf_stack.size());

// Compilation succeeded.
return true;
Expand Down Expand Up @@ -240,6 +465,11 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, std::size_t size)
return -1;
}

if (!verify_bpf_byte_code(program)) {
// The program failed verification.
return 0;
}

uint64_t interpreter_result = 0;
uint64_t jit_result = 0;

Expand Down
3 changes: 2 additions & 1 deletion vm/inc/ubpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,8 @@ extern "C"
int program_counter,
const uint64_t registers[16],
const uint8_t* stack_start,
size_t stack_length);
size_t stack_length,
uint64_t register_mask);

/**
* @brief Add option to invoke a debug function before each instruction.
Expand Down
2 changes: 1 addition & 1 deletion vm/ubpf_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ ubpf_exec_ex(

// Invoke the debug function to allow the user to inspect the state of the VM if it is enabled.
if (vm->debug_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)) {
Expand Down

0 comments on commit 3d34b60

Please sign in to comment.