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