1 /* mcheck debugging hooks for malloc.
2    Copyright (C) 1990-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <malloc-internal.h>
20 #include <mcheck.h>
21 #include <libintl.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 
25 /* Arbitrary magical numbers.  */
26 #define MAGICWORD       0xfedabeeb
27 #define MAGICFREE       0xd8675309
28 #define MAGICBYTE       ((char) 0xd7)
29 #define MALLOCFLOOD     ((char) 0x93)
30 #define FREEFLOOD       ((char) 0x95)
31 
32 /* Function to call when something awful happens.  */
33 static void (*abortfunc) (enum mcheck_status);
34 
35 struct hdr
36 {
37   size_t size;                  /* Exact size requested by user.  */
38   unsigned long int magic;      /* Magic number to check header integrity.  */
39   struct hdr *prev;
40   struct hdr *next;
41   void *block;                  /* Real block allocated, for memalign.  */
42   unsigned long int magic2;     /* Extra, keeps us doubleword aligned.  */
43 } __attribute__ ((aligned (MALLOC_ALIGNMENT)));
44 
45 /* This is the beginning of the list of all memory blocks allocated.
46    It is only constructed if the pedantic testing is requested.  */
47 static struct hdr *root;
48 
49 /* Nonzero if pedentic checking of all blocks is requested.  */
50 static bool pedantic;
51 
52 #if defined _LIBC || defined STDC_HEADERS || defined USG
53 # include <string.h>
54 # define flood memset
55 #else
56 static void flood (void *, int, size_t);
57 static void
flood(void * ptr,int val,size_t size)58 flood (void *ptr, int val, size_t size)
59 {
60   char *cp = ptr;
61   while (size--)
62     *cp++ = val;
63 }
64 #endif
65 
66 static enum mcheck_status
checkhdr(const struct hdr * hdr)67 checkhdr (const struct hdr *hdr)
68 {
69   enum mcheck_status status;
70   bool mcheck_used = __is_malloc_debug_enabled (MALLOC_MCHECK_HOOK);
71 
72   if (!mcheck_used)
73     /* Maybe the mcheck used is disabled?  This happens when we find
74        an error and report it.  */
75     return MCHECK_OK;
76 
77   switch (hdr->magic ^ ((uintptr_t) hdr->prev + (uintptr_t) hdr->next))
78     {
79     default:
80       status = MCHECK_HEAD;
81       break;
82     case MAGICFREE:
83       status = MCHECK_FREE;
84       break;
85     case MAGICWORD:
86       if (((char *) &hdr[1])[hdr->size] != MAGICBYTE)
87 	status = MCHECK_TAIL;
88       else if ((hdr->magic2 ^ (uintptr_t) hdr->block) != MAGICWORD)
89 	status = MCHECK_HEAD;
90       else
91 	status = MCHECK_OK;
92       break;
93     }
94   if (status != MCHECK_OK)
95     {
96       mcheck_used = 0;
97       (*abortfunc) (status);
98       mcheck_used = 1;
99     }
100   return status;
101 }
102 
103 static enum mcheck_status
__mcheck_checkptr(const void * ptr)104 __mcheck_checkptr (const void *ptr)
105 {
106   if (!__is_malloc_debug_enabled (MALLOC_MCHECK_HOOK))
107       return MCHECK_DISABLED;
108 
109   if (ptr != NULL)
110     return checkhdr (((struct hdr *) ptr) - 1);
111 
112   /* Walk through all the active blocks and test whether they were tampered
113      with.  */
114   struct hdr *runp = root;
115 
116   /* Temporarily turn off the checks.  */
117   pedantic = false;
118 
119   while (runp != NULL)
120     {
121       (void) checkhdr (runp);
122 
123       runp = runp->next;
124     }
125 
126   /* Turn checks on again.  */
127   pedantic = true;
128 
129   return MCHECK_OK;
130 }
131 
132 static void
unlink_blk(struct hdr * ptr)133 unlink_blk (struct hdr *ptr)
134 {
135   if (ptr->next != NULL)
136     {
137       ptr->next->prev = ptr->prev;
138       ptr->next->magic = MAGICWORD ^ ((uintptr_t) ptr->next->prev
139                                       + (uintptr_t) ptr->next->next);
140     }
141   if (ptr->prev != NULL)
142     {
143       ptr->prev->next = ptr->next;
144       ptr->prev->magic = MAGICWORD ^ ((uintptr_t) ptr->prev->prev
145                                       + (uintptr_t) ptr->prev->next);
146     }
147   else
148     root = ptr->next;
149 }
150 
151 static void
link_blk(struct hdr * hdr)152 link_blk (struct hdr *hdr)
153 {
154   hdr->prev = NULL;
155   hdr->next = root;
156   root = hdr;
157   hdr->magic = MAGICWORD ^ (uintptr_t) hdr->next;
158 
159   /* And the next block.  */
160   if (hdr->next != NULL)
161     {
162       hdr->next->prev = hdr;
163       hdr->next->magic = MAGICWORD ^ ((uintptr_t) hdr
164                                       + (uintptr_t) hdr->next->next);
165     }
166 }
167 
168 static void *
free_mcheck(void * ptr)169 free_mcheck (void *ptr)
170 {
171   if (pedantic)
172     __mcheck_checkptr (NULL);
173   if (ptr)
174     {
175       struct hdr *hdr = ((struct hdr *) ptr) - 1;
176       checkhdr (hdr);
177       hdr->magic = MAGICFREE;
178       hdr->magic2 = MAGICFREE;
179       unlink_blk (hdr);
180       hdr->prev = hdr->next = NULL;
181       flood (ptr, FREEFLOOD, hdr->size);
182       ptr = hdr->block;
183     }
184   return ptr;
185 }
186 
187 static bool
malloc_mcheck_before(size_t * sizep,void ** victimp)188 malloc_mcheck_before (size_t *sizep, void **victimp)
189 {
190   size_t size = *sizep;
191 
192   if (pedantic)
193     __mcheck_checkptr (NULL);
194 
195   if (size > ~((size_t) 0) - (sizeof (struct hdr) + 1))
196     {
197       __set_errno (ENOMEM);
198       *victimp = NULL;
199       return true;
200     }
201 
202   *sizep = sizeof (struct hdr) + size + 1;
203   return false;
204 }
205 
206 static void *
malloc_mcheck_after(void * mem,size_t size)207 malloc_mcheck_after (void *mem, size_t size)
208 {
209   struct hdr *hdr = mem;
210 
211   if (hdr == NULL)
212     return NULL;
213 
214   hdr->size = size;
215   link_blk (hdr);
216   hdr->block = hdr;
217   hdr->magic2 = (uintptr_t) hdr ^ MAGICWORD;
218   ((char *) &hdr[1])[size] = MAGICBYTE;
219   flood ((void *) (hdr + 1), MALLOCFLOOD, size);
220   return (void *) (hdr + 1);
221 }
222 
223 static bool
memalign_mcheck_before(size_t alignment,size_t * sizep,void ** victimp)224 memalign_mcheck_before (size_t alignment, size_t *sizep, void **victimp)
225 {
226   struct hdr *hdr;
227   size_t slop, size = *sizep;
228 
229   /* Punt to malloc to avoid double headers.  */
230   if (alignment <= MALLOC_ALIGNMENT)
231     {
232       *victimp = __debug_malloc (size);
233       return true;
234     }
235 
236   if (pedantic)
237     __mcheck_checkptr (NULL);
238 
239   slop = (sizeof *hdr + alignment - 1) & - alignment;
240 
241   if (size > ~((size_t) 0) - (slop + 1))
242     {
243       __set_errno (ENOMEM);
244       *victimp = NULL;
245       return true;
246     }
247 
248   *sizep = slop + size + 1;
249   return false;
250 }
251 
252 static void *
memalign_mcheck_after(void * block,size_t alignment,size_t size)253 memalign_mcheck_after (void *block, size_t alignment, size_t size)
254 {
255   if (block == NULL)
256     return NULL;
257 
258   /* This was served by __debug_malloc, so return as is.  */
259   if (alignment <= MALLOC_ALIGNMENT)
260     return block;
261 
262   size_t slop = (sizeof (struct hdr) + alignment - 1) & - alignment;
263   struct hdr *hdr = ((struct hdr *) (block + slop)) - 1;
264 
265   hdr->size = size;
266   link_blk (hdr);
267   hdr->block = (void *) block;
268   hdr->magic2 = (uintptr_t) block ^ MAGICWORD;
269   ((char *) &hdr[1])[size] = MAGICBYTE;
270   flood ((void *) (hdr + 1), MALLOCFLOOD, size);
271   return (void *) (hdr + 1);
272 }
273 
274 static bool
realloc_mcheck_before(void ** ptrp,size_t * sizep,size_t * oldsize,void ** victimp)275 realloc_mcheck_before (void **ptrp, size_t *sizep, size_t *oldsize,
276 		       void **victimp)
277 {
278   size_t size = *sizep;
279   void *ptr = *ptrp;
280 
281   if (ptr == NULL)
282     {
283       *victimp = __debug_malloc (size);
284       *oldsize = 0;
285       return true;
286     }
287 
288   if (size == 0)
289     {
290       __debug_free (ptr);
291       *victimp = NULL;
292       return true;
293     }
294 
295   if (size > ~((size_t) 0) - (sizeof (struct hdr) + 1))
296     {
297       __set_errno (ENOMEM);
298       *victimp = NULL;
299       *oldsize = 0;
300       return true;
301     }
302 
303   if (pedantic)
304     __mcheck_checkptr (NULL);
305 
306   struct hdr *hdr;
307   size_t osize;
308 
309   /* Update the oldptr for glibc realloc.  */
310   *ptrp = hdr = ((struct hdr *) ptr) - 1;
311 
312   osize = hdr->size;
313 
314   checkhdr (hdr);
315   unlink_blk (hdr);
316   if (size < osize)
317     flood ((char *) ptr + size, FREEFLOOD, osize - size);
318 
319   *oldsize = osize;
320   *sizep = sizeof (struct hdr) + size + 1;
321   return false;
322 }
323 
324 static void *
realloc_mcheck_after(void * ptr,void * oldptr,size_t size,size_t osize)325 realloc_mcheck_after (void *ptr, void *oldptr, size_t size, size_t osize)
326 {
327   struct hdr *hdr = ptr;
328 
329   if (hdr == NULL)
330     return NULL;
331 
332   /* Malloc already added the header so don't tamper with it.  */
333   if (oldptr == NULL)
334     return ptr;
335 
336   hdr->size = size;
337   link_blk (hdr);
338   hdr->block = hdr;
339   hdr->magic2 = (uintptr_t) hdr ^ MAGICWORD;
340   ((char *) &hdr[1])[size] = MAGICBYTE;
341   if (size > osize)
342     flood ((char *) (hdr + 1) + osize, MALLOCFLOOD, size - osize);
343   return (void *) (hdr + 1);
344 }
345 
346 __attribute__ ((noreturn))
347 static void
mabort(enum mcheck_status status)348 mabort (enum mcheck_status status)
349 {
350   const char *msg;
351   switch (status)
352     {
353     case MCHECK_OK:
354       msg = _ ("memory is consistent, library is buggy\n");
355       break;
356     case MCHECK_HEAD:
357       msg = _ ("memory clobbered before allocated block\n");
358       break;
359     case MCHECK_TAIL:
360       msg = _ ("memory clobbered past end of allocated block\n");
361       break;
362     case MCHECK_FREE:
363       msg = _ ("block freed twice\n");
364       break;
365     default:
366       msg = _ ("bogus mcheck_status, library is buggy\n");
367       break;
368     }
369 #ifdef _LIBC
370   __libc_fatal (msg);
371 #else
372   fprintf (stderr, "mcheck: %s", msg);
373   fflush (stderr);
374   abort ();
375 #endif
376 }
377 
378 /* Memory barrier so that GCC does not optimize out the argument.  */
379 #define malloc_opt_barrier(x) \
380   ({ __typeof (x) __x = x; __asm ("" : "+m" (__x)); __x; })
381 
382 static int
__mcheck_initialize(void (* func)(enum mcheck_status),bool in_pedantic)383 __mcheck_initialize (void (*func) (enum mcheck_status), bool in_pedantic)
384 {
385   abortfunc = (func != NULL) ? func : &mabort;
386 
387   switch (debug_initialized)
388     {
389     case -1:
390       /* Called before the first malloc was called.  */
391       __debug_free (__debug_malloc (0));
392       /* FALLTHROUGH */
393     case 0:
394       /* Called through the initializer hook.  */
395       __malloc_debug_enable (MALLOC_MCHECK_HOOK);
396       break;
397     case 1:
398     default:
399       /* Malloc was already called.  Fail.  */
400       return -1;
401     }
402 
403   pedantic = in_pedantic;
404   return 0;
405 }
406 
407 static int
mcheck_usable_size(struct hdr * h)408 mcheck_usable_size (struct hdr *h)
409 {
410   return (h - 1)->size;
411 }
412