Typo
[freeradius.git] / src / lib / debug.c
index 7bccccd..1947325 100644 (file)
  * @copyright 2013  The FreeRADIUS server project
  * @copyright 2013  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
  */
+#include <assert.h>
 #include <freeradius-devel/libradius.h>
 #include <sys/stat.h>
 
+#if defined(HAVE_MALLOPT) && defined(HAVE_MALLOC_H)
+#  include <malloc.h>
+#endif
+
 /*
  *     runtime backtrace functions are not POSIX but are included in
  *     glibc, OSX >= 10.5 and various BSDs
 #  include <sys/prctl.h>
 #endif
 
+#ifdef HAVE_SYS_RESOURCE_H
+#  include <sys/resource.h>
+#endif
+
 #ifdef HAVE_PTHREAD_H
 #  define PTHREAD_MUTEX_LOCK pthread_mutex_lock
 #  define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
@@ -46,7 +55,7 @@
 
 #ifdef HAVE_EXECINFO
 #  define MAX_BT_FRAMES 128
-#  define MAX_BT_CBUFF  65536                  //!< Should be a power of 2
+#  define MAX_BT_CBUFF  65536                          //!< Should be a power of 2
 
 #  ifdef HAVE_PTHREAD_H
 static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER;
@@ -59,20 +68,29 @@ typedef struct fr_bt_info {
 } fr_bt_info_t;
 
 struct fr_bt_marker {
-       void            *obj;                   //!< Pointer to the parent object, this is our needle
-                                               //!< when we iterate over the contents of the circular buffer.
-       fr_cbuff_t      *cbuff;                 //!< Where we temporarily store the backtraces
+       void            *obj;                           //!< Pointer to the parent object, this is our needle
+                                                       //!< when we iterate over the contents of the circular buffer.
+       fr_cbuff_t      *cbuff;                         //!< Where we temporarily store the backtraces
 };
 #endif
 
-static char panic_action[512];
-static fr_fault_cb panic_cb;
-static int fr_debugger_present = -1;
+static char panic_action[512];                         //!< The command to execute when panicking.
+static fr_fault_cb_t panic_cb = NULL;                  //!< Callback to execute whilst panicking, before the
+                                                       //!< panic_action.
+static fr_fault_log_t fr_fault_log = NULL;             //!< Function to use to process logging output.
+static int fr_fault_log_fd = STDERR_FILENO;            //!< Where to write debug output.
+
+static int fr_debugger_present = -1;                   //!< Whether were attached to by a debugger.
 
 #ifdef HAVE_SYS_RESOURCE_H
 static struct rlimit core_limits;
 #endif
 
+static TALLOC_CTX *talloc_null_ctx;
+static TALLOC_CTX *talloc_autofree_ctx;
+
+#define FR_FAULT_LOG(fmt, ...) fr_fault_log(fmt "\n", ## __VA_ARGS__)
+
 /** Stub callback to see if the SIGTRAP handler is overriden
  *
  * @param signum signal raised.
@@ -102,28 +120,6 @@ void fr_debug_break(void)
 }
 
 #ifdef HAVE_EXECINFO
-/** Generate a backtrace for an object during destruction
- *
- * If this is the first entry being inserted
- */
-static int _fr_do_bt(fr_bt_marker_t *marker)
-{
-       fr_bt_info_t *bt;
-
-       if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) {
-               return -1;
-       }
-
-       bt = talloc_zero(marker->cbuff, fr_bt_info_t);
-       if (!bt) {
-               return -1;
-       }
-       bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
-       fr_cbuff_rp_insert(marker->cbuff, bt);
-
-       return 0;
-}
-
 /** Print backtrace entry for a given object
  *
  * @param cbuff to search in.
@@ -133,23 +129,13 @@ void backtrace_print(fr_cbuff_t *cbuff, void *obj)
 {
        fr_bt_info_t *p;
        bool found = false;
-       int i = 0;
-       char **frames;
 
        while ((p = fr_cbuff_rp_next(cbuff, NULL))) {
-               if ((p == obj) || !obj) {
+               if ((p->obj == obj) || !obj) {
                        found = true;
-                       frames = backtrace_symbols(p->frames, p->count);
 
-                       fprintf(stderr, "Stacktrace for: %p\n", p);
-                       for (i = 0; i < p->count; i++) {
-                               fprintf(stderr, "%s\n", frames[i]);
-                       }
-
-                       /* We were only asked to look for one */
-                       if (obj) {
-                               return;
-                       }
+                       fprintf(stderr, "Stacktrace for: %p\n", p->obj);
+                       backtrace_symbols_fd(p->frames, p->count, STDERR_FILENO);
                }
        };
 
@@ -158,6 +144,27 @@ void backtrace_print(fr_cbuff_t *cbuff, void *obj)
        }
 }
 
+/** Generate a backtrace for an object
+ *
+ * If this is the first entry being inserted
+ */
+int fr_backtrace_do(fr_bt_marker_t *marker)
+{
+       fr_bt_info_t *bt;
+
+       if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) return -1;
+
+       bt = talloc_zero(NULL, fr_bt_info_t);
+       if (!bt) return -1;
+
+       bt->obj = marker->obj;
+       bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
+
+       fr_cbuff_rp_insert(marker->cbuff, bt);
+
+       return 0;
+}
+
 /** Inserts a backtrace marker into the provided context
  *
  * Allows for maximum laziness and will initialise a circular buffer if one has not already been created.
@@ -165,7 +172,7 @@ void backtrace_print(fr_cbuff_t *cbuff, void *obj)
  * Code augmentation should look something like:
 @verbatim
        // Create a static cbuffer pointer, the first call to backtrace_attach will initialise it
-       static fr_cbuff *my_obj_bt;
+       static fr_cbuff_t *my_obj_bt;
 
        my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) {
                my_obj_t *this;
@@ -197,12 +204,7 @@ fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj)
        if (*cbuff == NULL) {
                PTHREAD_MUTEX_LOCK(&fr_debug_init);
                /* Check again now we hold the mutex - eww*/
-               if (*cbuff == NULL) {
-                       TALLOC_CTX *ctx;
-
-                       ctx = fr_autofree_ctx();
-                       *cbuff = fr_cbuff_alloc(ctx, MAX_BT_CBUFF, true);
-               }
+               if (*cbuff == NULL) *cbuff = fr_cbuff_alloc(NULL, MAX_BT_CBUFF, true);
                PTHREAD_MUTEX_UNLOCK(&fr_debug_init);
        }
 
@@ -214,7 +216,12 @@ fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj)
        marker->obj = (void *) obj;
        marker->cbuff = *cbuff;
 
-       talloc_set_destructor(marker, _fr_do_bt);
+       fprintf(stderr, "Backtrace attached to %s %p\n", talloc_get_name(obj), obj);
+       /*
+        *      Generate the backtrace for memory allocation
+        */
+       fr_backtrace_do(marker);
+       talloc_set_destructor(marker, fr_backtrace_do);
 
        return marker;
 }
@@ -230,6 +237,25 @@ fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_cbuff_t **cbuff, UNUSED TALLOC_CTX
 }
 #endif /* ifdef HAVE_EXECINFO */
 
+static int _panic_on_free(UNUSED char *foo)
+{
+       fr_fault(SIGUSR1);
+       return -1;      /* this should make the free fail */
+}
+
+/** Insert memory into the context of another talloc memory chunk which
+ * causes a panic when freed.
+ *
+ * @param ctx TALLOC_CTX to monitor for frees.
+ */
+void fr_panic_on_free(TALLOC_CTX *ctx)
+{
+       char *ptr;
+
+       ptr = talloc(ctx, char);
+       talloc_set_destructor(ptr, _panic_on_free);
+}
+
 /** Set the dumpable flag, also controls whether processes can PATTACH
  *
  * @param dumpable whether we should allow core dumping
@@ -248,10 +274,40 @@ static int fr_set_dumpable_flag(bool dumpable)
 #else
 static int fr_set_dumpable_flag(UNUSED bool dumpable)
 {
-       return 0;
+       fr_strerror_printf("Changing value of PR_DUMPABLE not supported on this system");
+       return -2;
 }
 #endif
 
+/** Get the processes dumpable flag
+ *
+ */
+#if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_DUMPABLE)
+static int fr_get_dumpable_flag(void)
+{
+       int ret;
+
+       ret = prctl(PR_GET_DUMPABLE);
+       if (ret < 0) {
+               fr_strerror_printf("Cannot get dumpable flag: %s", fr_syserror(errno));
+               return -1;
+       }
+
+       /*
+        *  Linux is crazy and prctl sometimes returns 2 for disabled
+        */
+       if (ret != 1) return 0;
+       return 1;
+}
+#else
+static int fr_get_dumpable_flag(void)
+{
+       fr_strerror_printf("Getting value of PR_DUMPABLE not supported on this system");
+       return -2;
+}
+#endif
+
+
 /** Get the current maximum for core files
  *
  * Do this before anything else so as to ensure it's properly initialized.
@@ -307,6 +363,51 @@ int fr_set_dumpable(bool allow_core_dumps)
        return 0;
 }
 
+/** Check to see if panic_action file is world writeable
+ *
+ * @return 0 if file is OK, else -1.
+ */
+static int fr_fault_check_permissions(void)
+{
+       char const *p, *q;
+       size_t len;
+       char filename[256];
+       struct stat statbuf;
+
+       /*
+        *      Try and guess which part of the command is the binary, and check to see if
+        *      it's world writeable, to try and save the admin from their own stupidity.
+        *
+        *      @fixme we should do this properly and take into account single and double
+        *      quotes.
+        */
+       if ((q = strchr(panic_action, ' '))) {
+               /*
+                *      need to use a static buffer, because mallocing memory in a signal handler
+                *      is a bad idea and can result in deadlock.
+                */
+               len = snprintf(filename, sizeof(filename), "%.*s", (int)(q - panic_action), panic_action);
+               if (is_truncated(len, sizeof(filename))) {
+                       fr_strerror_printf("Failed writing panic_action to temporary buffer (truncated)");
+                       return -1;
+               }
+               p = filename;
+       } else {
+               p = panic_action;
+       }
+
+       if (stat(p, &statbuf) == 0) {
+#ifdef S_IWOTH
+               if ((statbuf.st_mode & S_IWOTH) != 0) {
+                       fr_strerror_printf("panic_action file \"%s\" is globally writable", p);
+                       return -1;
+               }
+#endif
+       }
+
+       return 0;
+}
+
 /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set.
  *
  * @param sig caught
@@ -322,45 +423,66 @@ void fr_fault(int sig)
 
        int code;
 
-       fprintf(stderr, "CAUGHT SIGNAL: %s\n", strsignal(sig));
+       /*
+        *      Makes the backtraces slightly cleaner
+        */
+       memset(cmd, 0, sizeof(cmd));
+
+       FR_FAULT_LOG("CAUGHT SIGNAL: %s", strsignal(sig));
 
-#ifdef SIGUSR1
        /*
-        *      SIGUSR1 skips the callback, and the backtrace and just runs the
-        *      panic_action.
+        *      Check for administrator sanity.
         */
-       if (sig == SIGUSR1) goto skip_backtrace;
-#endif
+       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)) fr_exit_now(1);
+       if (panic_cb && (panic_cb(sig) < 0)) goto finish;
 
        /*
         *      Produce a simple backtrace - They've 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.
         */
-#ifdef HAVE_EXECINFO
-       size_t frame_count, i;
-       void *stack[MAX_BT_FRAMES];
-       char **frames;
+#if defined(HAVE_EXECINFO) && (!defined(NDEBUG) || !defined(__GNUC__))
+       {
+               size_t frame_count, i;
+               void *stack[MAX_BT_FRAMES];
+               char **strings;
 
-       frame_count = backtrace(stack, MAX_BT_FRAMES);
-       frames = backtrace_symbols(stack, frame_count);
+               frame_count = backtrace(stack, MAX_BT_FRAMES);
 
-       fprintf(stderr, "Backtrace of last %zu frames:\n", frame_count);
-       for (i = 0; i < frame_count; i++) {
-               fprintf(stderr, "%s\n", frames[i]);
-               /* Leak the backtrace strings, freeing may lead to undefined behaviour... */
+               FR_FAULT_LOG("Backtrace of last %zu frames:", frame_count);
+
+               /*
+                *      Only use backtrace_symbols() if we don't have a logging fd.
+                *      If the server has experienced memory corruption, there's
+                *      a high probability that calling backtrace_symbols() which
+                *      mallocs more memory, will fail.
+                */
+               if (fr_fault_log_fd < 0) {
+                       strings = backtrace_symbols(stack, frame_count);
+                       for (i = 0; i < frame_count; i++) {
+                               FR_FAULT_LOG("%s", strings[i]);
+                       }
+                       free(strings);
+               } else {
+                       backtrace_symbols_fd(stack, frame_count, fr_fault_log_fd);
+               }
        }
 #endif
 
-skip_backtrace:
        /* No panic action set... */
        if (panic_action[0] == '\0') {
-               fprintf(stderr, "No panic action set\n");
-               fr_exit_now(1);
+               FR_FAULT_LOG("No panic action set");
+               goto finish;
        }
 
        /* Substitute %p for the current PID (useful for attaching a debugger) */
@@ -368,7 +490,7 @@ skip_backtrace:
                out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid());
                if (left <= ret) {
                oob:
-                       fprintf(stderr, "Panic action too long\n");
+                       FR_FAULT_LOG("Panic action too long");
                        fr_exit_now(1);
                }
                left -= ret;
@@ -377,27 +499,134 @@ skip_backtrace:
        if (strlen(p) >= left) goto oob;
        strlcpy(out, p, left);
 
-       fprintf(stderr, "Calling: %s\n", cmd);
-       code = system(cmd);
-       fprintf(stderr, "Panic action exited with %i\n", code);
+       FR_FAULT_LOG("Calling: %s", cmd);
 
-#ifdef SIGUSR1
-       if (sig == SIGUSR1) return;
-#endif
+       {
+               bool disable = false;
 
-#ifdef SIGUSR2
-       if (sig == SIGUSR2) return;
+               /*
+                *      Here we temporarily enable the dumpable flag so if GBD or LLDB
+                *      is called in the panic_action, they can pattach tot he 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 reseting 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);
+
+finish:
+#ifdef SIGUSR1
+       if (sig == SIGUSR1) {
+               return;
+       }
 #endif
        fr_exit_now(1);
 }
 
 #ifdef SIGABRT
-static void _fr_talloc_fault(UNUSED char const *message)
+/** Work around debuggers which can't backtrace past the signal handler
+ *
+ * At least this provides us some information when we get talloc errors.
+ */
+static void _fr_talloc_fault(char const *reason)
 {
+       fr_fault_log("talloc abort: %s\n", reason);
        fr_fault(SIGABRT);
 }
 #endif
 
+/** Wrapper to pass talloc log output to our fr_fault_log function
+ *
+ */
+static void _fr_talloc_log(char const *msg)
+{
+       fr_fault_log("%s\n", msg);
+}
+
+/** Generate a talloc memory report for a context and print to stderr/stdout
+ *
+ * @param ctx to generate a report for, may be NULL in which case the root context is used.
+ */
+int fr_log_talloc_report(TALLOC_CTX *ctx)
+{
+       FILE *log;
+       int i = 0;
+       int fd;
+
+       fd = dup(fr_fault_log_fd);
+       if (fd < 0) {
+               fr_strerror_printf("Couldn't write memory report, failed to dup log fd: %s", fr_syserror(errno));
+               return -1;
+       }
+       log = fdopen(fd, "w");
+       if (!log) {
+               close(fd);
+               fr_strerror_printf("Couldn't write memory report, fdopen failed: %s", fr_syserror(errno));
+               return -1;
+       }
+
+       if (!ctx) {
+               fprintf(log, "Current state of talloced memory:\n");
+               talloc_report_full(talloc_null_ctx, log);
+       } else {
+               fprintf(log, "Talloc chunk lineage:\n");
+               fprintf(log, "%p (%s)", ctx, talloc_get_name(ctx));
+               while ((ctx = talloc_parent(ctx))) fprintf(log, " < %p (%s)", ctx, talloc_get_name(ctx));
+               fprintf(log, "\n");
+
+               do {
+                       fprintf(log, "Talloc context level %i:\n", i++);
+                       talloc_report_full(ctx, log);
+               } while ((ctx = talloc_parent(ctx)) &&
+                        (talloc_parent(ctx) != talloc_autofree_ctx) && /* Stop before we hit the autofree ctx */
+                        (talloc_parent(ctx) != talloc_null_ctx));      /* Stop before we hit NULL ctx */
+       }
+
+       fclose(log);
+
+       return 0;
+}
+
+/** 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\n", strsignal(sig));
+
+       if (fr_log_talloc_report(NULL) < 0) fr_perror("memreport");
+}
+
+static int _fr_disable_null_tracking(UNUSED bool *p)
+{
+       talloc_disable_null_tracking();
+       return 0;
+}
+
 /** Registers signal handlers to execute panic_action on fatal signal
  *
  * May be called multiple time to change the panic_action/program.
@@ -417,8 +646,6 @@ int fr_fault_setup(char const *cmd, char const *program)
 
        char const *p = cmd;
        char const *q;
-       char *filename = NULL;
-       struct stat statbuf;
 
        if (cmd) {
                /* Substitute %e for the current program */
@@ -439,35 +666,9 @@ int fr_fault_setup(char const *cmd, char const *program)
        }
 
        /*
-        *      Try and guess which part of the command is the binary, and check to see if
-        *      it's world writeable, to try and save the admin from their own stupidity.
-        *
-        *      @fixme we should do this properly and take into account single and double
-        *      quotes.
-        */
-       if ((q = strchr(panic_action, ' '))) {
-               asprintf(&filename, "%.*s", (int)(q - panic_action), panic_action);
-               p = filename;
-       } else {
-               p = panic_action;
-       }
-
-       if (stat(p, &statbuf) == 0) {
-#ifdef S_IWOTH
-               if ((statbuf.st_mode & S_IWOTH) != 0) {
-                       fr_strerror_printf("panic_action binary \"%s\" is globally writable. "
-                                          "Refusing to start due to insecure configuration", p);
-                       return -1;
-               }
-#endif
-       }
-
-       free(filename);
-
-       /*
-        *      This is required on some systems to be able to PATTACH to the process.
+        *      Check for administrator sanity.
         */
-       fr_set_dumpable_flag(true);
+       if (fr_fault_check_permissions() < 0) return -1;
 
        /* Unsure what the side effects of changing the signal handler mid execution might be */
        if (!setup) {
@@ -494,7 +695,59 @@ int fr_fault_setup(char const *cmd, char const *program)
 #endif
 
 #ifdef SIGUSR2
-               if (fr_set_signal(SIGUSR2, fr_fault) < 0) return -1;
+               if (fr_set_signal(SIGUSR2, _fr_fault_mem_report) < 0) return -1;
+#endif
+
+               /*
+                *  Setup the default logger
+                */
+               if (!fr_fault_log) fr_fault_set_log_fn(NULL);
+               talloc_set_log_fn(_fr_talloc_log);
+
+               /*
+                *  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
        }
        setup = true;
@@ -504,10 +757,165 @@ int fr_fault_setup(char const *cmd, char const *program)
 
 /** Set a callback to be called before fr_fault()
  *
- * @param cb to execute. If callback returns < 0
+ * @param func to execute. If callback returns < 0
  *     fr_fault will exit before running panic_action code.
  */
-void fr_fault_set_cb(fr_fault_cb cb)
+void fr_fault_set_cb(fr_fault_cb_t func)
 {
-       panic_cb = cb;
+       panic_cb = func;
 };
+
+/** Default logger, logs output to stderr
+ *
+ */
+static void CC_HINT(format (printf, 1, 2)) _fr_fault_log(char const *msg, ...)
+{
+       va_list ap;
+
+       va_start(ap, msg);
+       vfprintf(stderr, msg, ap);
+       va_end(ap);
+}
+
+
+/** Set a file descriptor to log panic_action output to.
+ *
+ * @param func to call to output log messages.
+ */
+void fr_fault_set_log_fn(fr_fault_log_t func)
+{
+       fr_fault_log = func ? func : _fr_fault_log;
+}
+
+/** Set a file descriptor to log memory reports to.
+ *
+ * @param fd to write output to.
+ */
+void fr_fault_set_log_fd(int fd)
+{
+       fr_fault_log_fd = fd;
+}
+
+
+#ifdef WITH_VERIFY_PTR
+
+/*
+ *     Verify a VALUE_PAIR
+ */
+inline void fr_verify_vp(char const *file, int line, VALUE_PAIR const *vp)
+{
+       if (!vp) {
+               fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR pointer was NULL", file, line);
+               fr_assert(0);
+               fr_exit_now(0);
+       }
+
+       (void) talloc_get_type_abort(vp, VALUE_PAIR);
+
+       if (vp->data.ptr) switch (vp->da->type) {
+       case PW_TYPE_OCTETS:
+       case PW_TYPE_TLV:
+       {
+               size_t len;
+               TALLOC_CTX *parent;
+
+               if (!talloc_get_type(vp->data.ptr, uint8_t)) {
+                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be "
+                               "uint8_t but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr));
+                       (void) talloc_get_type_abort(vp->data.ptr, uint8_t);
+               }
+
+               len = talloc_array_length(vp->vp_octets);
+               if (vp->length > len) {
+                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than "
+                               "uint8_t data buffer length %zu\n", file, line, vp->da->name, vp->length, len);
+                       fr_assert(0);
+                       fr_exit_now(1);
+               }
+
+               parent = talloc_parent(vp->data.ptr);
+               if (parent != vp) {
+                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer is not "
+                               "parented by VALUE_PAIR %p, instead parented by %p (%s)\n",
+                               file, line, vp->da->name,
+                               vp, parent, parent ? talloc_get_name(parent) : "NULL");
+                       fr_assert(0);
+                       fr_exit_now(1);
+               }
+       }
+               break;
+
+       case PW_TYPE_STRING:
+       {
+               size_t len;
+               TALLOC_CTX *parent;
+
+               if (!talloc_get_type(vp->data.ptr, char)) {
+                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be "
+                               "char but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr));
+                       (void) talloc_get_type_abort(vp->data.ptr, char);
+               }
+
+               len = (talloc_array_length(vp->vp_strvalue) - 1);
+               if (vp->length > len) {
+                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than "
+                               "char buffer length %zu\n", file, line, vp->da->name, vp->length, len);
+                       fr_assert(0);
+                       fr_exit_now(1);
+               }
+
+               if (vp->vp_strvalue[vp->length] != '\0') {
+                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer not \\0 "
+                               "terminated\n", file, line, vp->da->name);
+                       fr_assert(0);
+                       fr_exit_now(1);
+               }
+
+               parent = talloc_parent(vp->data.ptr);
+               if (parent != vp) {
+                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" uint8_t buffer is not "
+                               "parented by VALUE_PAIR %p, instead parented by %p (%s)\n",
+                               file, line, vp->da->name,
+                               vp, parent, parent ? talloc_get_name(parent) : "NULL");
+                       fr_assert(0);
+                       fr_exit_now(1);
+               }
+       }
+               break;
+
+       default:
+               break;
+       }
+}
+
+/*
+ *     Verify a pair list
+ */
+void fr_verify_list(char const *file, int line, TALLOC_CTX *expected, VALUE_PAIR *vps)
+{
+       vp_cursor_t cursor;
+       VALUE_PAIR *vp;
+       TALLOC_CTX *parent;
+
+       for (vp = fr_cursor_init(&cursor, &vps);
+            vp;
+            vp = fr_cursor_next(&cursor)) {
+               VERIFY_VP(vp);
+
+               parent = talloc_parent(vp);
+               if (expected && (parent != expected)) {
+                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: Expected VALUE_PAIR \"%s\" to be parented "
+                               "by %p (%s), instead parented by %p (%s)\n",
+                               file, line, vp->da->name,
+                               expected, talloc_get_name(expected),
+                               parent, parent ? talloc_get_name(parent) : "NULL");
+
+                       fr_log_talloc_report(expected);
+                       if (parent) fr_log_talloc_report(parent);
+
+                       assert(0);
+               }
+
+       }
+}
+#endif