Index: lib/fuzzer/FuzzerUtilFuchsia.cpp =================================================================== --- lib/fuzzer/FuzzerUtilFuchsia.cpp +++ lib/fuzzer/FuzzerUtilFuchsia.cpp @@ -27,17 +27,39 @@ #include #include #include +#include +#include #include #include namespace fuzzer { +// Given that Fuchsia doesn't have the POSIX signals that libFuzzer was written +// around, the general approach is to spin up dedicated threads to watch for +// each requested condition (alarm, interrupt, crash). Of these, the crash +// handler is the most involved, as it requires resuming the crashed thread in +// order to invoke the sanitizers to get the needed state. + +// Forward declaration of assembly trampoline needed to resume crashed threads. +void CrashTrampolineAsm() __asm__("CrashTrampolineAsm"); + namespace { // A magic value for the Zircon exception port, chosen to spell 'FUZZING' // when interpreted as a byte sequence on little-endian platforms. const uint64_t kFuzzingCrash = 0x474e495a5a5546; +// Helper function to handle Zircon syscall failures. +void ExitOnErr(zx_status_t Status, const char *Syscall) { + if (Status != ZX_OK) { + Printf("libFuzzer: %s failed: %s\n", Syscall, + _zx_status_get_string(Status)); + exit(1); + } else { + Printf("libFuzzer: %s succeeded\n", Syscall); + } +} + void AlarmHandler(int Seconds) { while (true) { SleepSeconds(Seconds); @@ -56,32 +78,215 @@ Fuzzer::StaticInterruptCallback(); } -void CrashHandler(zx_handle_t *Port) { - std::unique_ptr ExceptionPort(Port); - zx_port_packet_t Packet; - _zx_port_wait(*ExceptionPort, ZX_TIME_INFINITE, &Packet); - // Unbind as soon as possible so we don't receive exceptions from this thread. - if (_zx_task_bind_exception_port(ZX_HANDLE_INVALID, ZX_HANDLE_INVALID, - kFuzzingCrash, 0) != ZX_OK) { - // Shouldn't happen; if it does the safest option is to just exit. - Printf("libFuzzer: unable to unbind exception port; aborting!\n"); - exit(1); - } - if (Packet.key != kFuzzingCrash) { - Printf("libFuzzer: invalid crash key: %" PRIx64 "; aborting!\n", - Packet.key); - exit(1); - } - // CrashCallback should not return from this call +// For the crash handler, we need to call Fuzzer::StaticCrashSignalCallback +// without POSIX signal handlers. To achieve this, we use and assembly function +// to add the necessary CFI unwinding information and a C function to bridge +// from that back into C++/ + +// FIXME: This works as a short-term solution, but this code really shouldn't be +// architecture dependent. A better long term solution is to implement remote +// unwinding and expose the necessary APIs through sanitizer_common and/or ASAN +// to allow the exception handling thread to gather the crash state directly. +#if defined(__x86_64__) +#define FOREACH_REGISTER(OP_REG, OP_NUM) \ + OP_REG(rax) \ + OP_REG(rbx) \ + OP_REG(rcx) \ + OP_REG(rdx) \ + OP_REG(rsi) \ + OP_REG(rdi) \ + OP_REG(rbp) \ + OP_REG(rsp) \ + OP_REG(r8) \ + OP_REG(r9) \ + OP_REG(r10) \ + OP_REG(r11) \ + OP_REG(r12) \ + OP_REG(r13) \ + OP_REG(r14) \ + OP_REG(r15) \ + OP_REG(rip) + +#elif defined(__aarch64__) +#define FOREACH_REGISTER(OP_REG, OP_NUM) \ + OP_NUM(0) \ + OP_NUM(1) \ + OP_NUM(2) \ + OP_NUM(3) \ + OP_NUM(4) \ + OP_NUM(5) \ + OP_NUM(6) \ + OP_NUM(7) \ + OP_NUM(8) \ + OP_NUM(9) \ + OP_NUM(10) \ + OP_NUM(11) \ + OP_NUM(12) \ + OP_NUM(13) \ + OP_NUM(14) \ + OP_NUM(15) \ + OP_NUM(16) \ + OP_NUM(17) \ + OP_NUM(18) \ + OP_NUM(19) \ + OP_NUM(20) \ + OP_NUM(21) \ + OP_NUM(22) \ + OP_NUM(23) \ + OP_NUM(24) \ + OP_NUM(25) \ + OP_NUM(26) \ + OP_NUM(27) \ + OP_NUM(28) \ + OP_NUM(29) \ + OP_REG(lr) \ + OP_REG(sp) + +#else +#error "Unsupported architecture for fuzzing on Fuchsia" +#endif + +// Produces a CFI directive for the named or numbered register. +#define CFI_OFFSET_REG(reg) ".cfi_offset " #reg ", %c[" #reg "]\n" +#define CFI_OFFSET_NUM(num) CFI_OFFSET_REG(r##num) + +// Produces an assembler input operand for the named or numbered register. +#define ASM_OPERAND_REG(reg) \ + [reg] "i"(offsetof(zx_thread_state_general_regs_t, reg)), +#define ASM_OPERAND_NUM(num) \ + [r##num] "i"(offsetof(zx_thread_state_general_regs_t, r) + \ + (num * sizeof(uint64_t))), + +// Trampoline to bridge from the assembly below to the static C++ crash +// callback. +__attribute__((noreturn)) +extern "C" void StaticCrashHandler() { Fuzzer::StaticCrashSignalCallback(); + for (;;) { + _Exit(1); + } +} + +// Creates the trampoline with the necessary CFI information to unwind through +// to the crashing call stack. +__attribute__((used)) +void MakeTrampoline() { + uint64_t dummy = 0; // ignored; used to swallow the trailing comma. + __asm__(".cfi_endproc\n" + ".pushsection .text.CrashTrampolineAsm\n" + ".type CrashTrampolineAsm,STT_FUNC;\n" +"CrashTrampolineAsm:\n" + ".cfi_startproc\n" + ".cfi_signal_frame\n" +#if defined(__x86_64__) + ".cfi_return_column rip\n" + ".cfi_def_cfa rsp, 0\n" + FOREACH_REGISTER(CFI_OFFSET_REG, CFI_OFFSET_NUM) + "call StaticCrashHandler\n" + "ud2\n" +#elif defined(__aarch64__) + ".cfi_return_column 33\n" + ".cfi_def_cfa sp, 0\n" + ".cfi_offset 33, %c[pc]\n" + FOREACH_REGISTER(CFI_OFFSET_REG, CFI_OFFSET_NUM) + "bl StaticCrashHandler\n" +#else +#error "Unsupported architecture for fuzzing on Fuchsia" +#endif + ".cfi_endproc\n" + ".size CrashTrampolineAsm, . - CrashTrampolineAsm\n" + ".popsection\n" + ".cfi_startproc\n" + : // No outputs + : FOREACH_REGISTER(ASM_OPERAND_REG, ASM_OPERAND_NUM) +#if defined(__aarch64__) + ASM_OPERAND_REG(pc) +#endif + "i" (dummy)); +} + +void CrashHandler(zx_handle_t *Event) { + // This structure is used to ensure we close handles to objects we create in + // this handler. + struct ScopedCrashContext { + ScopedCrashContext() : Port(ZX_HANDLE_INVALID), Thread(ZX_HANDLE_INVALID) {} + ~ScopedCrashContext() { + _zx_handle_close(Port); + _zx_handle_close(Thread); + } + zx_handle_t Port; + zx_handle_t Thread; + } Scoped; + + // Create and bind the exception port. We need to claim to be a "debugger" so + // the kernel will allow us to modify and resume dying threads (see below). + // Once the port is set, we can signal the main thread to continue and wait + // for the exception to arrive. + ExitOnErr(_zx_port_create(0, &Scoped.Port), "_zx_port_create"); + zx_handle_t Self = _zx_process_self(); + + ExitOnErr(_zx_task_bind_exception_port(Self, Scoped.Port, kFuzzingCrash, + ZX_EXCEPTION_PORT_DEBUGGER), + "_zx_task_bind_exception_port"); + + ExitOnErr(_zx_object_signal(*Event, 0, ZX_USER_SIGNAL_0), + "_zx_object_signal"); + + zx_port_packet_t Packet; + ExitOnErr(_zx_port_wait(Scoped.Port, ZX_TIME_INFINITE, &Packet), + "_zx_port_wait"); + + // At this point, we want to get the state of the crashing thread, but + // libFuzzer and the sanitizers assume this will happen from that same thread + // via a POSIX signal handler. "Resurrecting" the thread in the middle of the + // appropriate callback is as simple as forcibly setting the instruction + // pointer/program counter, provided we NEVER EVER return from that function + // (since otherwise our stack will not be valid). + ExitOnErr(_zx_object_get_child(Self, Packet.exception.tid, + ZX_RIGHT_SAME_RIGHTS, &Scoped.Thread), + "_zx_object_get_child"); + + zx_thread_state_general_regs_t GeneralRegisters; + ExitOnErr(_zx_thread_read_state(Scoped.Thread, ZX_THREAD_STATE_GENERAL_REGS, + &GeneralRegisters, sizeof(GeneralRegisters)), + "_zx_thread_read_state"); + + // To unwind properly, we need to push the crashing thread's register state + // onto the stack and jump into a trampoline with CFI instructions on how + // to restore it. +#if defined(__x86_64__) + uintptr_t StackPtr = + (GeneralRegisters.rsp - (128 + sizeof(GeneralRegisters))) & + -(uintptr_t)16; + memcpy(reinterpret_cast(StackPtr), &GeneralRegisters, + sizeof(GeneralRegisters)); + GeneralRegisters.rsp = StackPtr; + GeneralRegisters.rip = reinterpret_cast(CrashTrampolineAsm); + +#elif defined(__aarch64__) + uintptr_t StackPtr = GeneralRegisters.sp - sizeof(GeneralRegisters); + memcpy(reinterpret_cast(StackPtr), &GeneralRegisters, + sizeof(GeneralRegisters)); + GeneralRegisters.sp = StackPtr; + GeneralRegisters.pc = reinterpret_cast(CrashTrampolineAsm); + +#else +#error "Unsupported architecture for fuzzing on Fuchsia" +#endif + + // Now force the crashing thread's state. + ExitOnErr(_zx_thread_write_state(Scoped.Thread, ZX_THREAD_STATE_GENERAL_REGS, + &GeneralRegisters, sizeof(GeneralRegisters)), + "_zx_thread_write_state"); + + ExitOnErr(_zx_task_resume_from_exception(Scoped.Thread, Scoped.Port, 0), + "_zx_task_resume_from_exception"); } } // namespace // Platform specific functions. void SetSignalHandler(const FuzzingOptions &Options) { - zx_status_t rc; - // Set up alarm handler if needed. if (Options.UnitTimeoutSec > 0) { std::thread T(AlarmHandler, Options.UnitTimeoutSec / 2 + 1); @@ -99,23 +304,16 @@ !Options.HandleFpe && !Options.HandleAbrt) return; - // Create an exception port - zx_handle_t *ExceptionPort = new zx_handle_t; - if ((rc = _zx_port_create(0, ExceptionPort)) != ZX_OK) { - Printf("libFuzzer: zx_port_create failed: %s\n", _zx_status_get_string(rc)); - exit(1); - } + // Set up the crash handler and wait until its ready before proceeding. + zx_handle_t Event; + ExitOnErr(_zx_event_create(0, &Event), "_zx_event_create"); - // Bind the port to receive exceptions from our process - if ((rc = _zx_task_bind_exception_port(_zx_process_self(), *ExceptionPort, - kFuzzingCrash, 0)) != ZX_OK) { - Printf("libFuzzer: unable to bind exception port: %s\n", - _zx_status_get_string(rc)); - exit(1); - } + std::thread T(CrashHandler, &Event); + zx_status_t Status = + _zx_object_wait_one(Event, ZX_USER_SIGNAL_0, ZX_TIME_INFINITE, nullptr); + _zx_handle_close(Event); + ExitOnErr(Status, "_zx_object_wait_one"); - // Set up the crash handler. - std::thread T(CrashHandler, ExceptionPort); T.detach(); } @@ -127,7 +325,7 @@ zx_status_t rc; zx_info_handle_basic_t Info; if ((rc = _zx_object_get_info(_zx_process_self(), ZX_INFO_HANDLE_BASIC, &Info, - sizeof(Info), NULL, NULL)) != ZX_OK) { + sizeof(Info), NULL, NULL)) != ZX_OK) { Printf("libFuzzer: unable to get info about self: %s\n", _zx_status_get_string(rc)); exit(1); @@ -139,7 +337,7 @@ zx_status_t rc; zx_info_task_stats_t Info; if ((rc = _zx_object_get_info(_zx_process_self(), ZX_INFO_TASK_STATS, &Info, - sizeof(Info), NULL, NULL)) != ZX_OK) { + sizeof(Info), NULL, NULL)) != ZX_OK) { Printf("libFuzzer: unable to get info about self: %s\n", _zx_status_get_string(rc)); exit(1);