/* Copyright (c) 2018, Google Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "abi_test.h" #include #include #include #include #include #include #include #include #if defined(OPENSSL_LINUX) && defined(SUPPORTS_ABI_TEST) && \ defined(BORINGSSL_HAVE_LIBUNWIND) #define UNWIND_TEST_SIGTRAP #define UNW_LOCAL_ONLY #include #include #include #include #include #include #include #include #include #include #endif // LINUX && SUPPORTS_ABI_TEST && HAVE_LIBUNWIND namespace abi_test { namespace internal { static bool g_unwind_tests_enabled = false; std::string FixVAArgsString(const char *str) { std::string ret = str; size_t idx = ret.find(','); if (idx == std::string::npos) { return ret + "()"; } size_t idx2 = idx + 1; while (idx2 < ret.size() && ret[idx2] == ' ') { idx2++; } while (idx > 0 && ret[idx - 1] == ' ') { idx--; } return ret.substr(0, idx) + "(" + ret.substr(idx2) + ")"; } #if defined(SUPPORTS_ABI_TEST) // ForEachMismatch calls |func| for each register where |a| and |b| differ. template static void ForEachMismatch(const CallerState &a, const CallerState &b, const Func &func) { #define CALLER_STATE_REGISTER(type, name) \ if (a.name != b.name) { \ func(#name); \ } LOOP_CALLER_STATE_REGISTERS() #undef CALLER_STATE_REGISTER } // ReadUnwindResult adds the results of the most recent unwind test to |out|. static void ReadUnwindResult(Result *out); crypto_word_t RunTrampoline(Result *out, crypto_word_t func, const crypto_word_t *argv, size_t argc, bool unwind) { CallerState state; RAND_bytes(reinterpret_cast(&state), sizeof(state)); unwind &= g_unwind_tests_enabled; CallerState state2 = state; crypto_word_t ret = abi_test_trampoline(func, &state2, argv, argc, unwind); #if defined(OPENSSL_X86_64) || defined(OPENSSL_X86) // Query and clear the direction flag early, so negative tests do not // interfere with |malloc|. bool direction_flag = abi_test_get_and_clear_direction_flag(); #endif // OPENSSL_X86_64 || OPENSSL_X86 *out = Result(); ForEachMismatch(state, state2, [&](const char *reg) { out->errors.push_back(std::string(reg) + " was not restored after return"); }); #if defined(OPENSSL_X86_64) || defined(OPENSSL_X86) // Linux and Windows ABIs for x86 require the direction flag be cleared on // return. (Some OpenSSL assembly preserves it, which is stronger, but we only // require what is specified by the ABI so |CHECK_ABI| works with C compiler // output.) if (direction_flag) { out->errors.emplace_back("Direction flag set after return"); } #endif // OPENSSL_X86_64 || OPENSSL_X86 if (unwind) { ReadUnwindResult(out); } return ret; } #endif // SUPPORTS_ABI_TEST #if defined(UNWIND_TEST_SIGTRAP) // On Linux, we test unwind metadata using libunwind and |SIGTRAP|. We run the // function under test with the trap flag set. This results in |SIGTRAP|s on // every instruction. We then handle these signals and verify with libunwind. // HandleEINTR runs |func| and returns the result, retrying the operation on // |EINTR|. template static auto HandleEINTR(const Func &func) -> decltype(func()) { decltype(func()) ret; do { ret = func(); } while (ret < 0 && errno == EINTR); return ret; } static bool ReadFileToString(std::string *out, const char *path) { out->clear(); int fd = HandleEINTR([&] { return open(path, O_RDONLY); }); if (fd < 0) { return false; } for (;;) { char buf[1024]; ssize_t ret = HandleEINTR([&] { return read(fd, buf, sizeof(buf)); }); if (ret < 0) { close(fd); return false; } if (ret == 0) { close(fd); return true; } out->append(buf, static_cast(ret)); } } static bool IsBeingDebugged() { std::string status; if (!ReadFileToString(&status, "/proc/self/status")) { perror("error reading /proc/self/status"); return false; } std::string key = "\nTracerPid:\t"; size_t idx = status.find(key); if (idx == std::string::npos) { return false; } idx += key.size(); return idx < status.size() && status[idx] != '0'; } // IsAncestorStackFrame returns true if |a_sp| is an ancestor stack frame of // |b_sp|. static bool IsAncestorStackFrame(unw_word_t a_sp, unw_word_t b_sp) { #if defined(OPENSSL_X86_64) // The stack grows down, so ancestor stack frames have higher addresses. return a_sp > b_sp; #else #error "unknown architecture" #endif } static int CallerStateFromUNWCursor(CallerState *out, unw_cursor_t *cursor) { // |CallerState| uses |crypto_word_t|, while libunwind uses |unw_word_t|, but // both are defined as |uint*_t| from stdint.h, so we can assume the types // match. #if defined(OPENSSL_X86_64) int ret = 0; ret = ret < 0 ? ret : unw_get_reg(cursor, UNW_X86_64_RBX, &out->rbx); ret = ret < 0 ? ret : unw_get_reg(cursor, UNW_X86_64_RBP, &out->rbp); ret = ret < 0 ? ret : unw_get_reg(cursor, UNW_X86_64_R12, &out->r12); ret = ret < 0 ? ret : unw_get_reg(cursor, UNW_X86_64_R13, &out->r13); ret = ret < 0 ? ret : unw_get_reg(cursor, UNW_X86_64_R14, &out->r14); ret = ret < 0 ? ret : unw_get_reg(cursor, UNW_X86_64_R15, &out->r15); return ret; #else #error "unknown architecture" #endif } // Implement some string formatting utilties. Ideally we would use |snprintf|, // but this is called in a signal handler and |snprintf| is not async-signal- // safe. static std::array WordToDecimal( unw_word_t v) { std::array ret; size_t len = 0; do { ret[len++] = '0' + v % 10; v /= 10; } while (v != 0); for (size_t i = 0; i < len / 2; i++) { std::swap(ret[i], ret[len - 1 - i]); } ret[len] = '\0'; return ret; } static std::array WordToHex(unw_word_t v) { static const char kHex[] = "0123456789abcdef"; std::array ret; for (size_t i = sizeof(unw_word_t) - 1; i < sizeof(unw_word_t); i--) { uint8_t b = v & 0xff; v >>= 8; ret[i * 2] = kHex[b >> 4]; ret[i * 2 + 1] = kHex[b & 0xf]; } ret[sizeof(unw_word_t) * 2] = '\0'; return ret; } static void StrCatSignalSafeImpl(bssl::Span out) {} template static void StrCatSignalSafeImpl(bssl::Span out, const char *str, Args... args) { BUF_strlcat(out.data(), str, out.size()); StrCatSignalSafeImpl(out, args...); } template static void StrCatSignalSafe(bssl::Span out, Args... args) { if (out.empty()) { return; } out[0] = '\0'; StrCatSignalSafeImpl(out, args...); } static int UnwindToSignalFrame(unw_cursor_t *cursor) { for (;;) { int ret = unw_is_signal_frame(cursor); if (ret < 0) { return ret; } if (ret != 0) { return 0; // Found the signal frame. } ret = unw_step(cursor); if (ret < 0) { return ret; } } } // IPToString returns a human-readable representation of |ip|, using debug // information from |ctx| if available. |ip| must be the address of |ctx|'s // signal frame. This function is async-signal-safe. static std::array IPToString(unw_word_t ip, unw_context_t *ctx) { std::array ret; // Use a new cursor. The caller's cursor has already been unwound, but // |unw_get_proc_name| is slow so we do not wish to call it all the time. unw_cursor_t cursor; // Work around a bug in libunwind. See // https://git.savannah.gnu.org/gitweb/?p=libunwind.git;a=commit;h=819bf51bbd2da462c2ec3401e8ac9153b6e725e3 OPENSSL_memset(&cursor, 0, sizeof(cursor)); unw_word_t off; if (unw_init_local(&cursor, ctx) != 0 || UnwindToSignalFrame(&cursor) != 0 || unw_get_proc_name(&cursor, ret.data(), ret.size(), &off) != 0) { StrCatSignalSafe(bssl::MakeSpan(ret), "0x", WordToHex(ip).data()); return ret; } size_t len = strlen(ret.data()); // Print the offset in decimal, to match gdb's disassembly output and ease // debugging. StrCatSignalSafe(bssl::MakeSpan(ret).subspan(len), "+", WordToDecimal(off).data(), " (0x", WordToHex(ip).data(), ")"); return ret; } static pthread_t g_main_thread; // g_in_trampoline is true if we are in an instrumented |abi_test_trampoline| // call, in the region that triggers |SIGTRAP|. static bool g_in_trampoline = false; // g_unwind_function_done, if |g_in_trampoline| is true, is whether the function // under test has returned. It is undefined otherwise. static bool g_unwind_function_done; // g_trampoline_state, if |g_in_trampoline| is true, is the state the function // under test must preserve. It is undefined otherwise. static CallerState g_trampoline_state; // g_trampoline_sp, if |g_in_trampoline| is true, is the stack pointer of the // trampoline frame. It is undefined otherwise. static unw_word_t g_trampoline_sp; // kMaxUnwindErrors is the maximum number of unwind errors reported per // function. If a function's unwind tables are wrong, we are otherwise likely to // repeat the same error at multiple addresses. static constexpr size_t kMaxUnwindErrors = 10; // Errors are saved in a signal handler. We use a static buffer to avoid // allocation. static size_t num_unwind_errors = 0; static char unwind_errors[kMaxUnwindErrors][512]; template static void AddUnwindError(Args... args) { if (num_unwind_errors >= kMaxUnwindErrors) { return; } StrCatSignalSafe(unwind_errors[num_unwind_errors], args...); num_unwind_errors++; } template [[noreturn]] static void FatalError(Args... args) { // We cannot use |snprintf| here because it is not async-signal-safe. char buf[512]; StrCatSignalSafe(buf, args..., "\n"); write(STDERR_FILENO, buf, strlen(buf)); abort(); } static void TrapHandler(int sig) { // Note this is a signal handler, so only async-signal-safe functions may be // used here. See signal-safety(7). libunwind promises local unwind is // async-signal-safe. // |pthread_equal| is not listed as async-signal-safe, but this is clearly an // oversight. if (!pthread_equal(g_main_thread, pthread_self())) { FatalError("SIGTRAP on background thread"); } unw_context_t ctx; int ret = unw_getcontext(&ctx); unw_cursor_t cursor; // Work around a bug in libunwind which breaks rax and rdx recovery. This // breaks functions which temporarily use rax as the CFA register. See // https://git.savannah.gnu.org/gitweb/?p=libunwind.git;a=commit;h=819bf51bbd2da462c2ec3401e8ac9153b6e725e3 OPENSSL_memset(&cursor, 0, sizeof(cursor)); ret = ret < 0 ? ret : unw_init_local(&cursor, &ctx); ret = ret < 0 ? ret : UnwindToSignalFrame(&cursor); unw_word_t sp, ip; ret = ret < 0 ? ret : unw_get_reg(&cursor, UNW_REG_SP, &sp); ret = ret < 0 ? ret : unw_get_reg(&cursor, UNW_REG_IP, &ip); if (ret < 0) { FatalError("Error initializing unwind cursor: ", unw_strerror(ret)); } const unw_word_t kStartAddress = reinterpret_cast(&abi_test_unwind_start); const unw_word_t kReturnAddress = reinterpret_cast(&abi_test_unwind_return); const unw_word_t kStopAddress = reinterpret_cast(&abi_test_unwind_stop); if (!g_in_trampoline) { if (ip != kStartAddress) { FatalError("Unexpected SIGTRAP at ", IPToString(ip, &ctx).data()); } // Save the current state and begin. g_in_trampoline = true; g_unwind_function_done = false; g_trampoline_sp = sp; ret = CallerStateFromUNWCursor(&g_trampoline_state, &cursor); if (ret < 0) { FatalError("Error getting initial caller state: ", unw_strerror(ret)); } } else { if (sp == g_trampoline_sp || g_unwind_function_done) { // |g_unwind_function_done| should imply |sp| is |g_trampoline_sp|, but // clearing the trap flag in x86 briefly displaces the stack pointer. // // Also note we check both |ip| and |sp| below, in case the function under // test is also |abi_test_trampoline|. if (ip == kReturnAddress && sp == g_trampoline_sp) { g_unwind_function_done = true; } if (ip == kStopAddress && sp == g_trampoline_sp) { // |SIGTRAP| is fatal again. g_in_trampoline = false; } } else if (IsAncestorStackFrame(sp, g_trampoline_sp)) { // This should never happen. We went past |g_trampoline_sp| without // stopping at |kStopAddress|. AddUnwindError("stack frame is before caller at ", IPToString(ip, &ctx).data()); g_in_trampoline = false; } else if (num_unwind_errors < kMaxUnwindErrors) { for (;;) { ret = unw_step(&cursor); if (ret < 0) { AddUnwindError("error unwinding from ", IPToString(ip, &ctx).data(), ": ", unw_strerror(ret)); break; } if (ret == 0) { AddUnwindError("could not unwind to starting frame from ", IPToString(ip, &ctx).data()); break; } unw_word_t cur_sp; ret = unw_get_reg(&cursor, UNW_REG_SP, &cur_sp); if (ret < 0) { AddUnwindError("error recovering stack pointer unwinding from ", IPToString(ip, &ctx).data(), ": ", unw_strerror(ret)); break; } if (IsAncestorStackFrame(cur_sp, g_trampoline_sp)) { AddUnwindError("unwound past starting frame from ", IPToString(ip, &ctx).data()); break; } if (cur_sp == g_trampoline_sp) { // We found the parent frame. Check the return address. unw_word_t cur_ip; ret = unw_get_reg(&cursor, UNW_REG_IP, &cur_ip); if (ret < 0) { AddUnwindError("error recovering return address unwinding from ", IPToString(ip, &ctx).data(), ": ", unw_strerror(ret)); } else if (cur_ip != kReturnAddress) { AddUnwindError("wrong return address unwinding from ", IPToString(ip, &ctx).data()); } // Check the remaining registers. CallerState state; ret = CallerStateFromUNWCursor(&state, &cursor); if (ret < 0) { AddUnwindError("error recovering registers unwinding from ", IPToString(ip, &ctx).data(), ": ", unw_strerror(ret)); } else { ForEachMismatch(state, g_trampoline_state, [&](const char *reg) { AddUnwindError(reg, " was not recovered unwinding from ", IPToString(ip, &ctx).data()); }); } break; } } } } } static void ReadUnwindResult(Result *out) { for (size_t i = 0; i < num_unwind_errors; i++) { out->errors.emplace_back(unwind_errors[i]); } if (num_unwind_errors == kMaxUnwindErrors) { out->errors.emplace_back("(additional errors omitted)"); } num_unwind_errors = 0; } static void EnableUnwindTestsImpl() { if (IsBeingDebugged()) { // Unwind tests drive logic via |SIGTRAP|, which conflicts with debuggers. fprintf(stderr, "Debugger detected. Disabling unwind tests.\n"); return; } g_main_thread = pthread_self(); struct sigaction trap_action; OPENSSL_memset(&trap_action, 0, sizeof(trap_action)); sigemptyset(&trap_action.sa_mask); trap_action.sa_handler = TrapHandler; if (sigaction(SIGTRAP, &trap_action, NULL) != 0) { perror("sigaction"); abort(); } g_unwind_tests_enabled = true; } #else // TODO(davidben): Implement an SEH-based unwind-tester. #if defined(SUPPORTS_ABI_TEST) static void ReadUnwindResult(Result *) {} #endif static void EnableUnwindTestsImpl() {} #endif // UNWIND_TEST_SIGTRAP } // namespace internal void EnableUnwindTests() { internal::EnableUnwindTestsImpl(); } bool UnwindTestsEnabled() { return internal::g_unwind_tests_enabled; } } // namespace abi_test