diff options
Diffstat (limited to 'malloc')
| -rw-r--r-- | malloc/Makefile | 3 | ||||
| -rw-r--r-- | malloc/Versions | 9 | ||||
| -rw-r--r-- | malloc/hooks.c | 61 | ||||
| -rw-r--r-- | malloc/malloc-debug.c | 173 | ||||
| -rw-r--r-- | malloc/mcheck-impl.c | 406 | ||||
| -rw-r--r-- | malloc/mcheck.c | 398 |
6 files changed, 592 insertions, 458 deletions
diff --git a/malloc/Makefile b/malloc/Makefile index cafe427097..eb5f5560bb 100644 --- a/malloc/Makefile +++ b/malloc/Makefile @@ -92,9 +92,6 @@ tests-exclude-mcheck = tst-mallocstate \ tst-malloc-thread-fail \ tst-malloc-usable-tunables \ tst-malloc_info \ - tst-memalign \ - tst-posix_memalign \ - tst-realloc \ tst-pvalloc-fortify \ tst-reallocarray \ tst-compathooks-off tst-compathooks-on diff --git a/malloc/Versions b/malloc/Versions index c87f6df8ca..6548970419 100644 --- a/malloc/Versions +++ b/malloc/Versions @@ -110,8 +110,17 @@ libc_malloc_debug { realloc; valloc; + __free_hook; + __malloc_hook; + __memalign_hook; + __realloc_hook; + + mcheck; + mprobe; } GLIBC_2.2 { + mcheck_check_all; + mcheck_pedantic; posix_memalign; } GLIBC_2.16 { diff --git a/malloc/hooks.c b/malloc/hooks.c index 3cd44eeb84..8e9fefe6c3 100644 --- a/malloc/hooks.c +++ b/malloc/hooks.c @@ -34,65 +34,10 @@ void *(*__morecore)(ptrdiff_t); compat_symbol (libc, __morecore, __morecore, GLIBC_2_0); #endif -static void *malloc_hook_ini (size_t, const void *) __THROW; -static void *realloc_hook_ini (void *, size_t, const void *) __THROW; -static void *memalign_hook_ini (size_t, size_t, const void *) __THROW; - void weak_variable (*__free_hook) (void *, const void *) = NULL; -void *weak_variable (*__malloc_hook) - (size_t, const void *) = malloc_hook_ini; -void *weak_variable (*__realloc_hook) - (void *, size_t, const void *) = realloc_hook_ini; -void *weak_variable (*__memalign_hook) - (size_t, size_t, const void *) = memalign_hook_ini; - -/* Hooks for debugging versions. The initial hooks just call the - initialization routine, then do the normal work. */ - -/* These hooks will get executed only through the interposed allocator - functions in libc_malloc_debug.so. This means that the calls to malloc, - realloc, etc. will lead back into the interposed functions, which is what we - want. - - These initial hooks are assumed to be called in a single-threaded context, - so it is safe to reset all hooks at once upon initialization. */ - -static void -generic_hook_ini (void) -{ - __malloc_hook = NULL; - __realloc_hook = NULL; - __memalign_hook = NULL; - ptmalloc_init (); - -#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_24) - void (*hook) (void) = atomic_forced_read (__malloc_initialize_hook); - if (hook != NULL) - (*hook)(); -#endif - __malloc_initialized = 1; -} - -static void * -malloc_hook_ini (size_t sz, const void *caller) -{ - generic_hook_ini (); - return malloc (sz); -} - -static void * -realloc_hook_ini (void *ptr, size_t sz, const void *caller) -{ - generic_hook_ini (); - return realloc (ptr, sz); -} - -static void * -memalign_hook_ini (size_t alignment, size_t sz, const void *caller) -{ - generic_hook_ini (); - return memalign (alignment, sz); -} +void *weak_variable (*__malloc_hook) (size_t, const void *) = NULL; +void *weak_variable (*__realloc_hook) (void *, size_t, const void *) = NULL; +void *weak_variable (*__memalign_hook) (size_t, size_t, const void *) = NULL; #include "malloc-check.c" diff --git a/malloc/malloc-debug.c b/malloc/malloc-debug.c index 41dfcd3369..7c3a1e26b5 100644 --- a/malloc/malloc-debug.c +++ b/malloc/malloc-debug.c @@ -43,14 +43,96 @@ DEBUG_FN(valloc); DEBUG_FN(pvalloc); DEBUG_FN(calloc); -extern void (*__free_hook) (void *, const void *); -compat_symbol_reference (libc, __free_hook, __free_hook, GLIBC_2_0); -extern void * (*__malloc_hook) (size_t, const void *); -compat_symbol_reference (libc, __malloc_hook, __malloc_hook, GLIBC_2_0); -extern void * (*__realloc_hook) (void *, size_t, const void *); -compat_symbol_reference (libc, __realloc_hook, __realloc_hook, GLIBC_2_0); -extern void * (*__memalign_hook) (size_t, size_t, const void *); -compat_symbol_reference (libc, __memalign_hook, __memalign_hook, GLIBC_2_0); +static int debug_initialized = -1; + +enum malloc_debug_hooks +{ + MALLOC_NONE_HOOK = 0, + MALLOC_MCHECK_HOOK = 1 << 0, /* mcheck() */ +}; +static unsigned __malloc_debugging_hooks; + +static __always_inline bool +__is_malloc_debug_enabled (enum malloc_debug_hooks flag) +{ + return __malloc_debugging_hooks & flag; +} + +static __always_inline void +__malloc_debug_enable (enum malloc_debug_hooks flag) +{ + __malloc_debugging_hooks |= flag; +} + +static __always_inline void +__malloc_debug_disable (enum malloc_debug_hooks flag) +{ + __malloc_debugging_hooks &= ~flag; +} + +#include "mcheck.c" + +extern void (*__malloc_initialize_hook) (void); +compat_symbol_reference (libc, __malloc_initialize_hook, + __malloc_initialize_hook, GLIBC_2_0); + +static void *malloc_hook_ini (size_t, const void *) __THROW; +static void *realloc_hook_ini (void *, size_t, const void *) __THROW; +static void *memalign_hook_ini (size_t, size_t, const void *) __THROW; + +void (*__free_hook) (void *, const void *) = NULL; +void *(*__malloc_hook) (size_t, const void *) = malloc_hook_ini; +void *(*__realloc_hook) (void *, size_t, const void *) = realloc_hook_ini; +void *(*__memalign_hook) (size_t, size_t, const void *) = memalign_hook_ini; + +/* Hooks for debugging versions. The initial hooks just call the + initialization routine, then do the normal work. */ + +/* These hooks will get executed only through the interposed allocator + functions in libc_malloc_debug.so. This means that the calls to malloc, + realloc, etc. will lead back into the interposed functions, which is what we + want. + + These initial hooks are assumed to be called in a single-threaded context, + so it is safe to reset all hooks at once upon initialization. */ + +static void +generic_hook_ini (void) +{ + debug_initialized = 0; + __malloc_hook = NULL; + __realloc_hook = NULL; + __memalign_hook = NULL; + /* The compiler does not know that these functions are allocators, so it will + not try to optimize it away. */ + __libc_free (__libc_malloc (0)); + + void (*hook) (void) = __malloc_initialize_hook; + if (hook != NULL) + (*hook)(); + debug_initialized = 1; +} + +static void * +malloc_hook_ini (size_t sz, const void *caller) +{ + generic_hook_ini (); + return __debug_malloc (sz); +} + +static void * +realloc_hook_ini (void *ptr, size_t sz, const void *caller) +{ + generic_hook_ini (); + return __debug_realloc (ptr, sz); +} + +static void * +memalign_hook_ini (size_t alignment, size_t sz, const void *caller) +{ + generic_hook_ini (); + return __debug_memalign (alignment, sz); +} static size_t pagesize; @@ -63,7 +145,17 @@ __debug_malloc (size_t bytes) if (__builtin_expect (hook != NULL, 0)) return (*hook)(bytes, RETURN_ADDRESS (0)); - return __libc_malloc (bytes); + void *victim = NULL; + size_t orig_bytes = bytes; + if (!__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK) + || !malloc_mcheck_before (&bytes, &victim)) + { + victim = __libc_malloc (bytes); + } + if (__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK) && victim != NULL) + victim = malloc_mcheck_after (victim, orig_bytes); + + return victim; } strong_alias (__debug_malloc, malloc) @@ -76,6 +168,10 @@ __debug_free (void *mem) (*hook)(mem, RETURN_ADDRESS (0)); return; } + + if (__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK)) + mem = free_mcheck (mem); + __libc_free (mem); } strong_alias (__debug_free, free) @@ -88,7 +184,19 @@ __debug_realloc (void *oldmem, size_t bytes) if (__builtin_expect (hook != NULL, 0)) return (*hook)(oldmem, bytes, RETURN_ADDRESS (0)); - return __libc_realloc (oldmem, bytes); + size_t orig_bytes = bytes, oldsize = 0; + void *victim = NULL; + + if (!__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK) + || !realloc_mcheck_before (&oldmem, &bytes, &oldsize, &victim)) + { + victim = __libc_realloc (oldmem, bytes); + } + if (__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK) && victim != NULL) + victim = realloc_mcheck_after (victim, oldmem, orig_bytes, + oldsize); + + return victim; } strong_alias (__debug_realloc, realloc) @@ -100,7 +208,18 @@ _debug_mid_memalign (size_t alignment, size_t bytes, const void *address) if (__builtin_expect (hook != NULL, 0)) return (*hook)(alignment, bytes, address); - return __libc_memalign (alignment, bytes); + void *victim = NULL; + size_t orig_bytes = bytes; + + if (!__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK) + || !memalign_mcheck_before (alignment, &bytes, &victim)) + { + victim = __libc_memalign (alignment, bytes); + } + if (__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK) && victim != NULL) + victim = memalign_mcheck_after (victim, alignment, orig_bytes); + + return victim; } static void * @@ -165,17 +284,17 @@ strong_alias (__debug_posix_memalign, posix_memalign) static void * __debug_calloc (size_t nmemb, size_t size) { + size_t bytes; + + if (__glibc_unlikely (__builtin_mul_overflow (nmemb, size, &bytes))) + { + errno = ENOMEM; + return NULL; + } + void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL, 0)) { - size_t bytes; - - if (__glibc_unlikely (__builtin_mul_overflow (nmemb, size, &bytes))) - { - errno = ENOMEM; - return NULL; - } - void *mem = (*hook)(bytes, RETURN_ADDRESS (0)); if (mem != NULL) @@ -184,6 +303,20 @@ __debug_calloc (size_t nmemb, size_t size) return mem; } - return __libc_calloc (nmemb, size); + size_t orig_bytes = bytes; + void *victim = NULL; + + if (!__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK) + || !malloc_mcheck_before (&bytes, &victim)) + { + victim = __libc_malloc (bytes); + } + if (victim != NULL) + { + if (__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK)) + victim = malloc_mcheck_after (victim, orig_bytes); + memset (victim, 0, orig_bytes); + } + return victim; } strong_alias (__debug_calloc, calloc) diff --git a/malloc/mcheck-impl.c b/malloc/mcheck-impl.c new file mode 100644 index 0000000000..8857e6b179 --- /dev/null +++ b/malloc/mcheck-impl.c @@ -0,0 +1,406 @@ +/* mcheck debugging hooks for malloc. + Copyright (C) 1990-2021 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Written May 1989 by Mike Haertel. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <malloc-internal.h> +#include <mcheck.h> +#include <libintl.h> +#include <stdint.h> +#include <stdio.h> + +/* Arbitrary magical numbers. */ +#define MAGICWORD 0xfedabeeb +#define MAGICFREE 0xd8675309 +#define MAGICBYTE ((char) 0xd7) +#define MALLOCFLOOD ((char) 0x93) +#define FREEFLOOD ((char) 0x95) + +/* Function to call when something awful happens. */ +static void (*abortfunc) (enum mcheck_status); + +struct hdr +{ + size_t size; /* Exact size requested by user. */ + unsigned long int magic; /* Magic number to check header integrity. */ + struct hdr *prev; + struct hdr *next; + void *block; /* Real block allocated, for memalign. */ + unsigned long int magic2; /* Extra, keeps us doubleword aligned. */ +} __attribute__ ((aligned (MALLOC_ALIGNMENT))); + +/* This is the beginning of the list of all memory blocks allocated. + It is only constructed if the pedantic testing is requested. */ +static struct hdr *root; + +/* Nonzero if pedentic checking of all blocks is requested. */ +static bool pedantic; + +#if defined _LIBC || defined STDC_HEADERS || defined USG +# include <string.h> +# define flood memset +#else +static void flood (void *, int, size_t); +static void +flood (void *ptr, int val, size_t size) +{ + char *cp = ptr; + while (size--) + *cp++ = val; +} +#endif + +static enum mcheck_status +checkhdr (const struct hdr *hdr) +{ + enum mcheck_status status; + bool mcheck_used = __is_malloc_debug_enabled (MALLOC_MCHECK_HOOK); + + if (!mcheck_used) + /* Maybe the mcheck used is disabled? This happens when we find + an error and report it. */ + return MCHECK_OK; + + switch (hdr->magic ^ ((uintptr_t) hdr->prev + (uintptr_t) hdr->next)) + { + default: + status = MCHECK_HEAD; + break; + case MAGICFREE: + status = MCHECK_FREE; + break; + case MAGICWORD: + if (((char *) &hdr[1])[hdr->size] != MAGICBYTE) + status = MCHECK_TAIL; + else if ((hdr->magic2 ^ (uintptr_t) hdr->block) != MAGICWORD) + status = MCHECK_HEAD; + else + status = MCHECK_OK; + break; + } + if (status != MCHECK_OK) + { + mcheck_used = 0; + (*abortfunc) (status); + mcheck_used = 1; + } + return status; +} + +static enum mcheck_status +__mcheck_checkptr (const void *ptr) +{ + if (!__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK)) + return MCHECK_DISABLED; + + if (ptr != NULL) + return checkhdr (((struct hdr *) ptr) - 1); + + /* Walk through all the active blocks and test whether they were tampered + with. */ + struct hdr *runp = root; + + /* Temporarily turn off the checks. */ + pedantic = false; + + while (runp != NULL) + { + (void) checkhdr (runp); + + runp = runp->next; + } + + /* Turn checks on again. */ + pedantic = true; + + return MCHECK_OK; +} + +static void +unlink_blk (struct hdr *ptr) +{ + if (ptr->next != NULL) + { + ptr->next->prev = ptr->prev; + ptr->next->magic = MAGICWORD ^ ((uintptr_t) ptr->next->prev + + (uintptr_t) ptr->next->next); + } + if (ptr->prev != NULL) + { + ptr->prev->next = ptr->next; + ptr->prev->magic = MAGICWORD ^ ((uintptr_t) ptr->prev->prev + + (uintptr_t) ptr->prev->next); + } + else + root = ptr->next; +} + +static void +link_blk (struct hdr *hdr) +{ + hdr->prev = NULL; + hdr->next = root; + root = hdr; + hdr->magic = MAGICWORD ^ (uintptr_t) hdr->next; + + /* And the next block. */ + if (hdr->next != NULL) + { + hdr->next->prev = hdr; + hdr->next->magic = MAGICWORD ^ ((uintptr_t) hdr + + (uintptr_t) hdr->next->next); + } +} + +static void * +free_mcheck (void *ptr) +{ + if (pedantic) + __mcheck_checkptr (NULL); + if (ptr) + { + struct hdr *hdr = ((struct hdr *) ptr) - 1; + checkhdr (hdr); + hdr->magic = MAGICFREE; + hdr->magic2 = MAGICFREE; + unlink_blk (hdr); + hdr->prev = hdr->next = NULL; + flood (ptr, FREEFLOOD, hdr->size); + ptr = hdr->block; + } + return ptr; +} + +static bool +malloc_mcheck_before (size_t *sizep, void **victimp) +{ + size_t size = *sizep; + + if (pedantic) + __mcheck_checkptr (NULL); + + if (size > ~((size_t) 0) - (sizeof (struct hdr) + 1)) + { + __set_errno (ENOMEM); + *victimp = NULL; + return true; + } + + *sizep = sizeof (struct hdr) + size + 1; + return false; +} + +static void * +malloc_mcheck_after (void *mem, size_t size) +{ + struct hdr *hdr = mem; + + if (hdr == NULL) + return NULL; + + hdr->size = size; + link_blk (hdr); + hdr->block = hdr; + hdr->magic2 = (uintptr_t) hdr ^ MAGICWORD; + ((char *) &hdr[1])[size] = MAGICBYTE; + flood ((void *) (hdr + 1), MALLOCFLOOD, size); + return (void *) (hdr + 1); +} + +static bool +memalign_mcheck_before (size_t alignment, size_t *sizep, void **victimp) +{ + struct hdr *hdr; + size_t slop, size = *sizep; + + /* Punt to malloc to avoid double headers. */ + if (alignment <= MALLOC_ALIGNMENT) + { + *victimp = __debug_malloc (size); + return true; + } + + if (pedantic) + __mcheck_checkptr (NULL); + + slop = (sizeof *hdr + alignment - 1) & - alignment; + + if (size > ~((size_t) 0) - (slop + 1)) + { + __set_errno (ENOMEM); + *victimp = NULL; + return true; + } + + *sizep = slop + size + 1; + return false; +} + +static void * +memalign_mcheck_after (void *block, size_t alignment, size_t size) +{ + if (block == NULL) + return NULL; + + /* This was served by __debug_malloc, so return as is. */ + if (alignment <= MALLOC_ALIGNMENT) + return block; + + size_t slop = (sizeof (struct hdr) + alignment - 1) & - alignment; + struct hdr *hdr = ((struct hdr *) (block + slop)) - 1; + + hdr->size = size; + link_blk (hdr); + hdr->block = (void *) block; + hdr->magic2 = (uintptr_t) block ^ MAGICWORD; + ((char *) &hdr[1])[size] = MAGICBYTE; + flood ((void *) (hdr + 1), MALLOCFLOOD, size); + return (void *) (hdr + 1); +} + +static bool +realloc_mcheck_before (void **ptrp, size_t *sizep, size_t *oldsize, + void **victimp) +{ + size_t size = *sizep; + void *ptr = *ptrp; + + if (ptr == NULL) + { + *victimp = __debug_malloc (size); + *oldsize = 0; + return true; + } + + if (size == 0) + { + __debug_free (ptr); + *victimp = NULL; + return true; + } + + if (size > ~((size_t) 0) - (sizeof (struct hdr) + 1)) + { + __set_errno (ENOMEM); + *victimp = NULL; + *oldsize = 0; + return true; + } + + if (pedantic) + __mcheck_checkptr (NULL); + + struct hdr *hdr; + size_t osize; + + /* Update the oldptr for glibc realloc. */ + *ptrp = hdr = ((struct hdr *) ptr) - 1; + + osize = hdr->size; + + checkhdr (hdr); + unlink_blk (hdr); + if (size < osize) + flood ((char *) ptr + size, FREEFLOOD, osize - size); + + *oldsize = osize; + *sizep = sizeof (struct hdr) + size + 1; + return false; +} + +static void * +realloc_mcheck_after (void *ptr, void *oldptr, size_t size, size_t osize) +{ + struct hdr *hdr = ptr; + + if (hdr == NULL) + return NULL; + + /* Malloc already added the header so don't tamper with it. */ + if (oldptr == NULL) + return ptr; + + hdr->size = size; + link_blk (hdr); + hdr->block = hdr; + hdr->magic2 = (uintptr_t) hdr ^ MAGICWORD; + ((char *) &hdr[1])[size] = MAGICBYTE; + if (size > osize) + flood ((char *) (hdr + 1) + osize, MALLOCFLOOD, size - osize); + return (void *) (hdr + 1); +} + +__attribute__ ((noreturn)) +static void +mabort (enum mcheck_status status) +{ + const char *msg; + switch (status) + { + case MCHECK_OK: + msg = _ ("memory is consistent, library is buggy\n"); + break; + case MCHECK_HEAD: + msg = _ ("memory clobbered before allocated block\n"); + break; + case MCHECK_TAIL: + msg = _ ("memory clobbered past end of allocated block\n"); + break; + case MCHECK_FREE: + msg = _ ("block freed twice\n"); + break; + default: + msg = _ ("bogus mcheck_status, library is buggy\n"); + break; + } +#ifdef _LIBC + __libc_fatal (msg); +#else + fprintf (stderr, "mcheck: %s", msg); + fflush (stderr); + abort (); +#endif +} + +/* Memory barrier so that GCC does not optimize out the argument. */ +#define malloc_opt_barrier(x) \ + ({ __typeof (x) __x = x; __asm ("" : "+m" (__x)); __x; }) + +static int +__mcheck_initialize (void (*func) (enum mcheck_status), bool in_pedantic) +{ + abortfunc = (func != NULL) ? func : &mabort; + + switch (debug_initialized) + { + case -1: + /* Called before the first malloc was called. */ + __debug_free (__debug_malloc (0)); + /* FALLTHROUGH */ + case 0: + /* Called through the initializer hook. */ + __malloc_debug_enable (MALLOC_MCHECK_HOOK); + break; + case 1: + default: + /* Malloc was already called. Fail. */ + return -1; + } + + pedantic = in_pedantic; + return 0; +} diff --git a/malloc/mcheck.c b/malloc/mcheck.c index 1e68cedbf5..74c20ffe25 100644 --- a/malloc/mcheck.c +++ b/malloc/mcheck.c @@ -1,4 +1,4 @@ -/* Standard debugging hooks for `malloc'. +/* The mcheck() interface. Copyright (C) 1990-2021 Free Software Foundation, Inc. This file is part of the GNU C Library. Written May 1989 by Mike Haertel. @@ -17,402 +17,46 @@ License along with the GNU C Library; if not, see <https://www.gnu.org/licenses/>. */ -#ifndef _MALLOC_INTERNAL -# define _MALLOC_INTERNAL -# include <malloc.h> -# include <malloc-size.h> -# include <mcheck.h> -# include <stdint.h> -# include <stdio.h> -# include <libintl.h> -# include <err |
