diff options
author | Nick Mathewson <nickm@torproject.org> | 2013-11-18 11:00:16 -0500 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2013-11-18 11:00:16 -0500 |
commit | fbc20294aaf67f9434d5b1e107b8e1c43e42a3a4 (patch) | |
tree | 0d0f0b0aa37423e8f36f11e8410849e48be59ba6 /src/common | |
parent | 7a2b30fe16eacc040b3dd11f8c39c410628c2f43 (diff) | |
parent | c81f64ab44f71a97649a44e10d6fcbf15aa8c835 (diff) | |
download | tor-fbc20294aaf67f9434d5b1e107b8e1c43e42a3a4.tar tor-fbc20294aaf67f9434d5b1e107b8e1c43e42a3a4.tar.gz |
Merge branch 'backtrace_squashed'
Conflicts:
src/common/sandbox.c
src/common/sandbox.h
src/common/util.c
src/or/main.c
src/test/include.am
src/test/test.c
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/backtrace.c | 202 | ||||
-rw-r--r-- | src/common/backtrace.h | 12 | ||||
-rw-r--r-- | src/common/include.am | 2 | ||||
-rw-r--r-- | src/common/log.c | 140 | ||||
-rw-r--r-- | src/common/sandbox.c | 47 | ||||
-rw-r--r-- | src/common/sandbox.h | 3 | ||||
-rw-r--r-- | src/common/torlog.h | 8 | ||||
-rw-r--r-- | src/common/util.c | 111 | ||||
-rw-r--r-- | src/common/util.h | 17 |
9 files changed, 433 insertions, 109 deletions
diff --git a/src/common/backtrace.c b/src/common/backtrace.c new file mode 100644 index 000000000..78bc66efb --- /dev/null +++ b/src/common/backtrace.c @@ -0,0 +1,202 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "backtrace.h" +#include "compat.h" +#include "util.h" +#include "torlog.h" + +#define __USE_GNU + +#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 +#ifdef HAVE_UCONTEXT_H +#include <ucontext.h> +#endif +#ifdef HAVE_SYS_UCONTEXT_H +#include <sys/ucontext.h> +#endif + +#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 + +/** Version of Tor to report in backtrace messages. */ +static char *bt_version = NULL; + +#ifdef USE_BACKTRACE +/** 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]; + +/** 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. + */ +static void +clean_backtrace(void **stack, int depth, const ucontext_t *ctx) +{ +#ifdef PC_FROM_UCONTEXT +#if defined(__linux__) + const int n = 1; +#elif defined(__darwin__) || defined(__APPLE__) || defined(__OpenBSD__) \ + || defined(__FreeBSD__) + const int n = 2; +#else + const int n = 1; +#endif + if (depth <= n) + return; + + stack[n] = (void*) ctx->PC_FROM_UCONTEXT; +#else + (void) depth; + (void) ctx; +#endif +} + +/** Log a message <b>msg</b> at <b>severity</b> in <b>domain</b>, and follow + * that with a backtrace log. */ +void +log_backtrace(int severity, int domain, const char *msg) +{ + int depth = backtrace(cb_buf, MAX_DEPTH); + char **symbols = backtrace_symbols(cb_buf, depth); + int i; + tor_log(severity, domain, "%s. Stack trace:", msg); + if (!symbols) { + tor_log(severity, domain, " Unable to generate backtrace."); + return; + } + for (i=0; i < depth; ++i) { + tor_log(severity, domain, " %s", symbols[i]); + } + free(symbols); +} + +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]; + int 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, depth, fds[i]); + + abort(); +} + +/** Install signal handlers as needed so that when we crash, we produce a + * useful stack trace. Return 0 on success, -1 on failure. */ +static int +install_bt_handler(void) +{ + int trap_signals[] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGSYS, + SIGIO, -1 }; + int i, rv=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) { + log_warn(LD_BUG, "Sigaction failed: %s", strerror(errno)); + rv = -1; + } + } + return rv; +} + +/** Uninstall crash handlers. */ +static void +remove_bt_handler(void) +{ + /* We don't need to actually free anything at exit here. */ +} +#endif + +#ifdef NO_BACKTRACE_IMPL +void +log_backtrace(int severity, int domain, const char *msg) +{ + tor_log(severity, domain, "%s. (Stack trace not available)", msg); +} + +static int +install_bt_handler(void) +{ + return 0; +} + +static void +remove_bt_handler(void) +{ +} +#endif + +/** Set up code to handle generating error messages on crashes. */ +int +configure_backtrace_handler(const char *tor_version) +{ + tor_free(bt_version); + if (!tor_version) + tor_version = ""; + tor_asprintf(&bt_version, "Tor %s", tor_version); + + return install_bt_handler(); +} + +/** Perform end-of-process cleanup for code that generates error messages on + * crashes. */ +void +clean_up_backtrace_handler(void) +{ + remove_bt_handler(); + + tor_free(bt_version); +} + diff --git a/src/common/backtrace.h b/src/common/backtrace.h new file mode 100644 index 000000000..765436fee --- /dev/null +++ b/src/common/backtrace.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_BACKTRACE_H +#define TOR_BACKTRACE_H + +void log_backtrace(int severity, int domain, const char *msg); +int configure_backtrace_handler(const char *tor_version); +void clean_up_backtrace_handler(void); + +#endif + diff --git a/src/common/include.am b/src/common/include.am index 032befd20..814786b77 100644 --- a/src/common/include.am +++ b/src/common/include.am @@ -50,6 +50,7 @@ endif LIBOR_A_SOURCES = \ src/common/address.c \ + src/common/backtrace.c \ src/common/compat.c \ src/common/container.c \ src/common/di_ops.c \ @@ -90,6 +91,7 @@ src_common_libor_event_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) COMMONHEADERS = \ src/common/address.h \ + src/common/backtrace.h \ src/common/aes.h \ src/common/ciphers.inc \ src/common/compat.h \ diff --git a/src/common/log.c b/src/common/log.c index 303fba93a..dffda45c5 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -87,12 +87,12 @@ should_log_function_name(log_domain_mask_t domain, int severity) case LOG_DEBUG: case LOG_INFO: /* All debugging messages occur in interesting places. */ - return 1; + return (domain & LD_NOFUNCNAME) == 0; case LOG_NOTICE: case LOG_WARN: case LOG_ERR: /* We care about places where bugs occur. */ - return (domain == LD_BUG); + return (domain & (LD_BUG|LD_NOFUNCNAME)) == LD_BUG; default: /* Call assert, not tor_assert, since tor_assert calls log on failure. */ assert(0); return 0; @@ -443,6 +443,126 @@ tor_log(int severity, log_domain_mask_t domain, const char *format, ...) va_end(ap); } +/** Maximum number of fds that will get notifications if we crash */ +#define MAX_SIGSAFE_FDS 8 +/** Array of fds to log crash-style warnings to. */ +static int sigsafe_log_fds[MAX_SIGSAFE_FDS] = { STDERR_FILENO }; +/** The number of elements used in sigsafe_log_fds */ +static int n_sigsafe_log_fds = 1; + +/** 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[32]; + time_t now = time(NULL); + + if (!m) + return; + if (log_time_granularity >= 2000) { + int g = log_time_granularity / 1000; + now -= now % g; + } + timebuf[0] = '\0'; + format_dec_number_sigsafe(now, timebuf, sizeof(timebuf)); + 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. 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; +} + +/** Helper function; return true iff the <b>n</b>-element array <b>array</b> + * contains <b>item</b>. */ +static int +int_array_contains(const int *array, int n, int item) +{ + int j; + for (j = 0; j < n; ++j) { + if (array[j] == item) + return 1; + } + return 0; +} + +/** Function to call whenever the list of logs changes to get ready to log + * from signal handlers. */ +void +tor_log_update_sigsafe_err_fds(void) +{ + const logfile_t *lf; + int found_real_stderr = 0; + + LOCK_LOGS(); + /* Reserve the first one for stderr. This is safe because when we daemonize, + * we dup2 /dev/null to stderr, */ + sigsafe_log_fds[0] = STDERR_FILENO; + n_sigsafe_log_fds = 1; + + for (lf = logfiles; lf; lf = lf->next) { + /* Don't try callback to the control port, or syslogs: We can't + * do them from a signal handler. Don't try stdout: we always do stderr. + */ + if (lf->is_temporary || lf->is_syslog || + lf->callback || lf->seems_dead || lf->fd < 0) + continue; + if (lf->severities->masks[SEVERITY_MASK_IDX(LOG_ERR)] & + (LD_BUG|LD_GENERAL)) { + if (lf->fd == STDERR_FILENO) + found_real_stderr = 1; + /* Avoid duplicates */ + if (int_array_contains(sigsafe_log_fds, n_sigsafe_log_fds, lf->fd)) + continue; + sigsafe_log_fds[n_sigsafe_log_fds++] = lf->fd; + if (n_sigsafe_log_fds == MAX_SIGSAFE_FDS) + break; + } + } + + if (!found_real_stderr && + int_array_contains(sigsafe_log_fds, n_sigsafe_log_fds, STDOUT_FILENO)) { + /* Don't use a virtual stderr when we're also logging to stdout. */ + assert(n_sigsafe_log_fds >= 2); /* Don't use assert inside log functions*/ + sigsafe_log_fds[0] = sigsafe_log_fds[--n_sigsafe_log_fds]; + } + + UNLOCK_LOGS(); +} + /** Output a message to the log, prefixed with a function name <b>fn</b>. */ #ifdef __GNUC__ /** GCC-based implementation of the log_fn backend, used when we have @@ -1142,22 +1262,6 @@ get_min_log_level(void) return min; } -/** Return the fd of a file log that is receiving ERR messages, or -1 if - * no such log exists. */ -int -get_err_logging_fd(void) -{ - const logfile_t *lf; - for (lf = logfiles; lf; lf = lf->next) { - if (lf->is_temporary || lf->is_syslog || !lf->filename || - lf->callback || lf->seems_dead || lf->fd < 0) - continue; - if (lf->severities->masks[LOG_ERR] & LD_GENERAL) - return lf->fd; - } - return -1; -} - /** Switch all logs to output at most verbose level. */ void switch_logs_debug(void) diff --git a/src/common/sandbox.c b/src/common/sandbox.c index aaba9c776..7ef577dbe 100644 --- a/src/common/sandbox.c +++ b/src/common/sandbox.c @@ -1290,16 +1290,6 @@ install_syscall_filter(sandbox_cfg_t* cfg) return (rc < 0 ? -rc : rc); } -/** Additional file descriptor to use when logging seccomp2 failures */ -static int sigsys_debugging_fd = -1; - -/** Use the file descriptor <b>fd</b> to log seccomp2 failures. */ -static void -sigsys_set_debugging_fd(int fd) -{ - sigsys_debugging_fd = fd; -} - /** * Function called when a SIGSYS is caught by the application. It notifies the * user that an error has occurred and either terminates or allows the @@ -1309,8 +1299,8 @@ static void sigsys_debugging(int nr, siginfo_t *info, void *void_context) { ucontext_t *ctx = (ucontext_t *) (void_context); - char message[256]; - int rv = 0, syscall, length, err; + char number[32]; + int syscall; (void) nr; if (info->si_code != SYS_SECCOMP) @@ -1321,24 +1311,11 @@ sigsys_debugging(int nr, siginfo_t *info, void *void_context) syscall = ctx->uc_mcontext.gregs[REG_SYSCALL]; - strlcpy(message, "\n\n(Sandbox) Caught a bad syscall attempt (syscall 0x", - sizeof(message)); - (void) format_hex_number_sigsafe(syscall, message+strlen(message), - sizeof(message)-strlen(message)); - strlcat(message, ")\n", sizeof(message)); - length = strlen(message); - - err = 0; - if (sigsys_debugging_fd >= 0) { - rv = write(sigsys_debugging_fd, message, length); - err += rv != length; - } - - rv = write(STDOUT_FILENO, message, length); - err += rv != length; - - if (err) - _exit(2); + format_dec_number_sigsafe(syscall, number, sizeof(number)); + tor_log_err_sigsafe("(Sandbox) Caught a bad syscall attempt (syscall ", + number, + ")\n", + NULL); #if defined(DEBUGGING_CLOSE) _exit(1); @@ -1453,16 +1430,6 @@ sandbox_init(sandbox_cfg_t *cfg) #endif } -void -sandbox_set_debugging_fd(int fd) -{ -#ifdef USE_LIBSECCOMP - sigsys_set_debugging_fd(fd); -#else - (void)fd; -#endif -} - #ifndef USE_LIBSECCOMP int sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file, diff --git a/src/common/sandbox.h b/src/common/sandbox.h index 1d39be409..d64d427d3 100644 --- a/src/common/sandbox.h +++ b/src/common/sandbox.h @@ -153,9 +153,6 @@ int sandbox_getaddrinfo(const char *name, const char *servname, ((void)(name)) #endif -/** Use <b>fd</b> to log non-survivable sandbox violations. */ -void sandbox_set_debugging_fd(int fd); - #ifdef USE_LIBSECCOMP /** Returns a registered protected string used with the sandbox, given that * it matches the parameter. diff --git a/src/common/torlog.h b/src/common/torlog.h index ecd7e121e..d210c8b24 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -102,6 +102,9 @@ /** This log message is not safe to send to a callback-based logger * immediately. Used as a flag, not a log domain. */ #define LD_NOCB (1u<<31) +/** This log message should not include a function name, even if it otherwise + * would. Used as a flag, not a log domain. */ +#define LD_NOFUNCNAME (1u<<30) /** Mask of zero or more log domains, OR'd together. */ typedef uint32_t log_domain_mask_t; @@ -136,7 +139,6 @@ int get_min_log_level(void); void switch_logs_debug(void); void logs_free_all(void); void add_temp_log(int min_severity); -int get_err_logging_fd(void); void close_temp_logs(void); void rollback_log_changes(void); void mark_logs_temp(void); @@ -149,6 +151,10 @@ void set_log_time_granularity(int granularity_msec); void tor_log(int severity, log_domain_mask_t domain, const char *format, ...) CHECK_PRINTF(3,4); +void tor_log_err_sigsafe(const char *m, ...); +int tor_log_get_sigsafe_err_fds(const int **out); +void tor_log_update_sigsafe_err_fds(void); + #if defined(__GNUC__) || defined(RUNNING_DOXYGEN) extern int log_global_min_severity_; diff --git a/src/common/util.c b/src/common/util.c index 0771c9424..17d6c22ab 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -24,7 +24,8 @@ #include "torint.h" #include "container.h" #include "address.h" -#include "../common/sandbox.h" +#include "sandbox.h" +#include "backtrace.h" #ifdef _WIN32 #include <io.h> @@ -95,6 +96,23 @@ #endif /* ===== + * Assertion helper. + * ===== */ +/** Helper for tor_assert: report the assertion failure. */ +void +tor_assertion_failed_(const char *fname, unsigned int line, + const char *func, const char *expr) +{ + char buf[256]; + log_err(LD_BUG, "%s:%u: %s: Assertion %s failed; aborting.", + fname, line, func, expr); + tor_snprintf(buf, sizeof(buf), + "Assertion %s failed in %s at %s:%u", + expr, func, fname, line); + log_backtrace(LOG_ERR, LD_BUG, buf); +} + +/* ===== * Memory management * ===== */ #ifdef USE_DMALLOC @@ -3400,6 +3418,51 @@ tor_join_win_cmdline(const char *argv[]) return joined_argv; } +/* 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. */ + 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 = x % radix; + tor_assert(cp > buf); + --cp; + *cp = "0123456789ABCDEF"[digit]; + x /= radix; + } while (x); + + /* NOT tor_assert; see above. */ + if (cp != buf) { + abort(); + } + + return len; +} + /** * Helper function to output hex numbers from within a signal handler. * @@ -3422,45 +3485,16 @@ tor_join_win_cmdline(const char *argv[]) * arbitrary C functions. */ int -format_hex_number_sigsafe(unsigned int x, char *buf, int buf_len) +format_hex_number_sigsafe(unsigned long x, char *buf, int buf_len) { - int len; - unsigned int tmp; - char *cur; - - /* Sanity check */ - if (!buf || buf_len <= 1) - return 0; - - /* How many chars do we need for x? */ - if (x > 0) { - len = 0; - tmp = x; - while (tmp > 0) { - tmp >>= 4; - ++len; - } - } else { - len = 1; - } - - /* Bail if we would go past the end of the buffer */ - if (len+1 > buf_len) - return 0; - - /* Point to last one */ - cur = buf + len - 1; - - /* Convert x to hex */ - do { - *cur-- = "0123456789ABCDEF"[x & 0xf]; - x >>= 4; - } while (x != 0 && cur >= buf); - - buf[len] = '\0'; + return format_number_sigsafe(x, buf, buf_len, 16); +} - /* Return len */ - return len; +/** 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); } #ifndef _WIN32 @@ -5047,4 +5081,3 @@ tor_weak_random_range(tor_weak_rng_t *rng, int32_t top) } while (result >= top); return result; } - diff --git a/src/common/util.h b/src/common/util.h index 3199ab112..18dc20639 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -48,13 +48,13 @@ /** Like assert(3), but send assertion failures to the log as well as to * stderr. */ #define tor_assert(expr) STMT_BEGIN \ - if (PREDICT_UNLIKELY(!(expr))) { \ - log_err(LD_BUG, "%s:%d: %s: Assertion %s failed; aborting.", \ - SHORT_FILE__, __LINE__, __func__, #expr); \ - fprintf(stderr,"%s:%d %s: Assertion %s failed; aborting.\n", \ - SHORT_FILE__, __LINE__, __func__, #expr); \ - abort(); \ - } STMT_END + if (PREDICT_UNLIKELY(!(expr))) { \ + tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, #expr); \ + abort(); \ + } STMT_END + +void tor_assertion_failed_(const char *fname, unsigned int line, + const char *func, const char *expr); /* If we're building with dmalloc, we want all of our memory allocation * functions to take an extra file/line pair of arguments. If not, not. @@ -524,7 +524,8 @@ int32_t tor_weak_random_range(tor_weak_rng_t *rng, int32_t top); * <b>n</b> */ #define tor_weak_random_one_in_n(rng, n) (0==tor_weak_random_range((rng),(n))) -int format_hex_number_sigsafe(unsigned int x, char *buf, int max_len); +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); #ifdef UTIL_PRIVATE /* Prototypes for private functions only used by util.c (and unit tests) */ |