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