diff --git a/libfuzzer/libfuzz_harness.cc b/libfuzzer/libfuzz_harness.cc index 4756caf8d..7de9e2bab 100644 --- a/libfuzzer/libfuzz_harness.cc +++ b/libfuzzer/libfuzz_harness.cc @@ -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(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); @@ -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 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. @@ -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) { diff --git a/vm/inc/ubpf.h b/vm/inc/ubpf.h index daf6d2338..c45552258 100644 --- a/vm/inc/ubpf.h +++ b/vm/inc/ubpf.h @@ -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. * diff --git a/vm/ubpf_int.h b/vm/ubpf_int.h index cf3f8479b..5f6bc532d 100644 --- a/vm/ubpf_int.h +++ b/vm/ubpf_int.h @@ -93,7 +93,6 @@ struct ubpf_vm int instruction_limit; #ifdef DEBUG uint64_t* regs; - uintptr_t stack; #endif }; diff --git a/vm/ubpf_vm.c b/vm/ubpf_vm.c index bed8c4205..6ef905136 100644 --- a/vm/ubpf_vm.c +++ b/vm/ubpf_vm.c @@ -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; @@ -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 @@ -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 @@ -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 */ @@ -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 @@ -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) { @@ -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: @@ -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; } } @@ -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; } @@ -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) @@ -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