Skip to content

Commit

Permalink
PR feedback
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 16, 2024
1 parent 0167bd2 commit 4038087
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 7 deletions.
156 changes: 151 additions & 5 deletions libfuzzer/libfuzz_harness.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ extern "C"
#include "test_helpers.h"
#include <cassert>

/**
* @brief Context structure passed to the BPF program. Modelled after the context structure used by XDP.
*/
typedef struct _ubpf_context
{
uint64_t data;
Expand All @@ -33,14 +36,21 @@ typedef struct _ubpf_context
uint64_t stack_end;
} ubpf_context_t;

/**
* @brief Descriptor for the context structure. This is used by the verifier to determine the layout of the context
* structure in memory.
*/
ebpf_context_descriptor_t g_ebpf_context_descriptor_ubpf = {
.size = sizeof(ubpf_context_t),
.data = offsetof(ubpf_context_t, data),
.end = offsetof(ubpf_context_t, data_end),
.meta = -1,
};


/**
* @brief Description of the program type. This is used by the verifier to determine the what the context structure to use as well as the helper functions that are available.
*
*/
EbpfProgramType g_ubpf_program_type = {
.name = "ubpf",
.context_descriptor = &g_ebpf_context_descriptor_ubpf,
Expand All @@ -49,35 +59,75 @@ EbpfProgramType g_ubpf_program_type = {
.is_privileged = false,
};

/**
* @brief This function is called by the verifier when parsing an ELF file to determine the type of the program being loaded based on the section and path.
*
* @param[in] section The section name of the program.
* @param[in] path The path to the ELF file.
* @return The type of the program.
*/
EbpfProgramType ubpf_get_program_type(const std::string& section, const std::string& path)
{
UNREFERENCED_PARAMETER(section);
UNREFERENCED_PARAMETER(path);
return g_ubpf_program_type;
}

/***
* @brief This function is called by the verifier to determine the type of a map given the platform specific type.
*
* @param[in] platform_specific_type The platform specific type of the map.
* @return The type of the map.
*/
EbpfMapType ubpf_get_map_type(uint32_t platform_specific_type)
{
// Once the fuzzer supports maps, this function should be implemented to return metadata about the map, primarily the key and value size.
UNREFERENCED_PARAMETER(platform_specific_type);
return {};
}

/**
* @brief This function is called by the verifier to determine the prototype of a helper function given the helper function number.
*
* @param[in] n The helper function number.
* @return The prototype of the helper function.
*/
EbpfHelperPrototype ubpf_get_helper_prototype(int32_t n)
{
// Once the fuzzer supports helper functions, this function should be implemented to return metadata about the helper function.
UNREFERENCED_PARAMETER(n);
return {};
}

/**
* @brief This function is called by the verifier to determine whether a helper function is usable given the helper function number.
*
* @param[in] n The helper function number.
* @return true The helper function is usable.
* @return false The helper function is not usable.
*/
bool ubpf_is_helper_usable(int32_t n)
{
// Once the fuzzer supports helper functions, this function should be implemented to return whether the helper function is usable.
UNREFERENCED_PARAMETER(n);
return false;
}

/**
* @brief This function is called by the verifier to parse the maps section of the ELF file (if any).
*
* @param[in] map_descriptors The map descriptors to populate.
* @param[in] data The data in the maps section.
* @param[in] map_record_size The size of each map record.
* @param[in] map_count The number of maps in the maps section.
* @param[in] platform The platform specific data.
* @param[in] options Options for the verifier.
*/
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)
{
// Once the fuzzer supports maps, this function should be implemented to parse the maps section of the ELF file (if any).
UNREFERENCED_PARAMETER(map_descriptors);
UNREFERENCED_PARAMETER(data);
UNREFERENCED_PARAMETER(map_record_size);
Expand All @@ -86,18 +136,35 @@ void ubpf_parse_maps_section(std::vector<EbpfMapDescriptor>& map_descriptors, co
UNREFERENCED_PARAMETER(options);
throw std::runtime_error("parse_maps_section not implemented");
}

/**
* @brief Given a map descriptor, resolve any inner map references to other maps.
*
* @param[in,out] map_descriptors The map descriptors to resolve.
*/
void ubpf_resolve_inner_map_references(std::vector<EbpfMapDescriptor>& map_descriptors)
{
// Once the fuzzer supports maps, this function should be implemented to resolve inner map references.
UNREFERENCED_PARAMETER(map_descriptors);
throw std::runtime_error("resolve_inner_map_references not implemented");
}

/**
* @brief The function is called by the verifier to get the map descriptor for a given map file descriptor.
*
* @param[in] map_fd The map file descriptor.
* @return EbpfMapDescriptor& The map descriptor.
*/
EbpfMapDescriptor& ubpf_get_map_descriptor(int map_fd)
{
// Once the fuzzer supports maps, this function should be implemented to return the map descriptor for the given map file descriptor.
UNREFERENCED_PARAMETER(map_fd);
throw std::runtime_error("get_map_descriptor not implemented");
}

/**
* @brief The platform abstraction for verifier to call into the uBPF fuzzer platform.
*/
ebpf_platform_t g_ebpf_platform_ubpf_fuzzer = {
.get_program_type = ubpf_get_program_type,
.get_helper_prototype = ubpf_get_helper_prototype,
Expand All @@ -111,18 +178,47 @@ ebpf_platform_t g_ebpf_platform_ubpf_fuzzer = {
};


/**
* @brief Dispatcher for the helper functions.
*
* @param p0 First parameter to the helper function.
* @param p1 Second parameter to the helper function.
* @param p2 Third parameter to the helper function.
* @param p3 Fourth parameter to the helper function.
* @param p4 Fifth parameter to the helper function.
* @param idx Index of the helper function to call.
* @param cookie Cookie to pass to the helper function.
* @return Value returned by the helper function.
*/
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);
}

/**
* @brief Function to validate the helper function index.
*
* @param[in] idx Helper function index.
* @param[in] vm The VM instance.
* @return true The helper function index is valid.
* @return false The helper function index is invalid.
*/
bool test_helpers_validator(unsigned int idx, const struct ubpf_vm *vm) {
UNREFERENCED_PARAMETER(vm);
return helper_functions.contains(idx);
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, std::size_t size);

/**
* @brief Null printf function to suppress output.
*
* @param[in,out] stream The stream to write to.
* @param[in] format The format string.
* @param[in] ... The arguments to the format string.
*
* @return int The number of characters written.
*/
int null_printf(FILE* stream, const char* format, ...)
{
if (!stream) {
Expand All @@ -134,6 +230,13 @@ int null_printf(FILE* stream, const char* format, ...)
return 0;
}

/**
* @brief Invoke the verifier to verify the given BPF program.
*
* @param[in] program_code The program byte code to verify.
* @return true The program is safe to run.
* @return false The program might be unsafe to run. Note: The verifier is conservative and may reject safe programs.
*/
bool verify_bpf_byte_code(const std::vector<uint8_t>& program_code)
try
{
Expand All @@ -150,29 +253,41 @@ try
std::string file;
raw_program raw_prog{file, section, 0, {}, instructions, info};

// Unpack the program into a sequence of instructions that the verifier can understand.
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;
}

// Extract the program instructions.
InstructionSeq& prog = std::get<InstructionSeq>(prog_or_error);

// First try optimized for the success case.
// Start with the default verifier options.
ebpf_verifier_options_t options = ebpf_verifier_default_options;
ebpf_verifier_stats_t stats;

// Enable termination checking and pre-invariant storage.
options.check_termination = true;
options.store_pre_invariants = true;

// Disable simplification so that the verifier can provide more fine grained invariant information for each instruction.
options.simplify = false;

ebpf_verifier_stats_t stats;

std::ostringstream error_stream;

// Verify the program. This will return false or throw an exception if the program is invalid.
return ebpf_verify_program(error_stream, prog, raw_prog.info, &options, &stats);
}
catch (const std::exception& ex)
{
return false;
}

/**
* @brief RAII wrapper for the ubpf_vm object.
*/
typedef std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> ubpf_vm_ptr;

/**
Expand Down Expand Up @@ -226,7 +341,9 @@ ubpf_vm_ptr create_ubpf_vm(const std::vector<uint8_t>& program_code)
return vm;
}


/**
* @brief Classify the given register value as packet, context, stack, map, or unknown.
*/
typedef enum class _address_type {
Packet,
Context,
Expand All @@ -235,6 +352,16 @@ typedef enum class _address_type {
Unknown
} address_type_t;

/**
* @brief Given a register value, classify it as packet, context, stack, or unknown.
*
* @param[in] context Pointer to the context structure.
* @param[in] register_value Register value to classify.
* @retval address_type_t::Packet The register value is within the packet data.
* @retval address_type_t::Context The register value is within the context structure.
* @retval address_type_t::Stack The register value is within the stack.
* @retval address_type_t::Unknown The register value is unknown.
*/
address_type_t ubpf_classify_address(ubpf_context_t* context, uint64_t register_value)
{
if (register_value >= context->data && register_value < context->data_end) {
Expand All @@ -248,9 +375,20 @@ address_type_t ubpf_classify_address(ubpf_context_t* context, uint64_t register_
}
}

/**
* @brief Function invoked prior to executing each instruction in the program.
*
* @param[in] context Context passed to the program.
* @param[in] program_counter The program counter (the index of the instruction to execute)
* @param[in] registers The register values.
* @param[in] stack_start The start of the stack.
* @param[in] stack_length The length of the stack.
* @param[in] register_mask The set of registers that have been modified since the start of the program.
* @param[in] stack_mask The set of stack locations that have been modified since the start of the program.
*/
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)
void* context, int program_counter, const uint64_t registers[16], const uint8_t* stack_start, size_t stack_length, uint64_t register_mask, const uint8_t* stack_mask)
{
#if 1
UNREFERENCED_PARAMETER(context);
Expand All @@ -259,6 +397,7 @@ ubpf_debug_function(
UNREFERENCED_PARAMETER(stack_start);
UNREFERENCED_PARAMETER(stack_length);
UNREFERENCED_PARAMETER(register_mask);
UNREFERENCED_PARAMETER(stack_mask);
#else
ubpf_context_t* ubpf_context = reinterpret_cast<ubpf_context_t*>(context);
UNREFERENCED_PARAMETER(stack_start);
Expand Down Expand Up @@ -317,6 +456,13 @@ ubpf_debug_function(
#endif
}

/**
* @brief Helper function to create a ubpf_context_t object from the given memory and stack.
*
* @param[in] memory Vector containing the input memory.
* @param[in] ubpf_stack Vector containing the stack.
* @return ubpf_context_t The context object.
*/
ubpf_context_t ubpf_context_from(std::vector<uint8_t>& memory, std::vector<uint8_t>& ubpf_stack)
{
ubpf_context_t context;
Expand Down
6 changes: 5 additions & 1 deletion vm/inc/ubpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -571,14 +571,18 @@ extern "C"
* @param[in] registers Array of 11 registers representing the VM state.
* @param[in] stack_start Pointer to the beginning of the stack.
* @param[in] stack_length Size of the stack in bytes.
* @param[in] register_mask Bitmask of registers that have been modified since the start of the program.
* @param[in] stack_mask_begin Bitmask of the stack that has been modified since the start of the program.
* Each set bit represents 1 byte of the stack that has been modified.
*/
typedef void (*ubpf_debug_fn)(
void* context,
int program_counter,
const uint64_t registers[16],
const uint8_t* stack_start,
size_t stack_length,
uint64_t register_mask);
uint64_t register_mask,
uint8_t stack_mask_begin);

/**
* @brief Add option to invoke a debug function before each instruction.
Expand Down
10 changes: 9 additions & 1 deletion vm/ubpf_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,15 @@ 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, shadow_registers);
vm->debug_function(
vm->debug_function_context, // The user's context pointer that was passed to ubpf_register_debug_fn.
cur_pc, // The current instruction pointer.
reg, // The array of 11 registers representing the VM state.
stack_start, // Pointer to the beginning of the stack.
stack_length, // Size of the stack in bytes.
shadow_registers, // Bitmask of registers that have been modified since the start of the program.
(uint8_t*)shadow_stack // Bitmask of the stack that has been modified since the start of the program.
);
}

switch (inst.opcode) {
Expand Down

0 comments on commit 4038087

Please sign in to comment.