Turn on M_CHECK_ACTION too
[freeradius.git] / src / lib / debug.c
1 /*
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.
6  *
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.
11  *
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
15  */
16
17 /**
18  * @file debug.c
19  * @brief Various functions to aid in debugging
20  *
21  * @copyright 2013  The FreeRADIUS server project
22  * @copyright 2013  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  */
24 #include <assert.h>
25 #include <freeradius-devel/libradius.h>
26 #include <sys/stat.h>
27
28 #if defined(HAVE_MALLOPT) && defined(HAVE_MALLOC_H)
29 #  include <malloc.h>
30 #endif
31
32 /*
33  *      runtime backtrace functions are not POSIX but are included in
34  *      glibc, OSX >= 10.5 and various BSDs
35  */
36 #ifdef HAVE_EXECINFO
37 #  include <execinfo.h>
38 #endif
39
40 #ifdef HAVE_SYS_PRCTL_H
41 #  include <sys/prctl.h>
42 #endif
43
44 #ifdef HAVE_SYS_RESOURCE_H
45 #  include <sys/resource.h>
46 #endif
47
48 #ifdef HAVE_PTHREAD_H
49 #  define PTHREAD_MUTEX_LOCK pthread_mutex_lock
50 #  define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
51 #else
52 #  define PTHREAD_MUTEX_LOCK(_x)
53 #  define PTHREAD_MUTEX_UNLOCK(_x)
54 #endif
55
56 #ifdef HAVE_EXECINFO
57 #  define MAX_BT_FRAMES 128
58 #  define MAX_BT_CBUFF  65536                           //!< Should be a power of 2
59
60 #  ifdef HAVE_PTHREAD_H
61 static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER;
62 #  endif
63
64 typedef struct fr_bt_info {
65         void            *obj;                           //!< Memory address of the block of allocated memory.
66         void            *frames[MAX_BT_FRAMES];         //!< Backtrace frame data
67         int             count;                          //!< Number of frames stored
68 } fr_bt_info_t;
69
70 struct fr_bt_marker {
71         void            *obj;                           //!< Pointer to the parent object, this is our needle
72                                                         //!< when we iterate over the contents of the circular buffer.
73         fr_cbuff_t      *cbuff;                         //!< Where we temporarily store the backtraces
74 };
75 #endif
76
77 static char panic_action[512];                          //!< The command to execute when panicking.
78 static fr_fault_cb_t panic_cb = NULL;                   //!< Callback to execute whilst panicking, before the
79                                                         //!< panic_action.
80 static fr_fault_log_t fr_fault_log = NULL;              //!< Function to use to process logging output.
81 static int fr_fault_log_fd = STDERR_FILENO;             //!< Where to write debug output.
82
83 static int fr_debugger_present = -1;                    //!< Whether were attached to by a debugger.
84
85 #ifdef HAVE_SYS_RESOURCE_H
86 static struct rlimit core_limits;
87 #endif
88
89 /** Stub callback to see if the SIGTRAP handler is overriden
90  *
91  * @param signum signal raised.
92  */
93 static void _sigtrap_handler(UNUSED int signum)
94 {
95         fr_debugger_present = 0;
96         signal(SIGTRAP, SIG_DFL);
97 }
98
99 /** Break in debugger (if were running under a debugger)
100  *
101  * If the server is running under a debugger this will raise a
102  * SIGTRAP which will pause the running process.
103  *
104  * If the server is not running under debugger then this will do nothing.
105  */
106 void fr_debug_break(void)
107 {
108         if (fr_debugger_present == -1) {
109                 fr_debugger_present = 0;
110                 signal(SIGTRAP, _sigtrap_handler);
111                 raise(SIGTRAP);
112         } else if (fr_debugger_present == 1) {
113                 raise(SIGTRAP);
114         }
115 }
116
117 #ifdef HAVE_EXECINFO
118 /** Generate a backtrace for an object during destruction
119  *
120  * If this is the first entry being inserted
121  */
122 static int _fr_do_bt(fr_bt_marker_t *marker)
123 {
124         fr_bt_info_t *bt;
125
126         if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) {
127                 return -1;
128         }
129
130         bt = talloc_zero(marker->cbuff, fr_bt_info_t);
131         if (!bt) {
132                 return -1;
133         }
134         bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
135         fr_cbuff_rp_insert(marker->cbuff, bt);
136
137         return 0;
138 }
139
140 /** Print backtrace entry for a given object
141  *
142  * @param cbuff to search in.
143  * @param obj pointer to original object
144  */
145 void backtrace_print(fr_cbuff_t *cbuff, void *obj)
146 {
147         fr_bt_info_t *p;
148         bool found = false;
149         int i = 0;
150         char **frames;
151
152         while ((p = fr_cbuff_rp_next(cbuff, NULL))) {
153                 if ((p == obj) || !obj) {
154                         found = true;
155                         frames = backtrace_symbols(p->frames, p->count);
156
157                         fprintf(stderr, "Stacktrace for: %p\n", p);
158                         for (i = 0; i < p->count; i++) {
159                                 fprintf(stderr, "%s\n", frames[i]);
160                         }
161
162                         /* We were only asked to look for one */
163                         if (obj) {
164                                 return;
165                         }
166                 }
167         };
168
169         if (!found) {
170                 fprintf(stderr, "No backtrace available for %p", obj);
171         }
172 }
173
174 /** Inserts a backtrace marker into the provided context
175  *
176  * Allows for maximum laziness and will initialise a circular buffer if one has not already been created.
177  *
178  * Code augmentation should look something like:
179 @verbatim
180         // Create a static cbuffer pointer, the first call to backtrace_attach will initialise it
181         static fr_cbuff *my_obj_bt;
182
183         my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) {
184                 my_obj_t *this;
185
186                 this = talloc(ctx, my_obj_t);
187
188                 // Attach backtrace marker to object
189                 backtrace_attach(&my_obj_bt, this);
190
191                 return this;
192         }
193 @endverbatim
194  *
195  * Then, later when a double free occurs:
196 @verbatim
197         (gdb) call backtrace_print(&my_obj_bt, <pointer to double freed memory>)
198 @endverbatim
199  *
200  * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument
201  * values, but should at least show the code path taken.
202  *
203  * @param cbuff this should be a pointer to a static *fr_cbuff.
204  * @param obj we want to generate a backtrace for.
205  */
206 fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj)
207 {
208         fr_bt_marker_t *marker;
209
210         if (*cbuff == NULL) {
211                 PTHREAD_MUTEX_LOCK(&fr_debug_init);
212                 /* Check again now we hold the mutex - eww*/
213                 if (*cbuff == NULL) {
214                         TALLOC_CTX *ctx;
215
216                         ctx = fr_autofree_ctx();
217                         *cbuff = fr_cbuff_alloc(ctx, MAX_BT_CBUFF, true);
218                 }
219                 PTHREAD_MUTEX_UNLOCK(&fr_debug_init);
220         }
221
222         marker = talloc(obj, fr_bt_marker_t);
223         if (!marker) {
224                 return NULL;
225         }
226
227         marker->obj = (void *) obj;
228         marker->cbuff = *cbuff;
229
230         talloc_set_destructor(marker, _fr_do_bt);
231
232         return marker;
233 }
234 #else
235 void backtrace_print(UNUSED fr_cbuff_t *cbuff, UNUSED void *obj)
236 {
237         fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
238 }
239 fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_cbuff_t **cbuff, UNUSED TALLOC_CTX *obj)
240 {
241         fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
242         abort();
243 }
244 #endif /* ifdef HAVE_EXECINFO */
245
246 static int _panic_on_free(UNUSED char *foo)
247 {
248         fr_fault(SIGUSR1);
249         return -1;      /* this should make the free fail */
250 }
251
252 /** Insert memory into the context of another talloc memory chunk which
253  * causes a panic when freed.
254  *
255  * @param ctx TALLOC_CTX to monitor for frees.
256  */
257 void fr_panic_on_free(TALLOC_CTX *ctx)
258 {
259         char *ptr;
260
261         ptr = talloc(ctx, char);
262         talloc_set_destructor(ptr, _panic_on_free);
263 }
264
265 /** Set the dumpable flag, also controls whether processes can PATTACH
266  *
267  * @param dumpable whether we should allow core dumping
268  */
269 #if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE)
270 static int fr_set_dumpable_flag(bool dumpable)
271 {
272         if (prctl(PR_SET_DUMPABLE, dumpable ? 1 : 0) < 0) {
273                 fr_strerror_printf("Cannot re-enable core dumps: prctl(PR_SET_DUMPABLE) failed: %s",
274                                    fr_syserror(errno));
275                 return -1;
276         }
277
278         return 0;
279 }
280 #else
281 static int fr_set_dumpable_flag(UNUSED bool dumpable)
282 {
283         fr_strerror_printf("Changing value of PR_DUMPABLE not supported on this system");
284         return -2;
285 }
286 #endif
287
288 /** Get the processes dumpable flag
289  *
290  */
291 #if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_DUMPABLE)
292 static int fr_get_dumpable_flag(void)
293 {
294         int ret;
295
296         ret = prctl(PR_GET_DUMPABLE);
297         if (ret < 0) {
298                 fr_strerror_printf("Cannot get dumpable flag: %s", fr_syserror(errno));
299                 return -1;
300         }
301
302         /*
303          *  Linux is crazy and prctl sometimes returns 2 for disabled
304          */
305         if (ret != 1) return 0;
306         return 1;
307 }
308 #else
309 static int fr_get_dumpable_flag(void)
310 {
311         fr_strerror_printf("Getting value of PR_DUMPABLE not supported on this system");
312         return -2;
313 }
314 #endif
315
316
317 /** Get the current maximum for core files
318  *
319  * Do this before anything else so as to ensure it's properly initialized.
320  */
321 int fr_set_dumpable_init(void)
322 {
323 #ifdef HAVE_SYS_RESOURCE_H
324         if (getrlimit(RLIMIT_CORE, &core_limits) < 0) {
325                 fr_strerror_printf("Failed to get current core limit:  %s", fr_syserror(errno));
326                 return -1;
327         }
328 #endif
329         return 0;
330 }
331
332 /** Enable or disable core dumps
333  *
334  * @param allow_core_dumps whether to enable or disable core dumps.
335  */
336 int fr_set_dumpable(bool allow_core_dumps)
337 {
338         /*
339          *      If configured, turn core dumps off.
340          */
341         if (!allow_core_dumps) {
342 #ifdef HAVE_SYS_RESOURCE_H
343                 struct rlimit no_core;
344
345                 no_core.rlim_cur = 0;
346                 no_core.rlim_max = 0;
347
348                 if (setrlimit(RLIMIT_CORE, &no_core) < 0) {
349                         fr_strerror_printf("Failed disabling core dumps: %s", fr_syserror(errno));
350
351                         return -1;
352                 }
353 #endif
354                 return 0;
355         }
356
357         if (fr_set_dumpable_flag(true) < 0) return -1;
358
359         /*
360          *      Reset the core dump limits to their original value.
361          */
362 #ifdef HAVE_SYS_RESOURCE_H
363         if (setrlimit(RLIMIT_CORE, &core_limits) < 0) {
364                 fr_strerror_printf("Cannot update core dump limit: %s", fr_syserror(errno));
365
366                 return -1;
367         }
368 #endif
369         return 0;
370 }
371
372 /** Check to see if panic_action file is world writeable
373  *
374  * @return 0 if file is OK, else -1.
375  */
376 static int fr_fault_check_permissions(void)
377 {
378         char const *p, *q;
379         size_t len;
380         char filename[256];
381         struct stat statbuf;
382
383         /*
384          *      Try and guess which part of the command is the binary, and check to see if
385          *      it's world writeable, to try and save the admin from their own stupidity.
386          *
387          *      @fixme we should do this properly and take into account single and double
388          *      quotes.
389          */
390         if ((q = strchr(panic_action, ' '))) {
391                 /*
392                  *      need to use a static buffer, because mallocing memory in a signal handler
393                  *      is a bad idea and can result in deadlock.
394                  */
395                 len = snprintf(filename, sizeof(filename), "%.*s", (int)(q - panic_action), panic_action);
396                 if (is_truncated(len, sizeof(filename))) {
397                         fr_strerror_printf("Failed writing panic_action to temporary buffer (truncated)");
398                         return -1;
399                 }
400                 p = filename;
401         } else {
402                 p = panic_action;
403         }
404
405         if (stat(p, &statbuf) == 0) {
406 #ifdef S_IWOTH
407                 if ((statbuf.st_mode & S_IWOTH) != 0) {
408                         fr_strerror_printf("panic_action file \"%s\" is globally writable", p);
409                         return -1;
410                 }
411 #endif
412         }
413
414         return 0;
415 }
416
417 /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set.
418  *
419  * @param sig caught
420  */
421 void fr_fault(int sig)
422 {
423         char cmd[sizeof(panic_action) + 20];
424         char *out = cmd;
425         size_t left = sizeof(cmd), ret;
426
427         char const *p = panic_action;
428         char const *q;
429
430         int code;
431
432         /*
433          *      Makes the backtraces slightly cleaner
434          */
435         memset(cmd, 0, sizeof(cmd));
436
437         fr_fault_log("CAUGHT SIGNAL: %s\n", strsignal(sig));
438
439         /*
440          *      Check for administrator sanity.
441          */
442         if (fr_fault_check_permissions() < 0) {
443                 fr_fault_log("Refusing to execute panic action: %s\n", fr_strerror());
444                 goto finish;
445         }
446
447         /*
448          *      Run the callback if one was registered
449          */
450         if (panic_cb && (panic_cb(sig) < 0)) goto finish;
451
452         /*
453          *      Produce a simple backtrace - They've very basic but at least give us an
454          *      idea of the area of the code we hit the issue in.
455          */
456 #ifdef HAVE_EXECINFO
457         {
458                 size_t frame_count, i;
459                 void *stack[MAX_BT_FRAMES];
460                 char **strings;
461
462                 frame_count = backtrace(stack, MAX_BT_FRAMES);
463
464                 fr_fault_log("Backtrace of last %zu frames:\n", frame_count);
465
466                 /*
467                  *      Only use backtrace_symbols() if we don't have a logging fd.
468                  *      If the server has experienced memory corruption, there's
469                  *      a high probability that calling backtrace_symbols() which
470                  *      mallocs more memory, will fail.
471                  */
472                 if (fr_fault_log_fd < 0) {
473                         strings = backtrace_symbols(stack, frame_count);
474                         for (i = 0; i < frame_count; i++) {
475                                 fr_fault_log("%s\n", strings[i]);
476                         }
477                         free(strings);
478                 } else {
479                         backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd);
480                 }
481         }
482 #endif
483
484         /* No panic action set... */
485         if (panic_action[0] == '\0') {
486                 fr_fault_log("No panic action set\n");
487                 goto finish;
488         }
489
490         /* Substitute %p for the current PID (useful for attaching a debugger) */
491         while ((q = strstr(p, "%p"))) {
492                 out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid());
493                 if (left <= ret) {
494                 oob:
495                         fr_fault_log("Panic action too long");
496                         fr_exit_now(1);
497                 }
498                 left -= ret;
499                 p = q + 2;
500         }
501         if (strlen(p) >= left) goto oob;
502         strlcpy(out, p, left);
503
504         fr_fault_log("Calling: %s\n", cmd);
505
506         {
507                 bool disable = false;
508
509                 /*
510                  *      Here we temporarily enable the dumpable flag so if GBD or LLDB
511                  *      is called in the panic_action, they can pattach tot he running
512                  *      process.
513                  */
514                 if (fr_get_dumpable_flag() == 0) {
515                         if ((fr_set_dumpable_flag(true) < 0) || !fr_get_dumpable_flag()) {
516                                 fr_fault_log("Failed setting dumpable flag, pattach may not work: %s", fr_strerror());
517                         } else {
518                                 disable = true;
519                         }
520                         fr_fault_log("Temporarily setting PR_DUMPABLE to 1");
521                 }
522
523                 code = system(cmd);
524
525                 /*
526                  *      We only want to error out here, if dumpable was originally disabled
527                  *      and we managed to change the value to enabled, but failed
528                  *      setting it back to disabled.
529                  */
530                 if (disable) {
531                         fr_fault_log("Resetting PR_DUMPABLE to 0");
532                         if (fr_set_dumpable_flag(false) < 0) {
533                                 fr_fault_log("Failed reseting dumpable flag to off: %s", fr_strerror());
534                                 fr_fault_log("Exiting due to insecure process state");
535                                 fr_exit_now(1);
536                         }
537                 }
538         }
539
540         fr_fault_log("Panic action exited with %i", code);
541
542 finish:
543 #ifdef SIGUSR1
544         if (sig == SIGUSR1) {
545                 return;
546         }
547 #endif
548         fr_exit_now(1);
549 }
550
551 #ifdef SIGABRT
552 /** Work around debuggers which can't backtrace past the signal handler
553  *
554  * At least this provides us some information when we get talloc errors.
555  */
556 static void _fr_talloc_fault(char const *reason)
557 {
558         fr_fault_log("talloc abort: %s\n", reason);
559         fr_fault(SIGABRT);
560 }
561 #endif
562
563 /** Wrapper to pass talloc log output to our fr_fault_log function
564  *
565  */
566 static void _fr_talloc_log(char const *msg)
567 {
568         fr_fault_log("%s\n", msg);
569 }
570
571 /** Generate a talloc memory report for a context and print to stderr/stdout
572  *
573  * @param ctx to generate a report for, may be NULL in which case the root context is used.
574  */
575 int fr_log_talloc_report(TALLOC_CTX *ctx)
576 {
577         FILE *log;
578         char const *null_ctx = NULL;
579         int i = 0;
580         int fd;
581
582         fd = dup(fr_fault_log_fd);
583         if (fd < 0) {
584                 fr_strerror_printf("Couldn't write memory report, failed to dup log fd: %s", fr_syserror(errno));
585                 return -1;
586         }
587         log = fdopen(fd, "w");
588         if (!log) {
589                 close(fd);
590                 fr_strerror_printf("Couldn't write memory report, fdopen failed: %s", fr_syserror(errno));
591                 return -1;
592         }
593
594         fprintf(log, "Current state of talloced memory:\n");
595         if (ctx) {
596                 null_ctx = talloc_get_name(NULL);
597         }
598
599         if (!ctx) {
600                 talloc_report_full(NULL, log);
601         } else do {
602                 fprintf(log, "Context level %i", i++);
603
604                 talloc_report_full(ctx, log);
605         } while ((ctx = talloc_parent(ctx)) && (talloc_get_name(ctx) != null_ctx));  /* Stop before we hit NULL ctx */
606
607         fclose(log);
608
609         return 0;
610 }
611
612 /** Signal handler to print out a talloc memory report
613  *
614  * @param sig caught
615  */
616 static void _fr_fault_mem_report(int sig)
617 {
618         fr_fault_log("CAUGHT SIGNAL: %s\n", strsignal(sig));
619
620         if (fr_log_talloc_report(NULL) < 0) fr_perror("memreport");
621 }
622
623 static int _fr_disable_null_tracking(UNUSED bool *p)
624 {
625         talloc_disable_null_tracking();
626         return 0;
627 }
628
629 /** Registers signal handlers to execute panic_action on fatal signal
630  *
631  * May be called multiple time to change the panic_action/program.
632  *
633  * @param cmd to execute on fault. If present %p will be substituted
634  *        for the parent PID before the command is executed, and %e
635  *        will be substituted for the currently running program.
636  * @param program Name of program currently executing (argv[0]).
637  * @return 0 on success -1 on failure.
638  */
639 int fr_fault_setup(char const *cmd, char const *program)
640 {
641         static bool setup = false;
642
643         char *out = panic_action;
644         size_t left = sizeof(panic_action), ret;
645
646         char const *p = cmd;
647         char const *q;
648
649         if (cmd) {
650                 /* Substitute %e for the current program */
651                 while ((q = strstr(p, "%e"))) {
652                         out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : "");
653                         if (left <= ret) {
654                         oob:
655                                 fr_strerror_printf("Panic action too long");
656                                 return -1;
657                         }
658                         left -= ret;
659                         p = q + 2;
660                 }
661                 if (strlen(p) >= left) goto oob;
662                 strlcpy(out, p, left);
663         } else {
664                 *panic_action = '\0';
665         }
666
667         /*
668          *      Check for administrator sanity.
669          */
670         if (fr_fault_check_permissions() < 0) return -1;
671
672         /* Unsure what the side effects of changing the signal handler mid execution might be */
673         if (!setup) {
674 #ifdef SIGSEGV
675                 if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1;
676 #endif
677 #ifdef SIGBUS
678                 if (fr_set_signal(SIGBUS, fr_fault) < 0) return -1;
679 #endif
680 #ifdef SIGABRT
681                 if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1;
682                 /*
683                  *  Use this instead of abort so we get a
684                  *  full backtrace with broken versions of LLDB
685                  */
686                 talloc_set_abort_fn(_fr_talloc_fault);
687 #endif
688 #ifdef SIGFPE
689                 if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1;
690 #endif
691
692 #ifdef SIGUSR1
693                 if (fr_set_signal(SIGUSR1, fr_fault) < 0) return -1;
694 #endif
695
696 #ifdef SIGUSR2
697                 if (fr_set_signal(SIGUSR2, _fr_fault_mem_report) < 0) return -1;
698 #endif
699
700                 /*
701                  *  Setup the default logger
702                  */
703                 if (!fr_fault_log) fr_fault_set_log_fn(NULL);
704                 talloc_set_log_fn(_fr_talloc_log);
705
706                 /*
707                  *  Needed for memory reports
708                  *
709                  *  Disable null tracking on exit, else valgrind complains
710                  */
711                 {
712                         TALLOC_CTX *autofree;
713                         bool *marker;
714
715                         talloc_enable_null_tracking();
716
717                         autofree = talloc_autofree_context();
718                         marker = talloc(autofree, bool);
719                         talloc_set_destructor(marker, _fr_disable_null_tracking);
720                 }
721
722                 /*
723                  *  If were using glibc malloc > 2.4 this scribbles over
724                  *  uninitialised and freed memory, to make memory issues easier
725                  *  to track down.
726                  */
727 #if defined(HAVE_MALLOPT) && !defined(NDEBUG)
728                 mallopt(M_PERTURB, 0x42);
729                 mallopt(M_CHECK_ACTION, 3);
730 #endif
731         }
732         setup = true;
733
734         return 0;
735 }
736
737 /** Set a callback to be called before fr_fault()
738  *
739  * @param func to execute. If callback returns < 0
740  *      fr_fault will exit before running panic_action code.
741  */
742 void fr_fault_set_cb(fr_fault_cb_t func)
743 {
744         panic_cb = func;
745 };
746
747 /** Default logger, logs output to stderr
748  *
749  */
750 static void CC_HINT(format (printf, 1, 2)) _fr_fault_log(char const *msg, ...)
751 {
752         va_list ap;
753
754         va_start(ap, msg);
755         vfprintf(stderr, msg, ap);
756         va_end(ap);
757 }
758
759
760 /** Set a file descriptor to log panic_action output to.
761  *
762  * @param func to call to output log messages.
763  */
764 void fr_fault_set_log_fn(fr_fault_log_t func)
765 {
766         fr_fault_log = func ? func : _fr_fault_log;
767 }
768
769 /** Set a file descriptor to log memory reports to.
770  *
771  * @param fd to write output to.
772  */
773 void fr_fault_set_log_fd(int fd)
774 {
775         fr_fault_log_fd = fd;
776 }
777
778
779 #ifdef WITH_VERIFY_PTR
780
781 /*
782  *      Verify a VALUE_PAIR
783  */
784 inline void fr_verify_vp(VALUE_PAIR const *vp)
785 {
786         (void) talloc_get_type_abort(vp, VALUE_PAIR);
787
788         if (vp->data.ptr) switch (vp->da->type) {
789         case PW_TYPE_OCTETS:
790         case PW_TYPE_TLV:
791         {
792                 size_t len;
793
794                 if (!talloc_get_type(vp->data.ptr, uint8_t)) {
795                         fr_perror("Type check failed for attribute \"%s\"", vp->da->name);
796                         (void) talloc_get_type_abort(vp->data.ptr, uint8_t);
797                 }
798
799                 len = talloc_array_length(vp->vp_octets);
800                 if (vp->length > len) {
801                         fr_perror("VALUE_PAIR length %zu does not equal uint8_t buffer length %zu", vp->length, len);
802                         fr_assert(0);
803                         fr_exit_now(1);
804                 }
805         }
806                 break;
807
808         case PW_TYPE_STRING:
809         {
810                 size_t len;
811
812                 if (!talloc_get_type(vp->data.ptr, char)) {
813                         fr_perror("Type check failed for attribute \"%s\"", vp->da->name);
814                         (void) talloc_get_type_abort(vp->data.ptr, char);
815                 }
816
817                 len = (talloc_array_length(vp->vp_strvalue) - 1);
818                 if (vp->length > len) {
819                         fr_perror("VALUE_PAIR %s length %zu is too small for char buffer length %zu",
820                                   vp->da->name, vp->length, len);
821                         fr_assert(0);
822                         fr_exit_now(1);
823                 }
824                 if (vp->vp_strvalue[vp->length] != '\0') {
825                         fr_perror("VALUE_PAIR %s buffer not \\0 terminated", vp->da->name);
826                         fr_assert(0);
827                         fr_exit_now(1);
828                 }
829         }
830                 break;
831
832         default:
833                 break;
834         }
835 }
836
837 /*
838  *      Verify a pair list
839  */
840 void fr_verify_list(TALLOC_CTX *expected, VALUE_PAIR *vps)
841 {
842         vp_cursor_t cursor;
843         VALUE_PAIR *vp;
844         TALLOC_CTX *parent;
845
846         for (vp = fr_cursor_init(&cursor, &vps);
847              vp;
848              vp = fr_cursor_next(&cursor)) {
849                 VERIFY_VP(vp);
850
851                 parent = talloc_parent(vp);
852                 if (expected && (parent != expected)) {
853                         fr_perror("Expected VALUE_PAIR (%s) to be parented by %p (%s), "
854                                   "but parented by %p (%s)",
855                                   vp->da->name,
856                                   expected, talloc_get_name(expected),
857                                   parent, parent ? talloc_get_name(parent) : "NULL");
858
859                         fr_log_talloc_report(expected);
860                         if (parent) fr_log_talloc_report(parent);
861
862                         assert(0);
863                 }
864
865         }
866 }
867 #endif