2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Various functions to aid in debugging
21 * @copyright 2013 The FreeRADIUS server project
22 * @copyright 2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
25 #include <freeradius-devel/libradius.h>
29 * runtime backtrace functions are not POSIX but are included in
30 * glibc, OSX >= 10.5 and various BSDs
33 # include <execinfo.h>
36 #ifdef HAVE_SYS_PRCTL_H
37 # include <sys/prctl.h>
40 #ifdef HAVE_SYS_RESOURCE_H
41 # include <sys/resource.h>
45 # define PTHREAD_MUTEX_LOCK pthread_mutex_lock
46 # define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
48 # define PTHREAD_MUTEX_LOCK(_x)
49 # define PTHREAD_MUTEX_UNLOCK(_x)
53 # define MAX_BT_FRAMES 128
54 # define MAX_BT_CBUFF 65536 //!< Should be a power of 2
56 # ifdef HAVE_PTHREAD_H
57 static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER;
60 typedef struct fr_bt_info {
61 void *obj; //!< Memory address of the block of allocated memory.
62 void *frames[MAX_BT_FRAMES]; //!< Backtrace frame data
63 int count; //!< Number of frames stored
67 void *obj; //!< Pointer to the parent object, this is our needle
68 //!< when we iterate over the contents of the circular buffer.
69 fr_cbuff_t *cbuff; //!< Where we temporarily store the backtraces
73 static char panic_action[512]; //!< The command to execute when panicking.
74 static fr_fault_cb_t panic_cb = NULL; //!< Callback to execute whilst panicking, before the
76 static fr_fault_log_t fr_fault_log = NULL; //!< Function to use to process logging output.
77 static int fr_fault_log_fd = STDERR_FILENO; //!< Where to write debug output.
79 static int fr_debugger_present = -1; //!< Whether were attached to by a debugger.
81 #ifdef HAVE_SYS_RESOURCE_H
82 static struct rlimit core_limits;
85 /** Stub callback to see if the SIGTRAP handler is overriden
87 * @param signum signal raised.
89 static void _sigtrap_handler(UNUSED int signum)
91 fr_debugger_present = 0;
92 signal(SIGTRAP, SIG_DFL);
95 /** Break in debugger (if were running under a debugger)
97 * If the server is running under a debugger this will raise a
98 * SIGTRAP which will pause the running process.
100 * If the server is not running under debugger then this will do nothing.
102 void fr_debug_break(void)
104 if (fr_debugger_present == -1) {
105 fr_debugger_present = 0;
106 signal(SIGTRAP, _sigtrap_handler);
108 } else if (fr_debugger_present == 1) {
114 /** Generate a backtrace for an object during destruction
116 * If this is the first entry being inserted
118 static int _fr_do_bt(fr_bt_marker_t *marker)
122 if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) {
126 bt = talloc_zero(marker->cbuff, fr_bt_info_t);
130 bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
131 fr_cbuff_rp_insert(marker->cbuff, bt);
136 /** Print backtrace entry for a given object
138 * @param cbuff to search in.
139 * @param obj pointer to original object
141 void backtrace_print(fr_cbuff_t *cbuff, void *obj)
148 while ((p = fr_cbuff_rp_next(cbuff, NULL))) {
149 if ((p == obj) || !obj) {
151 frames = backtrace_symbols(p->frames, p->count);
153 fprintf(stderr, "Stacktrace for: %p\n", p);
154 for (i = 0; i < p->count; i++) {
155 fprintf(stderr, "%s\n", frames[i]);
158 /* We were only asked to look for one */
166 fprintf(stderr, "No backtrace available for %p", obj);
170 /** Inserts a backtrace marker into the provided context
172 * Allows for maximum laziness and will initialise a circular buffer if one has not already been created.
174 * Code augmentation should look something like:
176 // Create a static cbuffer pointer, the first call to backtrace_attach will initialise it
177 static fr_cbuff *my_obj_bt;
179 my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) {
182 this = talloc(ctx, my_obj_t);
184 // Attach backtrace marker to object
185 backtrace_attach(&my_obj_bt, this);
191 * Then, later when a double free occurs:
193 (gdb) call backtrace_print(&my_obj_bt, <pointer to double freed memory>)
196 * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument
197 * values, but should at least show the code path taken.
199 * @param cbuff this should be a pointer to a static *fr_cbuff.
200 * @param obj we want to generate a backtrace for.
202 fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj)
204 fr_bt_marker_t *marker;
206 if (*cbuff == NULL) {
207 PTHREAD_MUTEX_LOCK(&fr_debug_init);
208 /* Check again now we hold the mutex - eww*/
209 if (*cbuff == NULL) {
212 ctx = fr_autofree_ctx();
213 *cbuff = fr_cbuff_alloc(ctx, MAX_BT_CBUFF, true);
215 PTHREAD_MUTEX_UNLOCK(&fr_debug_init);
218 marker = talloc(obj, fr_bt_marker_t);
223 marker->obj = (void *) obj;
224 marker->cbuff = *cbuff;
226 talloc_set_destructor(marker, _fr_do_bt);
231 void backtrace_print(UNUSED fr_cbuff_t *cbuff, UNUSED void *obj)
233 fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
235 fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_cbuff_t **cbuff, UNUSED TALLOC_CTX *obj)
237 fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
240 #endif /* ifdef HAVE_EXECINFO */
242 /** Set the dumpable flag, also controls whether processes can PATTACH
244 * @param dumpable whether we should allow core dumping
246 #if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE)
247 static int fr_set_dumpable_flag(bool dumpable)
249 if (prctl(PR_SET_DUMPABLE, dumpable ? 1 : 0) < 0) {
250 fr_strerror_printf("Cannot re-enable core dumps: prctl(PR_SET_DUMPABLE) failed: %s",
258 static int fr_set_dumpable_flag(UNUSED bool dumpable)
260 fr_strerror_printf("Changing value of PR_DUMPABLE not supported on this system");
265 /** Get the processes dumpable flag
267 * @param dumpable whether we should allow core dumping
269 #if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_DUMPABLE)
270 static int fr_get_dumpable_flag(void)
274 ret = prctl(PR_GET_DUMPABLE);
276 fr_strerror_printf("Cannot get dumpable flag: %s", fr_syserror(errno));
283 static int fr_get_dumpable_flag(void)
285 fr_strerror_printf("Getting value of PR_DUMPABLE not supported on this system");
291 /** Get the current maximum for core files
293 * Do this before anything else so as to ensure it's properly initialized.
295 int fr_set_dumpable_init(void)
297 #ifdef HAVE_SYS_RESOURCE_H
298 if (getrlimit(RLIMIT_CORE, &core_limits) < 0) {
299 fr_strerror_printf("Failed to get current core limit: %s", fr_syserror(errno));
306 /** Enable or disable core dumps
308 * @param allow_core_dumps whether to enable or disable core dumps.
310 int fr_set_dumpable(bool allow_core_dumps)
313 * If configured, turn core dumps off.
315 if (!allow_core_dumps) {
316 #ifdef HAVE_SYS_RESOURCE_H
317 struct rlimit no_core;
319 no_core.rlim_cur = 0;
320 no_core.rlim_max = 0;
322 if (setrlimit(RLIMIT_CORE, &no_core) < 0) {
323 fr_strerror_printf("Failed disabling core dumps: %s", fr_syserror(errno));
331 if (fr_set_dumpable_flag(true) < 0) return -1;
334 * Reset the core dump limits to their original value.
336 #ifdef HAVE_SYS_RESOURCE_H
337 if (setrlimit(RLIMIT_CORE, &core_limits) < 0) {
338 fr_strerror_printf("Cannot update core dump limit: %s", fr_syserror(errno));
346 /** Check to see if panic_action file is world writeable
348 * @return 0 if file is OK, else -1.
350 static int fr_fault_check_permissions(void)
353 char *filename = NULL;
357 * Try and guess which part of the command is the binary, and check to see if
358 * it's world writeable, to try and save the admin from their own stupidity.
360 * @fixme we should do this properly and take into account single and double
363 if ((q = strchr(panic_action, ' '))) {
364 if (asprintf(&filename, "%.*s", (int)(q - panic_action), panic_action) < 0) {
365 fr_strerror_printf("Failed writing panic_action to temporary buffer");
373 if (stat(p, &statbuf) == 0) {
375 if ((statbuf.st_mode & S_IWOTH) != 0) {
376 fr_strerror_printf("panic_action file \"%s\" is globally writable", p);
387 /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set.
391 void fr_fault(int sig)
393 char cmd[sizeof(panic_action) + 20];
395 size_t left = sizeof(cmd), ret;
397 char const *p = panic_action;
402 fr_fault_log("CAUGHT SIGNAL: %s\n", strsignal(sig));
405 * Check for administrator sanity.
407 if (fr_fault_check_permissions() < 0) {
408 fr_fault_log("Refusing to execute panic action: %s\n", fr_strerror());
413 * Run the callback if one was registered
415 if (panic_cb && (panic_cb(sig) < 0)) goto finish;
418 * Produce a simple backtrace - They've very basic but at least give us an
419 * idea of the area of the code we hit the issue in.
423 size_t frame_count, i;
424 void *stack[MAX_BT_FRAMES];
427 frame_count = backtrace(stack, MAX_BT_FRAMES);
429 fr_fault_log("Backtrace of last %zu frames:\n", frame_count);
430 strings = backtrace_symbols(stack, frame_count);
431 for (i = 0; i < frame_count; i++) {
432 fr_fault_log("%s\n", strings[i]);
438 /* No panic action set... */
439 if (panic_action[0] == '\0') {
440 fr_fault_log("No panic action set\n");
444 /* Substitute %p for the current PID (useful for attaching a debugger) */
445 while ((q = strstr(p, "%p"))) {
446 out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid());
449 fr_fault_log("Panic action too long");
455 if (strlen(p) >= left) goto oob;
456 strlcpy(out, p, left);
458 fr_fault_log("Calling: %s\n", cmd);
462 bool disable = false;
465 * Here we temporarily enable the dumpable flag so if GBD or LLDB
466 * is called in the panic_action, they can pattach tot he running
469 disabled = (fr_get_dumpable_flag() == 0);
471 if (fr_set_dumpable_flag(true) < 0) {
472 fr_fault_log("Failed setting dumpable flag, pattach may not work: %s", fr_strerror());
481 * We only want to error out here, if dumable was originally disabled
482 * and we managed to change the value to enabled, but failed
483 * setting it back to disabled.
485 if (disable && (fr_set_dumpable_flag(false) < 0)) {
486 fr_fault_log("Failed reseting dumpable flag to off: %s", fr_strerror());
487 fr_fault_log("Exiting due to insecure process state");
492 fr_fault_log("Panic action exited with %i", code);
496 if (sig == SIGUSR1) {
504 /** Work around debuggers which can't backtrace past the signal handler
506 * At least this provides us some information when we get talloc errors.
508 static void _fr_talloc_fault(char const *reason)
510 fr_fault_log("talloc abort: %s\n", reason);
515 /** Wrapper to pass talloc log output to our fr_fault_log function
518 static void _fr_talloc_log(char const *msg)
520 fr_fault_log("%s\n", msg);
523 /** Generate a talloc memory report for a context and print to stderr/stdout
525 * @param ctx to generate a report for, may be NULL in which case the root context is used.
527 int fr_log_talloc_report(TALLOC_CTX *ctx)
530 char const *null_ctx = NULL;
534 fd = dup(fr_fault_log_fd);
536 fr_strerror_printf("Couldn't write memory report, failed to dup log fd: %s", fr_syserror(errno));
539 log = fdopen(fd, "w");
541 fr_strerror_printf("Couldn't write memory report, fdopen failed: %s", fr_syserror(errno));
545 fprintf(log, "Current state of talloced memory:\n");
547 null_ctx = talloc_get_name(NULL);
551 talloc_report_full(NULL, log);
553 fprintf(log, "Context level %i", i++);
555 talloc_report_full(ctx, log);
556 } while ((ctx = talloc_parent(ctx)) && (talloc_get_name(ctx) != null_ctx)); /* Stop before we hit NULL ctx */
563 /** Signal handler to print out a talloc memory report
567 static void _fr_fault_mem_report(int sig)
569 fr_fault_log("CAUGHT SIGNAL: %s\n", strsignal(sig));
571 if (fr_log_talloc_report(NULL) < 0) fr_perror("memreport");
574 /** Registers signal handlers to execute panic_action on fatal signal
576 * May be called multiple time to change the panic_action/program.
578 * @param cmd to execute on fault. If present %p will be substituted
579 * for the parent PID before the command is executed, and %e
580 * will be substituted for the currently running program.
581 * @param program Name of program currently executing (argv[0]).
582 * @return 0 on success -1 on failure.
584 int fr_fault_setup(char const *cmd, char const *program)
586 static bool setup = false;
588 char *out = panic_action;
589 size_t left = sizeof(panic_action), ret;
595 /* Substitute %e for the current program */
596 while ((q = strstr(p, "%e"))) {
597 out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : "");
600 fr_strerror_printf("Panic action too long");
606 if (strlen(p) >= left) goto oob;
607 strlcpy(out, p, left);
609 *panic_action = '\0';
613 * Check for administrator sanity.
615 if (fr_fault_check_permissions() < 0) return -1;
617 /* Unsure what the side effects of changing the signal handler mid execution might be */
620 if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1;
623 if (fr_set_signal(SIGBUS, fr_fault) < 0) return -1;
626 if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1;
628 * Use this instead of abort so we get a
629 * full backtrace with broken versions of LLDB
631 talloc_set_abort_fn(_fr_talloc_fault);
634 if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1;
638 if (fr_set_signal(SIGUSR1, fr_fault) < 0) return -1;
642 if (fr_set_signal(SIGUSR2, _fr_fault_mem_report) < 0) return -1;
646 * Setup the default logger
648 if (!fr_fault_log) fr_fault_set_log_fn(NULL);
649 talloc_set_log_fn(_fr_talloc_log);
652 * Needed for memory reports
654 talloc_enable_null_tracking();
661 /** Set a callback to be called before fr_fault()
663 * @param func to execute. If callback returns < 0
664 * fr_fault will exit before running panic_action code.
666 void fr_fault_set_cb(fr_fault_cb_t func)
671 /** Default logger, logs output to stderr
674 static void CC_HINT(format (printf, 1, 2)) _fr_fault_log(char const *msg, ...)
679 vfprintf(stderr, msg, ap);
684 /** Set a file descriptor to log panic_action output to.
686 * @param func to call to output log messages.
688 void fr_fault_set_log_fn(fr_fault_log_t func)
690 fr_fault_log = func ? func : _fr_fault_log;
693 /** Set a file descriptor to log memory reports to.
695 * @param fd to write output to.
697 void fr_fault_set_log_fd(int fd)
699 fr_fault_log_fd = fd;
703 #ifdef WITH_VERIFY_PTR
706 * Verify a VALUE_PAIR
708 inline void fr_verify_vp(VALUE_PAIR const *vp)
710 (void) talloc_get_type_abort(vp, VALUE_PAIR);
712 if (vp->data.ptr) switch (vp->da->type) {
718 if (!talloc_get_type(vp->data.ptr, uint8_t)) {
719 fr_perror("Type check failed for attribute \"%s\"", vp->da->name);
720 (void) talloc_get_type_abort(vp->data.ptr, uint8_t);
723 len = talloc_array_length(vp->vp_octets);
724 if (vp->length > len) {
725 fr_perror("VALUE_PAIR length %zu does not equal uint8_t buffer length %zu", vp->length, len);
736 if (!talloc_get_type(vp->data.ptr, char)) {
737 fr_perror("Type check failed for attribute \"%s\"", vp->da->name);
738 (void) talloc_get_type_abort(vp->data.ptr, char);
741 len = (talloc_array_length(vp->vp_strvalue) - 1);
742 if (vp->length > len) {
743 fr_perror("VALUE_PAIR %s length %zu is too small for char buffer length %zu",
744 vp->da->name, vp->length, len);
748 if (vp->vp_strvalue[vp->length] != '\0') {
749 fr_perror("VALUE_PAIR %s buffer not \\0 terminated", vp->da->name);
764 void fr_verify_list(TALLOC_CTX *expected, VALUE_PAIR *vps)
770 for (vp = fr_cursor_init(&cursor, &vps);
772 vp = fr_cursor_next(&cursor)) {
775 parent = talloc_parent(vp);
776 if (expected && (parent != expected)) {
777 fr_perror("Expected VALUE_PAIR (%s) to be parented by %p (%s), "
778 "but parented by %p (%s)",
780 expected, talloc_get_name(expected),
781 parent, parent ? talloc_get_name(parent) : "NULL");
783 fr_log_talloc_report(expected);
784 if (parent) fr_log_talloc_report(parent);