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 15, 2024
1 parent 663ab78 commit 5bfcb6e
Show file tree
Hide file tree
Showing 15 changed files with 792 additions and 247 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);
}
2 changes: 1 addition & 1 deletion external/bpf_conformance
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
71 changes: 68 additions & 3 deletions 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 @@ -192,6 +212,7 @@ int main(int argc, char **argv)
}
index_helper_result = fn(usable_program_memory_pointer, usable_program_memory.size());


// ... copy the JIT'd program ...
auto fn_copy_size = vm->jitted_size * sizeof(char);
void *fn_copy = mmap(0, fn_copy_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Expand All @@ -212,12 +233,32 @@ int main(int argc, char **argv)
}
copy_result = fn(usable_program_memory_pointer, usable_program_memory.size());

ubpf_jit_ex_fn fn_ex = ubpf_compile_ex(vm.get(), &error, ExtendedJitMode);
if (fn_ex == nullptr)
{
std::cerr << "Failed to compile program (extended): " << error << std::endl;
free(error);
return 1;
}

uint64_t index_helper_result_external_stack;
usable_program_memory = memory;
usable_program_memory_pointer = nullptr;
if (usable_program_memory.size() != 0) {
usable_program_memory_pointer = usable_program_memory.data();
}

uint8_t *external_stack = (uint8_t*)calloc(512, 1);
index_helper_result_external_stack = fn_ex(usable_program_memory_pointer, usable_program_memory.size(), external_stack, 512);
free(external_stack);

// ... and make sure the results are the same.
if (external_dispatcher_result != index_helper_result || index_helper_result != copy_result) {
if (external_dispatcher_result != index_helper_result || index_helper_result != copy_result || external_dispatcher_result != index_helper_result_external_stack) {
std::cerr << "Execution of the JIT'd code (with external and indexed helpers) and a copy of "
"the JIT'd code gave different results: 0x" << std::hex << external_dispatcher_result
<< " vs 0x" << std::hex << index_helper_result
<< " vs 0x" << std::hex << copy_result << "." << std::endl;
<< " vs 0x" << std::hex << copy_result
<< " vs 0x" << std::hex << index_helper_result_external_stack << "." << std::endl;
return 1;
}
}
Expand Down Expand Up @@ -246,7 +287,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 +301,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
Loading

0 comments on commit 5bfcb6e

Please sign in to comment.