blob: e86f2f45c56f0ae8e43549d6f3696b9729ab1bd0 [file] [log] [blame]
/* 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 <stdarg.h>
#include <stdio.h>
#include <algorithm>
#include <array>
#include <openssl/buf.h>
#include <openssl/mem.h>
#include <openssl/rand.h>
#include <openssl/span.h>
#if defined(OPENSSL_LINUX) && defined(SUPPORTS_ABI_TEST) && \
defined(BORINGSSL_HAVE_LIBUNWIND)
#define UNWIND_TEST_SIGTRAP
#define UNW_LOCAL_ONLY
#include <errno.h>
#include <fcntl.h>
#include <libunwind.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#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 <typename Func>
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<uint8_t *>(&state), sizeof(state));
unwind &= g_unwind_tests_enabled;
CallerState state2 = state;
crypto_word_t ret = abi_test_trampoline(func, &state2, argv, argc, unwind);
*out = Result();
ForEachMismatch(state, state2, [&](const char *reg) {
out->errors.push_back(std::string(reg) + " was not restored after return");
});
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 <typename Func>
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<size_t>(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<char, DECIMAL_SIZE(unw_word_t) + 1> WordToDecimal(
unw_word_t v) {
std::array<char, DECIMAL_SIZE(unw_word_t) + 1> 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<char, sizeof(unw_word_t) * 2 + 1> WordToHex(unw_word_t v) {
static const char kHex[] = "0123456789abcdef";
std::array<char, sizeof(unw_word_t) * 2 + 1> 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<char> out) {}
template <typename... Args>
static void StrCatSignalSafeImpl(bssl::Span<char> out, const char *str,
Args... args) {
BUF_strlcat(out.data(), str, out.size());
StrCatSignalSafeImpl(out, args...);
}
template <typename... Args>
static void StrCatSignalSafe(bssl::Span<char> 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<char, 256> IPToString(unw_word_t ip, unw_context_t *ctx) {
std::array<char, 256> 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 <typename... Args>
static void AddUnwindError(Args... args) {
if (num_unwind_errors >= kMaxUnwindErrors) {
return;
}
StrCatSignalSafe(unwind_errors[num_unwind_errors], args...);
num_unwind_errors++;
}
template <typename... Args>
[[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<unw_word_t>(&abi_test_unwind_start);
const unw_word_t kReturnAddress =
reinterpret_cast<unw_word_t>(&abi_test_unwind_return);
const unw_word_t kStopAddress =
reinterpret_cast<unw_word_t>(&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