static void _fr_talloc_fault_simple(char const *reason) { FR_FAULT_LOG("talloc abort: %s\n", reason); #if defined(HAVE_EXECINFO) && (!defined(NDEBUG) || !defined(__GNUC__)) if (fr_fault_log_fd >= 0) { size_t frame_count; void *stack[MAX_BT_FRAMES]; frame_count = backtrace(stack, MAX_BT_FRAMES); FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count); backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd); } #endif abort(); }
static void _fr_talloc_fault(char const *reason) { FR_FAULT_LOG("talloc abort: %s", reason); #ifdef SIGABRT fr_fault(SIGABRT); #endif fr_exit_now(1); }
/** Registers signal handlers to execute panic_action on fatal signal * * May be called multiple time to change the panic_action/program. * * @param cmd to execute on fault. If present %p will be substituted * for the parent PID before the command is executed, and %e * will be substituted for the currently running program. * @param program Name of program currently executing (argv[0]). * @return 0 on success -1 on failure. */ int fr_fault_setup(char const *cmd, char const *program) { static bool setup = false; char *out = panic_action; size_t left = sizeof(panic_action); char const *p = cmd; char const *q; if (cmd) { size_t ret; /* Substitute %e for the current program */ while ((q = strstr(p, "%e"))) { out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : ""); if (left <= ret) { oob: fr_strerror_printf("Panic action too long"); return -1; } left -= ret; p = q + 2; } if (strlen(p) >= left) goto oob; strlcpy(out, p, left); } else { *panic_action = '\0'; } /* * Check for administrator sanity. */ if (fr_fault_check_permissions() < 0) return -1; /* Unsure what the side effects of changing the signal handler mid execution might be */ if (!setup) { char *env; fr_debug_state_t debug_state; /* * Installing signal handlers interferes with some debugging * operations. Give the developer control over whether the * signal handlers are installed or not. */ env = getenv("DEBUG"); if (!env || (strcmp(env, "no") == 0)) { debug_state = DEBUG_STATE_NOT_ATTACHED; } else if (!strcmp(env, "auto") || !strcmp(env, "yes")) { /* * Figure out if we were started under a debugger */ if (fr_debug_state < 0) fr_debug_state = fr_get_debug_state(); debug_state = fr_debug_state; } else { debug_state = DEBUG_STATE_ATTACHED; } talloc_set_log_fn(_fr_talloc_log); /* * These signals can't be properly dealt with in the debugger * if we set our own signal handlers. */ switch (debug_state) { default: #ifndef NDEBUG FR_FAULT_LOG("Debugger check failed: %s", fr_strerror()); FR_FAULT_LOG("Signal processing in debuggers may not work as expected"); #endif /* FALL-THROUGH */ case DEBUG_STATE_NOT_ATTACHED: #ifdef SIGABRT if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1; /* * Use this instead of abort so we get a * full backtrace with broken versions of LLDB */ talloc_set_abort_fn(_fr_talloc_fault); #endif #ifdef SIGILL if (fr_set_signal(SIGILL, fr_fault) < 0) return -1; #endif #ifdef SIGFPE if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1; #endif #ifdef SIGSEGV if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1; #endif break; case DEBUG_STATE_ATTACHED: break; } /* * Needed for memory reports */ { TALLOC_CTX *tmp; bool *marker; tmp = talloc(NULL, bool); talloc_null_ctx = talloc_parent(tmp); talloc_free(tmp); /* * Disable null tracking on exit, else valgrind complains */ talloc_autofree_ctx = talloc_autofree_context(); marker = talloc(talloc_autofree_ctx, bool); talloc_set_destructor(marker, _fr_disable_null_tracking); } #if defined(HAVE_MALLOPT) && !defined(NDEBUG) /* * If were using glibc malloc > 2.4 this scribbles over * uninitialised and freed memory, to make memory issues easier * to track down. */ if (!getenv("TALLOC_FREE_FILL")) mallopt(M_PERTURB, 0x42); mallopt(M_CHECK_ACTION, 3); #endif #if defined(HAVE_EXECINFO) && defined(__GNUC__) && !defined(NDEBUG) /* * We need to pre-load lgcc_s, else we can get into a deadlock * in fr_fault, as backtrace() attempts to dlopen it. * * Apparently there's a performance impact of loading lgcc_s, * so only do it if this is a debug build. * * See: https://sourceware.org/bugzilla/show_bug.cgi?id=16159 */ { void *stack[10]; backtrace(stack, 10); } #endif }
/** Prints a simple backtrace (if execinfo is available) and calls panic_action if set. * * @param sig caught */ NEVER_RETURNS void fr_fault(int sig) { char cmd[sizeof(panic_action) + 20]; char *out = cmd; size_t left = sizeof(cmd), ret; char const *p = panic_action; char const *q; int code; /* * If a debugger is attached, we don't want to run the panic action, * as it may interfere with the operation of the debugger. * If something calls us directly we just raise the signal and let * the debugger handle it how it wants. */ if (fr_debug_state == DEBUG_STATE_ATTACHED) { FR_FAULT_LOG("RAISING SIGNAL: %s", strsignal(sig)); raise(sig); goto finish; } /* * Makes the backtraces slightly cleaner */ memset(cmd, 0, sizeof(cmd)); FR_FAULT_LOG("CAUGHT SIGNAL: %s", strsignal(sig)); /* * Check for administrator sanity. */ if (fr_fault_check_permissions() < 0) { FR_FAULT_LOG("Refusing to execute panic action: %s", fr_strerror()); goto finish; } /* * Run the callback if one was registered */ if (panic_cb && (panic_cb(sig) < 0)) goto finish; /* * Produce a simple backtrace - They're very basic but at least give us an * idea of the area of the code we hit the issue in. * * See below in fr_fault_setup() and * https://sourceware.org/bugzilla/show_bug.cgi?id=16159 * for why we only print backtraces in debug builds if we're using GLIBC. */ #if defined(HAVE_EXECINFO) && (!defined(NDEBUG) || !defined(__GNUC__)) if (fr_fault_log_fd >= 0) { size_t frame_count; void *stack[MAX_BT_FRAMES]; frame_count = backtrace(stack, MAX_BT_FRAMES); FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count); backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd); } #endif /* No panic action set... */ if (panic_action[0] == '\0') { FR_FAULT_LOG("No panic action set"); goto finish; } /* Substitute %p for the current PID (useful for attaching a debugger) */ while ((q = strstr(p, "%p"))) { out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid()); if (left <= ret) { oob: FR_FAULT_LOG("Panic action too long"); fr_exit_now(1); } left -= ret; p = q + 2; } if (strlen(p) >= left) goto oob; strlcpy(out, p, left); { bool disable = false; FR_FAULT_LOG("Calling: %s", cmd); /* * Here we temporarily enable the dumpable flag so if GBD or LLDB * is called in the panic_action, they can pattach to the running * process. */ if (fr_get_dumpable_flag() == 0) { if ((fr_set_dumpable_flag(true) < 0) || !fr_get_dumpable_flag()) { FR_FAULT_LOG("Failed setting dumpable flag, pattach may not work: %s", fr_strerror()); } else { disable = true; } FR_FAULT_LOG("Temporarily setting PR_DUMPABLE to 1"); } code = system(cmd); /* * We only want to error out here, if dumpable was originally disabled * and we managed to change the value to enabled, but failed * setting it back to disabled. */ if (disable) { FR_FAULT_LOG("Resetting PR_DUMPABLE to 0"); if (fr_set_dumpable_flag(false) < 0) { FR_FAULT_LOG("Failed resetting dumpable flag to off: %s", fr_strerror()); FR_FAULT_LOG("Exiting due to insecure process state"); fr_exit_now(1); } } FR_FAULT_LOG("Panic action exited with %i", code); fr_exit_now(code); } finish: /* * (Re-)Raise the signal, so that if we're running under * a debugger, the debugger can break when it receives * the signal. */ fr_unset_signal(sig); /* Make sure we don't get into a loop */ raise(sig); fr_exit_now(1); /* Function marked as noreturn */ }
/** Signal handler to print out a talloc memory report * * @param sig caught */ static void _fr_fault_mem_report(int sig) { FR_FAULT_LOG("CAUGHT SIGNAL: %s", strsignal(sig)); if (fr_log_talloc_report(NULL) < 0) fr_perror("memreport"); }