diff options
author | Nick Mathewson <nickm@torproject.org> | 2018-06-21 09:53:08 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2018-06-21 10:47:11 -0400 |
commit | 2cfcb7b364541c27749ae0df73eabd7a56797f93 (patch) | |
tree | 1fb03761e4631f1d5edd893fc9164a49ff8981b6 /src/lib/err | |
parent | b2346b12019c45288bbae3bd009fe0bafe80ff58 (diff) | |
download | tor-2cfcb7b364541c27749ae0df73eabd7a56797f93.tar.gz tor-2cfcb7b364541c27749ae0df73eabd7a56797f93.zip |
Extract error functionality into a new lowest-level library.
Diffstat (limited to 'src/lib/err')
-rw-r--r-- | src/lib/err/backtrace.c | 282 | ||||
-rw-r--r-- | src/lib/err/backtrace.h | 29 | ||||
-rw-r--r-- | src/lib/err/include.am | 17 | ||||
-rw-r--r-- | src/lib/err/torerr.c | 229 | ||||
-rw-r--r-- | src/lib/err/torerr.h | 47 |
5 files changed, 604 insertions, 0 deletions
diff --git a/src/lib/err/backtrace.c b/src/lib/err/backtrace.c new file mode 100644 index 0000000000..d83cbba0e8 --- /dev/null +++ b/src/lib/err/backtrace.c @@ -0,0 +1,282 @@ +/* Copyright (c) 2013-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file backtrace.c + * + * \brief Functions to produce backtraces on bugs, crashes, or assertion + * failures. + * + * Currently, we've only got an implementation here using the backtrace() + * family of functions, which are sometimes provided by libc and sometimes + * provided by libexecinfo. We tie into the sigaction() backend in order to + * detect crashes. + */ + +#include "orconfig.h" +#include "common/torerr.h" + +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#ifdef HAVE_CYGWIN_SIGNAL_H +#include <cygwin/signal.h> +#elif defined(HAVE_SYS_UCONTEXT_H) +#include <sys/ucontext.h> +#elif defined(HAVE_UCONTEXT_H) +#include <ucontext.h> +#endif /* defined(HAVE_CYGWIN_SIGNAL_H) || ... */ + +#ifdef HAVE_PTHREAD_H +#include <pthread.h> +#endif + +#define EXPOSE_CLEAN_BACKTRACE +#include "common/backtrace.h" +#include "common/torerr.h" + +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && \ + defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION) +#define USE_BACKTRACE +#endif + +#if !defined(USE_BACKTRACE) +#define NO_BACKTRACE_IMPL +#endif + +// Redundant with util.h, but doing it here so we can avoid that dependency. +#define raw_free free + +#ifdef USE_BACKTRACE +/** Version of Tor to report in backtrace messages. */ +static char bt_version[128] = ""; + +/** Largest stack depth to try to dump. */ +#define MAX_DEPTH 256 +/** Static allocation of stack to dump. This is static so we avoid stack + * pressure. */ +static void *cb_buf[MAX_DEPTH]; +/** Protects cb_buf from concurrent access. Pthreads, since this code + * is Unix-only, and since this code needs to be lowest-level. */ +static pthread_mutex_t cb_buf_mutex = PTHREAD_MUTEX_INITIALIZER; + +/** Change a stacktrace in <b>stack</b> of depth <b>depth</b> so that it will + * log the correct function from which a signal was received with context + * <b>ctx</b>. (When we get a signal, the current function will not have + * called any other function, and will therefore have not pushed its address + * onto the stack. Fortunately, we usually have the program counter in the + * ucontext_t structure. + */ +void +clean_backtrace(void **stack, size_t depth, const ucontext_t *ctx) +{ +#ifdef PC_FROM_UCONTEXT +#if defined(__linux__) + const size_t n = 1; +#elif defined(__darwin__) || defined(__APPLE__) || defined(OpenBSD) \ + || defined(__FreeBSD__) + const size_t n = 2; +#else + const size_t n = 1; +#endif /* defined(__linux__) || ... */ + if (depth <= n) + return; + + stack[n] = (void*) ctx->PC_FROM_UCONTEXT; +#else /* !(defined(PC_FROM_UCONTEXT)) */ + (void) depth; + (void) ctx; + (void) stack; +#endif /* defined(PC_FROM_UCONTEXT) */ +} + +/** Log a message <b>msg</b> at <b>severity</b> in <b>domain</b>, and follow + * that with a backtrace log. Send messages via the tor_log function at + * logger". */ +void +log_backtrace_impl(int severity, int domain, const char *msg, + tor_log_fn logger) +{ + size_t depth; + char **symbols; + size_t i; + + pthread_mutex_lock(&cb_buf_mutex); + + depth = backtrace(cb_buf, MAX_DEPTH); + symbols = backtrace_symbols(cb_buf, (int)depth); + + logger(severity, domain, "%s. Stack trace:", msg); + if (!symbols) { + /* LCOV_EXCL_START -- we can't provoke this. */ + logger(severity, domain, " Unable to generate backtrace."); + goto done; + /* LCOV_EXCL_STOP */ + } + for (i=0; i < depth; ++i) { + logger(severity, domain, " %s", symbols[i]); + } + raw_free(symbols); + + done: + pthread_mutex_unlock(&cb_buf_mutex); +} + +static void crash_handler(int sig, siginfo_t *si, void *ctx_) + __attribute__((noreturn)); + +/** Signal handler: write a crash message with a stack trace, and die. */ +static void +crash_handler(int sig, siginfo_t *si, void *ctx_) +{ + char buf[40]; + size_t depth; + ucontext_t *ctx = (ucontext_t *) ctx_; + int n_fds, i; + const int *fds = NULL; + + (void) si; + + depth = backtrace(cb_buf, MAX_DEPTH); + /* Clean up the top stack frame so we get the real function + * name for the most recently failing function. */ + clean_backtrace(cb_buf, depth, ctx); + + format_dec_number_sigsafe((unsigned)sig, buf, sizeof(buf)); + + tor_log_err_sigsafe(bt_version, " died: Caught signal ", buf, "\n", + NULL); + + n_fds = tor_log_get_sigsafe_err_fds(&fds); + for (i=0; i < n_fds; ++i) + backtrace_symbols_fd(cb_buf, (int)depth, fds[i]); + + abort(); +} + +/** Write a backtrace to all of the emergency-error fds. */ +void +dump_stack_symbols_to_error_fds(void) +{ + int n_fds, i; + const int *fds = NULL; + size_t depth; + + depth = backtrace(cb_buf, MAX_DEPTH); + + n_fds = tor_log_get_sigsafe_err_fds(&fds); + for (i=0; i < n_fds; ++i) + backtrace_symbols_fd(cb_buf, (int)depth, fds[i]); +} + +/** Install signal handlers as needed so that when we crash, we produce a + * useful stack trace. Return 0 on success, -errno on failure. */ +static int +install_bt_handler(const char *software) +{ + int trap_signals[] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGSYS, + SIGIO, -1 }; + int i, rv=0; + + strncpy(bt_version, software, sizeof(bt_version) - 1); + bt_version[sizeof(bt_version) - 1] = 0; + + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_handler; + sa.sa_flags = SA_SIGINFO; + sigfillset(&sa.sa_mask); + + for (i = 0; trap_signals[i] >= 0; ++i) { + if (sigaction(trap_signals[i], &sa, NULL) == -1) { + /* LCOV_EXCL_START */ + rv = -errno; + /* LCOV_EXCL_STOP */ + } + } + + { + /* Now, generate (but do not log) a backtrace. This ensures that + * libc has pre-loaded the symbols we need to dump things, so that later + * reads won't be denied by the sandbox code */ + char **symbols; + size_t depth = backtrace(cb_buf, MAX_DEPTH); + symbols = backtrace_symbols(cb_buf, (int) depth); + if (symbols) + raw_free(symbols); + } + + return rv; +} + +/** Uninstall crash handlers. */ +static void +remove_bt_handler(void) +{ +} +#endif /* defined(USE_BACKTRACE) */ + +#ifdef NO_BACKTRACE_IMPL +void +log_backtrace_impl(int severity, int domain, const char *msg, + tor_log_fn logger) +{ + logger(severity, domain, "%s. (Stack trace not available)", msg); +} + +static int +install_bt_handler(const char *software) +{ + (void) software; + return 0; +} + +static void +remove_bt_handler(void) +{ +} + +void +dump_stack_symbols_to_error_fds(void) +{ +} +#endif /* defined(NO_BACKTRACE_IMPL) */ + +/** Set up code to handle generating error messages on crashes. */ +int +configure_backtrace_handler(const char *tor_version) +{ + char version[128]; + strncpy(version, "Tor", sizeof(version)-1); + + if (tor_version) { + strncat(version, " ", sizeof(version)-1); + strncat(version, tor_version, sizeof(version)-1); + } + + version[sizeof(version) - 1] = 0; + + return install_bt_handler(version); +} + +/** Perform end-of-process cleanup for code that generates error messages on + * crashes. */ +void +clean_up_backtrace_handler(void) +{ + remove_bt_handler(); +} diff --git a/src/lib/err/backtrace.h b/src/lib/err/backtrace.h new file mode 100644 index 0000000000..0a145fc92d --- /dev/null +++ b/src/lib/err/backtrace.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2013-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_BACKTRACE_H +#define TOR_BACKTRACE_H + +#include "orconfig.h" +#include "common/compat_compiler.h" + +typedef void (*tor_log_fn)(int, unsigned, const char *fmt, ...) + CHECK_PRINTF(3,4); + +void log_backtrace_impl(int severity, int domain, const char *msg, + tor_log_fn logger); +int configure_backtrace_handler(const char *tor_version); +void clean_up_backtrace_handler(void); +void dump_stack_symbols_to_error_fds(void); + +#define log_backtrace(sev, dom, msg) \ + log_backtrace_impl((sev), (dom), (msg), tor_log) + +#ifdef EXPOSE_CLEAN_BACKTRACE +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && \ + defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION) +void clean_backtrace(void **stack, size_t depth, const ucontext_t *ctx); +#endif +#endif /* defined(EXPOSE_CLEAN_BACKTRACE) */ + +#endif /* !defined(TOR_BACKTRACE_H) */ diff --git a/src/lib/err/include.am b/src/lib/err/include.am new file mode 100644 index 0000000000..257ba3004b --- /dev/null +++ b/src/lib/err/include.am @@ -0,0 +1,17 @@ + +noinst_LIBRARIES += src/lib/libtor-err.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-err-testing.a +endif + +src_lib_libtor_err_a_SOURCES = \ + src/lib/err/backtrace.c \ + src/lib/err/torerr.c + +src_lib_libtor_err_testing_a_SOURCES = \ + $(src_lib_libtor_err_a_SOURCES) + +noinst_HEADERS += \ + src/lib/err/backtrace.h \ + src/lib/err/torerr.h diff --git a/src/lib/err/torerr.c b/src/lib/err/torerr.c new file mode 100644 index 0000000000..0e0c4db355 --- /dev/null +++ b/src/lib/err/torerr.c @@ -0,0 +1,229 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file torerr.c + * + * \brief Handling code for unrecoverable emergencies, at a lower level + * than the logging code. + */ + +#include "orconfig.h" +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include "common/torerr.h" +#include "common/backtrace.h" + +/** Array of fds to log crash-style warnings to. */ +static int sigsafe_log_fds[TOR_SIGSAFE_LOG_MAX_FDS] = { STDERR_FILENO }; +/** The number of elements used in sigsafe_log_fds */ +static int n_sigsafe_log_fds = 1; +/** Log granularity in milliseconds. */ +static int log_granularity = 1000; + +/** Write <b>s</b> to each element of sigsafe_log_fds. Return 0 on success, -1 + * on failure. */ +static int +tor_log_err_sigsafe_write(const char *s) +{ + int i; + ssize_t r; + size_t len = strlen(s); + int err = 0; + for (i=0; i < n_sigsafe_log_fds; ++i) { + r = write(sigsafe_log_fds[i], s, len); + err += (r != (ssize_t)len); + } + return err ? -1 : 0; +} + +/** Given a list of string arguments ending with a NULL, writes them + * to our logs and to stderr (if possible). This function is safe to call + * from within a signal handler. */ +void +tor_log_err_sigsafe(const char *m, ...) +{ + va_list ap; + const char *x; + char timebuf[33]; + time_t now = time(NULL); + + if (!m) + return; + if (log_granularity >= 2000) { + int g = log_granularity / 1000; + now -= now % g; + } + timebuf[0] = now < 0 ? '-' : ' '; + if (now < 0) now = -now; + timebuf[1] = '\0'; + format_dec_number_sigsafe(now, timebuf+1, sizeof(timebuf)-1); + tor_log_err_sigsafe_write("\n==========================================" + "================== T="); + tor_log_err_sigsafe_write(timebuf); + tor_log_err_sigsafe_write("\n"); + tor_log_err_sigsafe_write(m); + va_start(ap, m); + while ((x = va_arg(ap, const char*))) { + tor_log_err_sigsafe_write(x); + } + va_end(ap); +} + +/** Set *<b>out</b> to a pointer to an array of the fds to log errors to from + * inside a signal handler or other emergency condition. Return the number of + * elements in the array. */ +int +tor_log_get_sigsafe_err_fds(const int **out) +{ + *out = sigsafe_log_fds; + return n_sigsafe_log_fds; +} + +/** + * Update the list of fds that get errors from inside a signal handler or + * other emergency condition. Ignore any beyond the first + * TOR_SIGSAFE_LOG_MAX_FDS. + */ +void +tor_log_set_sigsafe_err_fds(const int *fds, int n) +{ + if (n > TOR_SIGSAFE_LOG_MAX_FDS) { + n = TOR_SIGSAFE_LOG_MAX_FDS; + } + + memcpy(sigsafe_log_fds, fds, n * sizeof(int)); + n_sigsafe_log_fds = n; +} + +/** + * Set the granularity (in ms) to use when reporting fatal errors outside + * the logging system. + */ +void +tor_log_sigsafe_err_set_granularity(int ms) +{ + log_granularity = ms; +} + +/** + * Log an emergency assertion failure message. + * + * This kind of message is safe to send from within a log handler, + * a signal handler, or other emergency situation. + */ +void +tor_raw_assertion_failed_msg_(const char *file, int line, const char *expr, + const char *msg) +{ + char linebuf[16]; + format_dec_number_sigsafe(line, linebuf, sizeof(linebuf)); + tor_log_err_sigsafe("INTERNAL ERROR: Raw assertion failed at ", + file, ":", linebuf, ": ", expr, NULL); + if (msg) { + tor_log_err_sigsafe_write(msg); + tor_log_err_sigsafe_write("\n"); + } + + dump_stack_symbols_to_error_fds(); +} + +/* As format_{hex,dex}_number_sigsafe, but takes a <b>radix</b> argument + * in range 2..16 inclusive. */ +static int +format_number_sigsafe(unsigned long x, char *buf, int buf_len, + unsigned int radix) +{ + unsigned long tmp; + int len; + char *cp; + + /* NOT tor_assert. This needs to be safe to run from within a signal + * handler, and from within the 'tor_assert() has failed' code. Not even + * raw_assert(), since raw_assert() calls this function on failure. */ + if (radix < 2 || radix > 16) + return 0; + + /* Count how many digits we need. */ + tmp = x; + len = 1; + while (tmp >= radix) { + tmp /= radix; + ++len; + } + + /* Not long enough */ + if (!buf || len >= buf_len) + return 0; + + cp = buf + len; + *cp = '\0'; + do { + unsigned digit = (unsigned) (x % radix); + if (cp <= buf) { + /* Not tor_assert(); see above. */ + abort(); + } + --cp; + *cp = "0123456789ABCDEF"[digit]; + x /= radix; + } while (x); + + /* NOT tor_assert; see above. */ + if (cp != buf) { + abort(); // LCOV_EXCL_LINE + } + + return len; +} + +/** + * Helper function to output hex numbers from within a signal handler. + * + * Writes the nul-terminated hexadecimal digits of <b>x</b> into a buffer + * <b>buf</b> of size <b>buf_len</b>, and return the actual number of digits + * written, not counting the terminal NUL. + * + * If there is insufficient space, write nothing and return 0. + * + * This accepts an unsigned int because format_helper_exit_status() needs to + * call it with a signed int and an unsigned char, and since the C standard + * does not guarantee that an int is wider than a char (an int must be at + * least 16 bits but it is permitted for a char to be that wide as well), we + * can't assume a signed int is sufficient to accommodate an unsigned char. + * Thus, format_helper_exit_status() will still need to emit any require '-' + * on its own. + * + * For most purposes, you'd want to use tor_snprintf("%x") instead of this + * function; it's designed to be used in code paths where you can't call + * arbitrary C functions. + */ +int +format_hex_number_sigsafe(unsigned long x, char *buf, int buf_len) +{ + return format_number_sigsafe(x, buf, buf_len, 16); +} + +/** As format_hex_number_sigsafe, but format the number in base 10. */ +int +format_dec_number_sigsafe(unsigned long x, char *buf, int buf_len) +{ + return format_number_sigsafe(x, buf, buf_len, 10); +} diff --git a/src/lib/err/torerr.h b/src/lib/err/torerr.h new file mode 100644 index 0000000000..10d9f481c0 --- /dev/null +++ b/src/lib/err/torerr.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file torerr.h + * + * \brief Headers for torerr.c. + **/ + +#ifndef TOR_TORERR_H +#define TOR_TORERR_H + +#include "common/compat_compiler.h" + +/* The raw_assert...() variants are for use within code that can't call + * tor_assertion_failed_() because of call circularity issues. */ +#define raw_assert(expr) STMT_BEGIN \ + if (!(expr)) { \ + tor_raw_assertion_failed_msg_(__FILE__, __LINE__, #expr, NULL); \ + abort(); \ + } \ + STMT_END +#define raw_assert_unreached(expr) raw_assert(0) +#define raw_assert_unreached_msg(msg) STMT_BEGIN \ + tor_raw_assertion_failed_msg_(__FILE__, __LINE__, "0", (msg)); \ + abort(); \ + STMT_END + +void tor_raw_assertion_failed_msg_(const char *file, int line, + const char *expr, + const char *msg); + +/** Maximum number of fds that will get notifications if we crash */ +#define TOR_SIGSAFE_LOG_MAX_FDS 8 + +void tor_log_err_sigsafe(const char *m, ...); +int tor_log_get_sigsafe_err_fds(const int **out); +void tor_log_set_sigsafe_err_fds(const int *fds, int n); +void tor_log_sigsafe_err_set_granularity(int ms); + +int format_hex_number_sigsafe(unsigned long x, char *buf, int max_len); +int format_dec_number_sigsafe(unsigned long x, char *buf, int max_len); + +#endif /* !defined(TOR_TORLOG_H) */ |