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. */
33static void (*abortfunc) (enum mcheck_status);
34
35struct 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. */
47static struct hdr *root;
48
49/* Nonzero if pedentic checking of all blocks is requested. */
50static bool pedantic;
51
52#if defined _LIBC || defined STDC_HEADERS || defined USG
53# include <string.h>
54# define flood memset
55#else
56static void flood (void *, int, size_t);
57static void
58flood (void *ptr, int val, size_t size)
59{
60 char *cp = ptr;
61 while (size--)
62 *cp++ = val;
63}
64#endif
65
66static enum mcheck_status
67checkhdr (const struct hdr *hdr)
68{
69 enum mcheck_status status;
70 bool mcheck_used = __is_malloc_debug_enabled (flag: 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
103static enum mcheck_status
104__mcheck_checkptr (const void *ptr)
105{
106 if (!__is_malloc_debug_enabled (flag: MALLOC_MCHECK_HOOK))
107 return MCHECK_DISABLED;
108
109 if (ptr != NULL)
110 return checkhdr (hdr: ((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 (hdr: runp);
122
123 runp = runp->next;
124 }
125
126 /* Turn checks on again. */
127 pedantic = true;
128
129 return MCHECK_OK;
130}
131
132static void
133unlink_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
151static void
152link_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
168static void *
169free_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 (ptr: hdr);
180 hdr->prev = hdr->next = NULL;
181 flood (s: ptr, FREEFLOOD, n: hdr->size);
182 ptr = hdr->block;
183 }
184 return ptr;
185}
186
187static bool
188malloc_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
206static void *
207malloc_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 (s: (void *) (hdr + 1), MALLOCFLOOD, n: size);
220 return (void *) (hdr + 1);
221}
222
223static bool
224memalign_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
252static void *
253memalign_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 (s: (void *) (hdr + 1), MALLOCFLOOD, n: size);
271 return (void *) (hdr + 1);
272}
273
274static bool
275realloc_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 (ptr: hdr);
316 if (size < osize)
317 flood (s: (char *) ptr + size, FREEFLOOD, n: osize - size);
318
319 *oldsize = osize;
320 *sizep = sizeof (struct hdr) + size + 1;
321 return false;
322}
323
324static void *
325realloc_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 (s: (char *) (hdr + 1) + osize, MALLOCFLOOD, n: size - osize);
343 return (void *) (hdr + 1);
344}
345
346__attribute__ ((noreturn))
347static void
348mabort (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 (message: 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
382static int
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 (flag: 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
407static int
408mcheck_usable_size (struct hdr *h)
409{
410 return (h - 1)->size;
411}
412

source code of glibc/malloc/mcheck-impl.c