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