Skip to content

Commit

Permalink
WIP: Support external stacks and local function calls using local memory
Browse files Browse the repository at this point in the history
1. Add support for invoking the interpreter and JIT'd code with
   an external stack. This feature is generally useful and will
   also make it easier to fuzz the runtime and check for correctness.
2. Add support for local functions that use local memory. Prior to
   this commit, a local function could be called but could not use
   any local memory (without overwriting memory from another function).

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
  • Loading branch information
hawkinsw committed May 14, 2024
1 parent 663ab78 commit 9a42663
Show file tree
Hide file tree
Showing 14 changed files with 740 additions and 242 deletions.
1 change: 1 addition & 0 deletions custom_tests/data/ubpf_test_frame_pointer.input
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b7 06 00 00 0a 00 00 00 b7 07 00 00 0a 00 00 00 b7 08 00 00 0a 00 00 00 b7 09 00 00 0a 00 00 00 b7 01 00 00 05 00 00 00 7b 1a f8 ff 00 00 00 00 85 10 00 00 02 00 00 00 79 a0 f8 ff 00 00 00 00 95 00 00 00 00 00 00 00 b7 01 00 00 37 00 00 00 7b 1a f8 ff 00 00 00 00 95 00 00 00 00 00 00 00
4 changes: 4 additions & 0 deletions custom_tests/descrs/ubpf_test_frame_pointer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Test Description

This custom test program tests whether it is possible to update the external helper
functions for an eBPF program that has already been JIT'd.
103 changes: 103 additions & 0 deletions custom_tests/srcs/ubpf_test_frame_pointer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Will Hawkins
// SPDX-License-Identifier: Apache-2.0

#include <cstdint>
#include <iostream>
#include <memory>
#include <stdint.h>
#include <vector>
#include <string>

extern "C"
{
#include "ubpf.h"
}

#include "ubpf_custom_test_support.h"

int
stack_usage_calculator(const struct ubpf_vm *vm, uint16_t pc, void *cookie)
{
UNREFERENCED_PARAMETER(vm);
UNREFERENCED_PARAMETER(pc);
UNREFERENCED_PARAMETER(cookie);
return 16;
}

int
overwrite_stack_usage_calculator(const struct ubpf_vm *vm, uint16_t pc, void *cookie)
{
UNREFERENCED_PARAMETER(vm);
UNREFERENCED_PARAMETER(pc);
UNREFERENCED_PARAMETER(cookie);
return 0;
}

int main(int argc, char **argv)
{
std::vector<std::string> args(argv, argv + argc);
std::string program_string{};
ubpf_jit_fn jit_fn;

std::getline(std::cin, program_string);

uint64_t no_overwrite_interp_result = 0;
uint64_t no_overwrite_jit_result = 0;
uint64_t overwrite_interp_result = 0;
uint64_t overwrite_jit_result = 0;

{

std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);
std::string error{};
if (!ubpf_setup_custom_test(
vm,
program_string,
[](ubpf_vm_up& vm, std::string& error) {
if (ubpf_register_stack_usage_calculator(vm.get(), stack_usage_calculator, nullptr) < 0) {
error = "Failed to register stack usage calculator.";
return false;
}
return true;
},
jit_fn,
error)) {
std::cerr << "Problem setting up custom test: " << error << std::endl;
return 1;
}

no_overwrite_jit_result = jit_fn(nullptr, 0);
[[maybe_unused]] auto exec_result = ubpf_exec(vm.get(), NULL, 0, &no_overwrite_interp_result);
}

{

std::unique_ptr<ubpf_vm, decltype(&ubpf_destroy)> vm(ubpf_create(), ubpf_destroy);
std::string error{};
if (!ubpf_setup_custom_test(
vm,
program_string,
[](ubpf_vm_up& vm, std::string& error) {
if (ubpf_register_stack_usage_calculator(vm.get(), overwrite_stack_usage_calculator, nullptr) < 0) {
error = "Failed to register stack usage calculator.";
return false;
}
return true;
},
jit_fn,
error)) {
std::cerr << "Problem setting up custom test: " << error << std::endl;
return 1;
}

overwrite_jit_result = jit_fn(nullptr, 0);

[[maybe_unused]] auto exec_result = ubpf_exec(vm.get(), NULL, 0, &overwrite_interp_result);
}
// ... because of the semantics of external_dispatcher, the result of the eBPF
// program execution should point to the same place to which &memory points.
return !(no_overwrite_interp_result == no_overwrite_jit_result &&
no_overwrite_interp_result == 0x5 &&
overwrite_interp_result == overwrite_jit_result &&
overwrite_interp_result == 0x37);
}
55 changes: 55 additions & 0 deletions tests/call-save.data
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,68 @@ mov %r6, 0x0001
mov %r7, 0x0020
mov %r8, 0x0300
mov %r9, 0x4000

# r1 should contain pointer to program memory.
# Don't screw that up because helper function 1 (memfrob)
# needs it.
mov %r2, 0x0001
mov %r3, 0x0001
mov %r4, 0x0001
mov %r5, 0x0001
call 1
mov %r0, 0
or %r0, %r6
or %r0, %r7
or %r0, %r8
or %r0, %r9
jeq %r0, 0x4321, +1
exit

# Call helper function 0 -- the memory pointer is
# no longer needed for any other helper functions, so
# we don't have to worry about keeping it safe.
mov %r1, 0x0001
mov %r2, 0x0001
mov %r3, 0x0001
mov %r4, 0x0001
mov %r5, 0x0001
call 0
mov %r0, 0
or %r0, %r6
or %r0, %r7
or %r0, %r8
or %r0, %r9
jeq %r0, 0x4321, +1
exit

mov %r1, 0x0001
mov %r2, 0x0001
mov %r3, 0x0001
mov %r4, 0x0001
mov %r5, 0x0001
call 2
mov %r0, 0
or %r0, %r6
or %r0, %r7
or %r0, %r8
or %r0, %r9
jeq %r0, 0x4321, +1
exit

mov %r1, 0x0001
mov %r2, 0x0001
mov %r3, 0x0001
mov %r4, 0x0001
mov %r5, 0x0001
call 3
mov %r0, 0
or %r0, %r6
or %r0, %r7
or %r0, %r8
or %r0, %r9
exit
-- mem
01 02 03 04 05 06 07 08
-- result
0x4321
-- no register offset
Expand Down
46 changes: 45 additions & 1 deletion ubpf_plugin/ubpf_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,24 @@ bytes_to_ebpf_inst(std::vector<uint8_t> bytes)
return instructions;
}

/**
* @brief The handler to determine the stack usage of local functions.
*
* @param[in] vm Pointer to the VM of which the local function at pc is a part.
* @param[in] pc The instruction address of the local function.
* @param[in] cookie A pointer to the context cookie given when this callback
* was registered.
* @return The amount of stack used by the local function starting at pc.
*/
int stack_usage_calculator(const struct ubpf_vm *vm, uint16_t pc, void *cookie) {
UNREFERENCED_PARAMETER(pc);
UNREFERENCED_PARAMETER(cookie);
UNREFERENCED_PARAMETER(vm);
// We will default to a conservative 64 bytes of stack usage for each local function.
// That should be enough for all the conformance tests.
return 64;
}

/**
* @brief This program reads BPF instructions from stdin and memory contents from
* the first agument. It then executes the BPF program and prints the
Expand Down Expand Up @@ -138,6 +156,8 @@ int main(int argc, char **argv)

ubpf_register_external_dispatcher(vm.get(), test_helpers_dispatcher, test_helpers_validater);

ubpf_register_stack_usage_calculator(vm.get(), stack_usage_calculator, nullptr);

if (ubpf_set_unwind_function_index(vm.get(), 5) != 0)
{
std::cerr << "Failed to set unwind function index" << std::endl;
Expand Down Expand Up @@ -246,7 +266,7 @@ int main(int argc, char **argv)
}
}

// ... but first reset program memory.
// ... but first reset program memory ...
usable_program_memory = memory;
usable_program_memory_pointer = nullptr;
if (usable_program_memory.size() != 0) {
Expand All @@ -260,6 +280,30 @@ int main(int argc, char **argv)
return 1;
}

// ... and, for the cherry on the sundae, execute the program by specifying a stack ...
uint64_t* external_stack = NULL;

external_stack = (uint64_t*)calloc(512, 1);
if (!external_stack) {
return -1;
}

// ... but first, reset that pesky memory again ...
usable_program_memory = memory;
usable_program_memory_pointer = nullptr;
if (usable_program_memory.size() != 0) {
usable_program_memory_pointer = usable_program_memory.data();
}

uint64_t external_memory_index_helper_result;
if (ubpf_exec_ex(vm.get(), usable_program_memory_pointer, usable_program_memory.size(), &external_memory_index_helper_result, (uint8_t*)external_stack, 512) != 0)
{
std::cerr << "Failed to execute program" << std::endl;
return 1;
}

free(external_stack);

// ... and make sure the results are the same.
if (external_dispatcher_result != index_helper_result) {
std::cerr << "Execution of the interpreted code with external and indexed helpers gave difference results: 0x"
Expand Down
40 changes: 32 additions & 8 deletions vm/inc/ubpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@ extern "C"
#endif

/**
* @brief Default stack size for the VM. Must be divisible by 16.
* @brief Default stack size for the eBPF program. Must be divisible by 16.
*/
#if !defined(UBPF_STACK_SIZE)
#define UBPF_STACK_SIZE 512
#if !defined(UBPF_EBPF_STACK_SIZE)
// #define UBPF_EBPF_STACK_SIZE 512
#define UBPF_EBPF_STACK_SIZE 4096
#endif

#define UBPF_EBPF_NONVOLATILE_SIZE (sizeof(uint64_t) * 5)

/**
* @brief Default maximum number of nested calls in the VM.
*/
Expand All @@ -63,6 +66,16 @@ extern "C"
*/
typedef uint64_t (*ubpf_jit_fn)(void* mem, size_t mem_len);

/**
* @brief Enum to describe JIT mode.
*/

enum JitMode
{
ExtendedJitMode,
BasicJitMode
};

/**
* @brief Create a new uBPF VM.
*
Expand Down Expand Up @@ -160,9 +173,12 @@ extern "C"
*/
int
ubpf_register_external_dispatcher(
struct ubpf_vm* vm,
external_function_dispatcher_t dispatcher,
external_function_validate_t validater);
struct ubpf_vm* vm, external_function_dispatcher_t dispatcher, external_function_validate_t validater);

typedef int (*stack_usage_calculator_t)(const struct ubpf_vm* vm, uint16_t pc, void* cookie);

int
ubpf_register_stack_usage_calculator(struct ubpf_vm* vm, stack_usage_calculator_t calculator, void* cookie);

/**
* @brief Load code into a VM.
Expand Down Expand Up @@ -268,6 +284,15 @@ extern "C"
int
ubpf_exec(const struct ubpf_vm* vm, void* mem, size_t mem_len, uint64_t* bpf_return_value);

int
ubpf_exec_ex(
const struct ubpf_vm* vm,
void* mem,
size_t mem_len,
uint64_t* bpf_return_value,
uint8_t* stack,
size_t stack_len);

/**
* @brief Compile a BPF program in the VM to native code.
*
Expand All @@ -294,7 +319,7 @@ extern "C"
* NULL on failure.
*/
ubpf_jit_fn
ubpf_copy_jit(struct ubpf_vm* vm, void *buffer, size_t size, char** errmsg);
ubpf_copy_jit(struct ubpf_vm* vm, void* buffer, size_t size, char** errmsg);

/**
* @brief Translate the eBPF byte code to machine code.
Expand Down Expand Up @@ -428,7 +453,6 @@ extern "C"
int
ubpf_set_instruction_limit(struct ubpf_vm* vm, uint32_t limit, uint32_t* previous_limit);


#ifdef __cplusplus
}
#endif
Expand Down
20 changes: 20 additions & 0 deletions vm/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,25 @@ map_relocation_bounds_check_function(void* user_context, uint64_t addr, uint64_t
}
return false;
}
/**
* @brief The handler to determine the stack usage of local functions.
*
* @param[in] vm Pointer to the VM of which the local function at pc is a part.
* @param[in] pc The instruction address of the local function.
* @param[in] cookie A pointer to the context cookie given when this callback
* was registered.
* @return The amount of stack used by the local function starting at pc.
*/
int
stack_usage_calculator(const struct ubpf_vm* vm, uint16_t pc, void* cookie)
{
(void)(pc);
(void)(cookie);
(void)(vm);
// This is sized large enough that the rel_64_32.bpf.c program has enough space
// for each local function!
return 32;
}

int
main(int argc, char** argv)
Expand Down Expand Up @@ -283,6 +302,7 @@ main(int argc, char** argv)

register_functions(vm);

ubpf_register_stack_usage_calculator(vm, stack_usage_calculator, NULL);
/*
* The ELF magic corresponds to an RSH instruction with an offset,
* which is invalid.
Expand Down
Loading

0 comments on commit 9a42663

Please sign in to comment.