Skip to content

Commit

Permalink
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 <[email protected]>
  • Loading branch information
hawkinsw committed May 16, 2024
1 parent 3fb3da0 commit b5a492e
Show file tree
Hide file tree
Showing 18 changed files with 937 additions and 250 deletions.
1 change: 1 addition & 0 deletions custom_tests/data/ubpf_test_external_stack_contents.input
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
72 0a fc ff 01 00 00 00 72 0a fd ff 02 00 00 00 72 0a fe ff 03 00 00 00 72 0a ff ff 04 00 00 00 85 10 00 00 02 00 00 00 b7 00 00 00 00 00 00 00 95 00 00 00 00 00 00 00 72 0a fc ff 05 00 00 00 72 0a fd ff 06 00 00 00 72 0a fe ff 07 00 00 00 72 0a ff ff 08 00 00 00 95 00 00 00 00 00 00 00
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
59 changes: 59 additions & 0 deletions custom_tests/descrs/ubpf_test_external_stack_contents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Test Description

This custom test guarantees that the eBPF program's manipulation of its stack has the intended effect. The eBPF program is JIT'd under the assumption that each of the functions require 16 bytes of space. The test guarantees that by returning `16` from `stack_usage_calculator` which is registered as the callback that will determine the stack usage of a local function.

### eBPF Program Source

```
stb [%r10-4], 0x01
stb [%r10-3], 0x02
stb [%r10-2], 0x03
stb [%r10-1], 0x04
call local inner
mov %r0, 0
exit
inner:
stb [%r10-4], 0x05
stb [%r10-3], 0x06
stb [%r10-2], 0x07
stb [%r10-1], 0x08
exit
```

### Expected Behavior

Given the size of the stack usage for each function (see above), the contents of the memory at the end of the program will be:

```
0x00: 0x00
0x01: 0x00
0x02: 0x00
0x03: 0x00
0x04: 0x00
0x05: 0x00
0x06: 0x00
0x07: 0x00
0x08: 0x00
0x09: 0x00
0x0a: 0x00
0x0b: 0x05
0x0c: 0x06
0x0d: 0x07
0x0e: 0x08
0x0f: 0x00
0x10: 0x00
0x11: 0x00
0x12: 0x00
0x13: 0x00
0x14: 0x00
0x15: 0x00
0x16: 0x00
0x17: 0x00
0x18: 0x00
0x19: 0x00
0x1a: 0x00
0x1b: 0x01
0x1c: 0x02
0x1d: 0x03
0x1e: 0x04
```
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.
74 changes: 74 additions & 0 deletions custom_tests/srcs/ubpf_test_external_stack_contents.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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
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);

const size_t stack_size{32};
uint8_t expected_result[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
};

bool success = true;

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;
}

char* ex_jit_compile_error = nullptr;
auto jit_ex_fn = ubpf_compile_ex(vm.get(), &ex_jit_compile_error, ExtendedJitMode);
uint8_t external_stack[stack_size] = {
0,
};
jit_ex_fn(nullptr, 0, external_stack, stack_size);

for (size_t i = 0; i < stack_size; i++) {
if (external_stack[i] != expected_result[i]) {
success = false;
}
}
return !success;
}
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
Loading

0 comments on commit b5a492e

Please sign in to comment.