Skip to content

Commit

Permalink
Pickup external stack changes
Browse files Browse the repository at this point in the history
Signed-off-by: Alan Jowett <alanjo@microsoft.com>
  • Loading branch information
Alan-Jowett committed May 17, 2024
1 parent a1c0309 commit 02eef61
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 182 deletions.
93 changes: 5 additions & 88 deletions libfuzzer/libfuzz_harness.cc
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,6 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, std::size_t size)
return -1;
}

for (std::size_t i = 0; i < program_length / sizeof(ebpf_inst); i++) {
ebpf_inst inst = reinterpret_cast<const ebpf_inst*>(program_start)[i];
if (inst.opcode == EBPF_OP_CALL && inst.src == 1) {
// Until local calls are fixed, reject local calls.
// This is not interesting, as the fuzzer input is invalid.
// Do not add it to the corpus.
return -1;
}
}

// Copy any input memory into a writable buffer.
if (memory_length > 0) {
memory.resize(memory_length);
Expand Down Expand Up @@ -152,24 +142,17 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, std::size_t size)

uint64_t jit_result = 0;
uint64_t interpreter_result = 0;

// Reserve 3 pages for the stack.
const std::size_t helper_function_stack_space = 3*4096; // 3 pages
const std::size_t prolog_size = 64; // Account for extra space needed for the prolog of the jitted function.
uint8_t ubpf_stack[UBPF_STACK_SIZE + helper_function_stack_space];
memset(ubpf_stack, 0, sizeof(ubpf_stack));

// Tell the interpreter where the stack is.
ubpf_set_stack(vm.get(), (uint8_t*)ubpf_stack + sizeof(ubpf_stack) - UBPF_STACK_SIZE - prolog_size);
std::vector<uint8_t> ubpf_stack(3*4096);

// Execute the program using the input memory.
if (ubpf_exec(vm.get(), memory.data(), memory.size(), &interpreter_result) != 0) {
if (ubpf_exec_ex(vm.get(), memory.data(), memory.size(), &interpreter_result, ubpf_stack.data(), ubpf_stack.size()) != 0) {
// The program passed validation during load, but failed during execution.
// due to a runtime error. Add it to the corpus as it may be interesting.
return 0;
}

auto fn = ubpf_compile(vm.get(), &error_message);
auto fn = ubpf_compile_ex(vm.get(), &error_message, JitMode::ExtendedJitMode);

if (fn == nullptr) {
// The program failed to compile.
// This is not interesting, as the fuzzer input is invalid.
Expand All @@ -178,73 +161,7 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, std::size_t size)
return -1;
}

memset(ubpf_stack, 0, sizeof(ubpf_stack));

// Setup the stack for the function call.
uintptr_t new_rsp = (uintptr_t)ubpf_stack + sizeof(ubpf_stack);
uintptr_t* rsp;
uintptr_t* old_rsp_ptr;
uintptr_t old_rdi;
uintptr_t old_rsi;
uintptr_t old_rax;
new_rsp &= ~0xf;
new_rsp -= sizeof(uintptr_t);

rsp = (uintptr_t*)new_rsp;

// Save space for the old value of rsp
*(--rsp) = 0;
old_rsp_ptr = rsp;

// Store the function address.
*(--rsp) = (uintptr_t)fn;

// Store the memory address.
*(--rsp) = (uintptr_t)memory.data();

// Store the memory size.
*(--rsp) = (uintptr_t)memory.size();

// Copy the current value of rsp into the reserved space.
__asm__ __volatile__("movq %%rsp, %0" : "=r"(*old_rsp_ptr));

// Copy the current value of rcx into the reserved space.
__asm__ __volatile__("movq %%rdi, %0" : "=r"(old_rdi));

// Copy the current value of rdx into the reserved space.
__asm__ __volatile__("movq %%rsi, %0" : "=r"(old_rsi));

// Copy the current value of rax into the reserved space.
__asm__ __volatile__("movq %%rax, %0" : "=r"(old_rax));

// Set the new value of rsp.
__asm__ __volatile__("movq %0, %%rsp" : : "r"(rsp));

// Pop arguments into registers.
__asm__ __volatile__("pop %rsi");
__asm__ __volatile__("pop %rdi");

// Pop the function address into rax.
__asm__ __volatile__("pop %rax");

// Call the function.
__asm__ __volatile__("call *%rax");

// Pop the old value of rsp.
__asm__ __volatile__("pop %rsp");

// Copy rax into jit result.
__asm__ __volatile__("movq %%rax, %0" : "=r"(jit_result));

// Put back the old value of rax.
__asm__ __volatile__("movq %0, %%rax" : : "r"(old_rax));

// Put back the old value of rdx.
__asm__ __volatile__("movq %0, %%rsi" : : "r"(old_rsi));

// Put back the old value of rcx.
__asm__ __volatile__("movq %0, %%rdi" : : "r"(old_rdi));

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

// If interpreter_result is not equal to jit_result, raise a fatal signal
if (interpreter_result != jit_result) {
Expand Down
18 changes: 0 additions & 18 deletions vm/inc/ubpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -460,24 +460,6 @@ extern "C"
uint64_t*
ubpf_get_registers(const struct ubpf_vm* vm);

/**
* @brief Override the storage location for the BPF stack in the VM.
*
* @param[in] vm The VM to set the stack storage in.
* @param[in] stack The stack storage.
*/
void
ubpf_set_stack(struct ubpf_vm* vm, uint8_t stack[UBPF_STACK_SIZE]);

/**
* @brief Retrieve the storage location for the BPF stack in the VM.
*
* @param[in] vm The VM to get the stack storage from.
* @return uint8_t* A pointer to the stack storage.
*/
uint8_t*
ubpf_get_stack(const struct ubpf_vm* vm);

/**
* @brief Optional secret to improve ROP protection.
*
Expand Down
1 change: 0 additions & 1 deletion vm/ubpf_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ struct ubpf_vm
int instruction_limit;
#ifdef DEBUG
uint64_t* regs;
uintptr_t stack;
#endif
};

Expand Down
133 changes: 58 additions & 75 deletions vm/ubpf_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,9 @@ ubpf_mem_store(uint64_t address, uint64_t value, size_t size)
* @param[in] address The address being written to.
* @param[in] size The number of bytes being written.
*/
static inline
void ubpf_mark_shadow_stack(const struct ubpf_vm* vm, uint64_t* stack, uint8_t* shadow_stack, void* address, size_t size)
static inline void
ubpf_mark_shadow_stack(
const struct ubpf_vm* vm, uint8_t* stack, uint64_t stack_length, uint8_t* shadow_stack, void* address, size_t size)
{
if (!vm->bounds_check_enabled) {
return;
Expand All @@ -391,7 +392,7 @@ void ubpf_mark_shadow_stack(const struct ubpf_vm* vm, uint64_t* stack, uint8_t*
uintptr_t access_start = (uintptr_t)address;
uintptr_t access_end = access_start + size;
uintptr_t stack_start = (uintptr_t)stack;
uintptr_t stack_end = stack_start + UBPF_STACK_SIZE;
uintptr_t stack_end = stack_start + stack_length;

if (access_start > access_end) {
// Overflow
Expand Down Expand Up @@ -421,17 +422,18 @@ void ubpf_mark_shadow_stack(const struct ubpf_vm* vm, uint64_t* stack, uint8_t*
* @return true - The read is from initialized memory or is not within the stack bounds.
* @return false - The read is from uninitialized memory within the stack bounds.
*/
static inline
bool ubpf_check_shadow_stack(const struct ubpf_vm* vm, uint64_t* stack, uint8_t* shadow_stack, void* address, size_t size)
static inline bool
ubpf_check_shadow_stack(
const struct ubpf_vm* vm, uint8_t* stack, uint64_t stack_length, uint8_t* shadow_stack, void* address, size_t size)
{
if (!vm->bounds_check_enabled) {
return true;
}

uintptr_t access_start= (uintptr_t)address;
uintptr_t access_start = (uintptr_t)address;
uintptr_t access_end = access_start + size;
uintptr_t stack_start = (uintptr_t)stack;
uintptr_t stack_end = stack_start + UBPF_STACK_SIZE;
uintptr_t stack_end = stack_start + stack_length;

if (access_start > access_end) {
// Overflow
Expand Down Expand Up @@ -590,6 +592,7 @@ ubpf_exec_ex(
uint64_t stack_frame_index = 0;
int return_value = -1;
void* external_dispatcher_cookie = mem;
void* shadow_stack = NULL;

if (!insts) {
/* Code must be loaded before we can execute */
Expand All @@ -600,15 +603,19 @@ ubpf_exec_ex(
0,
};

if (vm->bounds_check_enabled) {
shadow_stack = calloc(stack_length / 8, 1);
if (!shadow_stack) {
return_value = -1;
goto cleanup;
}
}

#ifdef DEBUG
if (vm->regs)
reg = vm->regs;
else
reg = _reg;

if (vm->stack) {
stack = (uint64_t*)vm->stack;
}
#else
reg = _reg;
#endif
Expand All @@ -618,13 +625,11 @@ ubpf_exec_ex(
reg[2] = (uint64_t)mem_len;
reg[10] = (uintptr_t)stack_start + stack_length;


// Mark r1, r2, r10 as initialized.
shadow_registers |= (1 << 1) | (1 << 2) | (1 << 10);

int instruction_limit = vm->instruction_limit;


while (1) {
const uint16_t cur_pc = pc;
if (pc >= vm->num_insts) {
Expand Down Expand Up @@ -846,41 +851,43 @@ ubpf_exec_ex(
*
* Needed since we don't have a verifier yet.
*/
#define BOUNDS_CHECK_LOAD(size) \
do { \
if (!ubpf_check_shadow_stack(vm, stack, shadow_stack, (char*)reg[inst.src] + inst.offset, size)) { \
return_value = -1; \
goto cleanup; \
} \
if (!bounds_check( \
vm, \
(char*)reg[inst.src] + inst.offset, \
size, \
"load", \
cur_pc, \
mem, \
mem_len, \
stack_start, \
stack_length)) { \
return_value = -1; \
goto cleanup; \
} \
#define BOUNDS_CHECK_LOAD(size) \
do { \
if (!ubpf_check_shadow_stack( \
vm, stack_start, stack_length, shadow_stack, (char*)reg[inst.src] + inst.offset, size)) { \
return_value = -1; \
goto cleanup; \
} \
if (!bounds_check( \
vm, \
(char*)reg[inst.src] + inst.offset, \
size, \
"load", \
cur_pc, \
mem, \
mem_len, \
stack_start, \
stack_length)) { \
return_value = -1; \
goto cleanup; \
} \
} while (0)
#define BOUNDS_CHECK_STORE(size) \
do { \
if (!bounds_check( \
vm, \
(char*)reg[inst.dst] + inst.offset, \
size, \
"store", \
cur_pc, \
mem, \
mem_len, \
stack_start, \
stack_length)) { \
return_value = -1; \
goto cleanup; \
} \
#define BOUNDS_CHECK_STORE(size) \
do { \
if (!bounds_check( \
vm, \
(char*)reg[inst.dst] + inst.offset, \
size, \
"store", \
cur_pc, \
mem, \
mem_len, \
stack_start, \
stack_length)) { \
return_value = -1; \
goto cleanup; \
} \
ubpf_mark_shadow_stack(vm, stack_start, stack_length, shadow_stack, (char*)reg[inst.dst] + inst.offset, size); \
} while (0)

case EBPF_OP_LDXW:
Expand Down Expand Up @@ -1223,8 +1230,7 @@ ubpf_exec_ex(
// valid.
break;
}
if (((inst.opcode & EBPF_CLS_MASK) == EBPF_CLS_ALU) &&
(inst.opcode & EBPF_ALU_OP_MASK) != 0xd0) {
if (((inst.opcode & EBPF_CLS_MASK) == EBPF_CLS_ALU) && (inst.opcode & EBPF_ALU_OP_MASK) != 0xd0) {
reg[inst.dst] &= UINT32_MAX;
}
}
Expand All @@ -1233,6 +1239,9 @@ ubpf_exec_ex(
#if defined(NTDDI_VERSION) && defined(WINNT)
free(stack_frames);
#endif
if (shadow_stack) {
free(shadow_stack);
}
return return_value;
}

Expand Down Expand Up @@ -1580,17 +1589,6 @@ ubpf_get_registers(const struct ubpf_vm* vm)
return vm->regs;
}

void
ubpf_set_stack(struct ubpf_vm* vm, uint8_t stack[UBPF_STACK_SIZE])
{
vm->stack = (uintptr_t)stack;
}

uint8_t*
ubpf_get_stack(const struct ubpf_vm* vm)
{
return (uint8_t*)vm->stack;
}
#else
void
ubpf_set_registers(struct ubpf_vm* vm, uint64_t* regs)
Expand All @@ -1607,21 +1605,6 @@ ubpf_get_registers(const struct ubpf_vm* vm)
fprintf(stderr, "uBPF warning: registers are not exposed in release mode. Please recompile in debug mode\n");
return NULL;
}

void ubpf_set_stack(struct ubpf_vm* vm, uint8_t stack[UBPF_STACK_SIZE])
{
(void)vm;
(void)stack;
fprintf(stderr, "uBPF warning: stack is not exposed in release mode. Please recompile in debug mode\n");
}

uint8_t*
ubpf_get_stack(const struct ubpf_vm* vm)
{
(void)vm;
fprintf(stderr, "uBPF warning: stack is not exposed in release mode. Please recompile in debug mode\n");
return NULL;
}
#endif

typedef struct _ebpf_encoded_inst
Expand Down

0 comments on commit 02eef61

Please sign in to comment.