blob: 6902140992287c4abc08ff2b7d8e1464ed131608 [file] [log] [blame]
Eugene Urievf5f24c82024-03-31 23:03:21 +03001/* SPDX-License-Identifier: GPL-2.0+ */
2/*
3 * Copyright (C) 2024 Free Software Foundation, Inc.
4 * Written by Eugene Uriev, based on glibc 2.0 prototype of Mike Haertel.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This 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 * Library General Public License for more details.
15 * <https://www.gnu.org/licenses/>
16 */
17
18/*
19 * TL;DR: this is a porting of glibc mcheck into U-Boot
20 *
21 * This file contains no entities for external linkage.
22 * So mcheck protection may be used in parallel, e.g. for "malloc_simple(..)" and "malloc(..)".
23 * To do so, the file should be shared/include twice, - without linkage conflicts.
24 * I.e. "core"-part is shared as a source, but not as a binary.
25 * Maybe some optimization here make sense, to engage more binary sharing too.
26 * But, currently I strive to keep it as simple, as possible.
27 * And this, programmers'-only, mode don't pretend to be main.
28 *
29 * This library is aware of U-Boot specific. It's also aware of ARM alignment concerns.
30 * Unlike glibc-clients, U-Boot has limited malloc-usage, and only one thread.
31 * So it's better to make the protection heavier.
32 * Thus overflow canary here is greater, than glibc's one. Underflow canary is bigger too.
Eugene Uriev2601b612024-03-31 23:03:24 +030033 * U-Boot also allows to use fixed-size heap-registry, instead of double-linked list in glibc.
34 *
Eugene Urievf5f24c82024-03-31 23:03:21 +030035 * Heavy canary allows to catch not only memset(..)-errors,
36 * but overflow/underflow of struct-array access:
37 * {
38 * struct mystruct* p = malloc(sizeof(struct mystruct) * N);
39 * p[-1].field1 = 0;
40 * p[N].field2 = 13;
41 * }
42 * TODO: In order to guarantee full coverage of that kind of errors, a user can add variable-size
43 * canaries here. So pre- and post-canary with size >= reqested_size, could be provided
44 * (with the price of 3x heap-usage). Therefore, it would catch 100% of changes beyond
45 * an array, for index(+1/-1) errors.
46 *
47 * U-Boot is a BL, not an OS with a lib. Activity of the library is set not in runtime,
48 * rather in compile-time, by MCHECK_HEAP_PROTECTION macro. That guarantees that
49 * we haven't missed first malloc.
50 */
Eugene Uriev2d546412024-03-31 23:03:26 +030051
52/*
53 * Testing
54 * This library had been successfully tested for U-Boot @ ARM SoC chip / 64bits.
55 * Proven for both default and pedantic mode: confirms U-Boot to be clean, and catches
56 * intentional/testing corruptions. Working with malloc_trim is not tested.
57 */
Eugene Urievf5f24c82024-03-31 23:03:21 +030058#ifndef _MCHECKCORE_INC_H
59#define _MCHECKCORE_INC_H 1
60#include "mcheck.h"
61
62#if defined(MCHECK_HEAP_PROTECTION)
63#define mcheck_flood memset
64
65// these are from /dev/random:
66#define MAGICWORD 0x99ccf430fa562a05ULL
67#define MAGICFREE 0x4875e63c0c6fc08eULL
68#define MAGICTAIL 0x918dbcd7df78dcd6ULL
69#define MALLOCFLOOD ((char)0xb6)
70#define FREEFLOOD ((char)0xf5)
71#define PADDINGFLOOD ((char)0x58)
72
Eugene Uriev2601b612024-03-31 23:03:24 +030073// my normal run demands 4427-6449 chunks:
74#define REGISTRY_SZ 6608
Eugene Urievf5f24c82024-03-31 23:03:21 +030075#define CANARY_DEPTH 2
76
Eugene Uriev2601b612024-03-31 23:03:24 +030077// avoid problems with BSS at early stage:
78static char mcheck_pedantic_flag __section(".data") = 0;
79static void *mcheck_registry[REGISTRY_SZ] __section(".data") = {0};
Eugene Uriev2d546412024-03-31 23:03:26 +030080static size_t mcheck_chunk_count __section(".data") = 0;
81static size_t mcheck_chunk_count_max __section(".data") = 0;
Eugene Uriev2601b612024-03-31 23:03:24 +030082
Eugene Urievf5f24c82024-03-31 23:03:21 +030083typedef unsigned long long mcheck_elem;
84typedef struct {
85 mcheck_elem elems[CANARY_DEPTH];
86} mcheck_canary;
87struct mcheck_hdr {
88 size_t size; /* Exact size requested by user. */
Eugene Urievc61e3132024-03-31 23:03:23 +030089 size_t aln_skip; /* Ignored bytes, before the mcheck_hdr, to fulfill alignment */
Eugene Urievf5f24c82024-03-31 23:03:21 +030090 mcheck_canary canary; /* Magic number to check header integrity. */
91};
92
Eugene Uriev5d7f16f2024-03-31 23:03:27 +030093static void mcheck_default_abort(enum mcheck_status status, const void *p)
Eugene Urievf5f24c82024-03-31 23:03:21 +030094{
95 const char *msg;
96
97 switch (status) {
98 case MCHECK_OK:
99 msg = "memory is consistent, library is buggy\n";
100 break;
101 case MCHECK_HEAD:
102 msg = "memory clobbered before allocated block\n";
103 break;
104 case MCHECK_TAIL:
105 msg = "memory clobbered past end of allocated block\n";
106 break;
107 case MCHECK_FREE:
108 msg = "block freed twice\n";
109 break;
110 default:
111 msg = "bogus mcheck_status, library is buggy\n";
112 break;
113 }
Eugene Uriev5d7f16f2024-03-31 23:03:27 +0300114 printf("\n\nmcheck: %p:%s!!! [%zu]\n\n", p, msg, mcheck_chunk_count_max);
Eugene Urievf5f24c82024-03-31 23:03:21 +0300115}
116
117static mcheck_abortfunc_t mcheck_abortfunc = &mcheck_default_abort;
118
119static inline size_t allign_size_up(size_t sz, size_t grain)
120{
121 return (sz + grain - 1) & ~(grain - 1);
122}
123
124#define mcheck_allign_customer_size(SZ) allign_size_up(SZ, sizeof(mcheck_elem))
Eugene Urievc61e3132024-03-31 23:03:23 +0300125#define mcheck_evaluate_memalign_prefix_size(ALIGN) allign_size_up(sizeof(struct mcheck_hdr), ALIGN)
Eugene Urievf5f24c82024-03-31 23:03:21 +0300126
Eugene Uriev5d7f16f2024-03-31 23:03:27 +0300127static enum mcheck_status mcheck_OnNok(enum mcheck_status status, const void *p)
Eugene Urievf5f24c82024-03-31 23:03:21 +0300128{
Eugene Uriev5d7f16f2024-03-31 23:03:27 +0300129 (*mcheck_abortfunc)(status, p);
Eugene Urievf5f24c82024-03-31 23:03:21 +0300130 return status;
131}
132
133static enum mcheck_status mcheck_checkhdr(const struct mcheck_hdr *hdr)
134{
135 int i;
136
137 for (i = 0; i < CANARY_DEPTH; ++i)
138 if (hdr->canary.elems[i] == MAGICFREE)
Eugene Uriev5d7f16f2024-03-31 23:03:27 +0300139 return mcheck_OnNok(MCHECK_FREE, hdr + 1);
Eugene Urievf5f24c82024-03-31 23:03:21 +0300140
141 for (i = 0; i < CANARY_DEPTH; ++i)
142 if (hdr->canary.elems[i] != MAGICWORD)
Eugene Uriev5d7f16f2024-03-31 23:03:27 +0300143 return mcheck_OnNok(MCHECK_HEAD, hdr + 1);
Eugene Urievf5f24c82024-03-31 23:03:21 +0300144
145 const size_t payload_size = hdr->size;
146 const size_t payload_size_aligned = mcheck_allign_customer_size(payload_size);
147 const size_t padd_size = payload_size_aligned - hdr->size;
148
149 const char *payload = (const char *)&hdr[1];
150
151 for (i = 0; i < padd_size; ++i)
152 if (payload[payload_size + i] != PADDINGFLOOD)
Eugene Uriev5d7f16f2024-03-31 23:03:27 +0300153 return mcheck_OnNok(MCHECK_TAIL, hdr + 1);
Eugene Urievf5f24c82024-03-31 23:03:21 +0300154
155 const mcheck_canary *tail = (const mcheck_canary *)&payload[payload_size_aligned];
156
157 for (i = 0; i < CANARY_DEPTH; ++i)
158 if (tail->elems[i] != MAGICTAIL)
Eugene Uriev5d7f16f2024-03-31 23:03:27 +0300159 return mcheck_OnNok(MCHECK_TAIL, hdr + 1);
Eugene Urievf5f24c82024-03-31 23:03:21 +0300160 return MCHECK_OK;
161}
162
163enum { KEEP_CONTENT = 0, CLEAN_CONTENT, ANY_ALIGNMENT = 1 };
164static void *mcheck_free_helper(void *ptr, int clean_content)
165{
166 if (!ptr)
167 return ptr;
168
169 struct mcheck_hdr *hdr = &((struct mcheck_hdr *)ptr)[-1];
170 int i;
171
172 mcheck_checkhdr(hdr);
173 for (i = 0; i < CANARY_DEPTH; ++i)
174 hdr->canary.elems[i] = MAGICFREE;
175
176 if (clean_content)
177 mcheck_flood(ptr, FREEFLOOD, mcheck_allign_customer_size(hdr->size));
Eugene Urievc61e3132024-03-31 23:03:23 +0300178
Eugene Uriev2601b612024-03-31 23:03:24 +0300179 for (i = 0; i < REGISTRY_SZ; ++i)
180 if (mcheck_registry[i] == hdr) {
181 mcheck_registry[i] = 0;
182 break;
183 }
184
Eugene Uriev2d546412024-03-31 23:03:26 +0300185 --mcheck_chunk_count;
Eugene Urievc61e3132024-03-31 23:03:23 +0300186 return (char *)hdr - hdr->aln_skip;
Eugene Urievf5f24c82024-03-31 23:03:21 +0300187}
188
189static void *mcheck_free_prehook(void *ptr) { return mcheck_free_helper(ptr, CLEAN_CONTENT); }
190static void *mcheck_reallocfree_prehook(void *ptr) { return mcheck_free_helper(ptr, KEEP_CONTENT); }
191
192static size_t mcheck_alloc_prehook(size_t sz)
193{
194 sz = mcheck_allign_customer_size(sz);
195 return sizeof(struct mcheck_hdr) + sz + sizeof(mcheck_canary);
196}
197
198static void *mcheck_allocated_helper(void *altoghether_ptr, size_t customer_sz,
199 size_t alignment, int clean_content)
200{
Eugene Urievc61e3132024-03-31 23:03:23 +0300201 const size_t slop = alignment ?
202 mcheck_evaluate_memalign_prefix_size(alignment) - sizeof(struct mcheck_hdr) : 0;
203 struct mcheck_hdr *hdr = (struct mcheck_hdr *)((char *)altoghether_ptr + slop);
Eugene Urievf5f24c82024-03-31 23:03:21 +0300204 int i;
205
206 hdr->size = customer_sz;
Eugene Urievc61e3132024-03-31 23:03:23 +0300207 hdr->aln_skip = slop;
Eugene Urievf5f24c82024-03-31 23:03:21 +0300208 for (i = 0; i < CANARY_DEPTH; ++i)
209 hdr->canary.elems[i] = MAGICWORD;
210
211 char *payload = (char *)&hdr[1];
212
213 if (clean_content)
214 mcheck_flood(payload, MALLOCFLOOD, customer_sz);
215
216 const size_t customer_size_aligned = mcheck_allign_customer_size(customer_sz);
217
218 mcheck_flood(payload + customer_sz, PADDINGFLOOD, customer_size_aligned - customer_sz);
219
220 mcheck_canary *tail = (mcheck_canary *)&payload[customer_size_aligned];
221
222 for (i = 0; i < CANARY_DEPTH; ++i)
223 tail->elems[i] = MAGICTAIL;
Eugene Uriev2601b612024-03-31 23:03:24 +0300224
Eugene Uriev2d546412024-03-31 23:03:26 +0300225 ++mcheck_chunk_count;
226 if (mcheck_chunk_count > mcheck_chunk_count_max)
227 mcheck_chunk_count_max = mcheck_chunk_count;
228
Eugene Uriev2601b612024-03-31 23:03:24 +0300229 for (i = 0; i < REGISTRY_SZ; ++i)
230 if (!mcheck_registry[i]) {
231 mcheck_registry[i] = hdr;
232 return payload; // normal end
233 }
234
235 static char *overflow_msg = "\n\n\nERROR: mcheck registry overflow, pedantic check would be incomplete!!\n\n\n\n";
236
237 printf("%s", overflow_msg);
238 overflow_msg = "(mcheck registry full)";
Eugene Urievf5f24c82024-03-31 23:03:21 +0300239 return payload;
240}
241
242static void *mcheck_alloc_posthook(void *altoghether_ptr, size_t customer_sz)
243{
244 return mcheck_allocated_helper(altoghether_ptr, customer_sz, ANY_ALIGNMENT, CLEAN_CONTENT);
245}
246
247static void *mcheck_alloc_noclean_posthook(void *altoghether_ptr, size_t customer_sz)
248{
249 return mcheck_allocated_helper(altoghether_ptr, customer_sz, ANY_ALIGNMENT, KEEP_CONTENT);
250}
251
Eugene Urievc61e3132024-03-31 23:03:23 +0300252static size_t mcheck_memalign_prehook(size_t alig, size_t sz)
253{
254 return mcheck_evaluate_memalign_prefix_size(alig) + sz + sizeof(mcheck_canary);
255}
256
257static void *mcheck_memalign_posthook(size_t alignment, void *altoghether_ptr, size_t customer_sz)
258{
259 return mcheck_allocated_helper(altoghether_ptr, customer_sz, alignment, CLEAN_CONTENT);
260}
261
Eugene Urievf5f24c82024-03-31 23:03:21 +0300262static enum mcheck_status mcheck_mprobe(void *ptr)
263{
264 struct mcheck_hdr *hdr = &((struct mcheck_hdr *)ptr)[-1];
265
266 return mcheck_checkhdr(hdr);
267}
268
Eugene Uriev2601b612024-03-31 23:03:24 +0300269static void mcheck_pedantic_check(void)
270{
271 int i;
272
273 for (i = 0; i < REGISTRY_SZ; ++i)
274 if (mcheck_registry[i])
275 mcheck_checkhdr(mcheck_registry[i]);
276}
277
278static void mcheck_pedantic_prehook(void)
279{
280 if (mcheck_pedantic_flag)
281 mcheck_pedantic_check();
282}
283
Eugene Urievf5f24c82024-03-31 23:03:21 +0300284static void mcheck_initialize(mcheck_abortfunc_t new_func, char pedantic_flag)
285{
286 mcheck_abortfunc = (new_func) ? new_func : &mcheck_default_abort;
Eugene Uriev2601b612024-03-31 23:03:24 +0300287 mcheck_pedantic_flag = pedantic_flag;
Eugene Urievf5f24c82024-03-31 23:03:21 +0300288}
289
Eugene Urieve84d69a2024-03-31 23:03:25 +0300290void mcheck_on_ramrelocation(size_t offset)
291{
292 char *p;
293 int i;
294 // Simple, but inaccurate strategy: drop the pre-reloc heap
295 for (i = 0; i < REGISTRY_SZ; ++i)
296 if ((p = mcheck_registry[i]) != NULL ) {
297 printf("mcheck, WRN: forgetting %p chunk\n", p);
298 mcheck_registry[i] = 0;
299 }
Eugene Uriev2d546412024-03-31 23:03:26 +0300300
301 mcheck_chunk_count = 0;
Eugene Urieve84d69a2024-03-31 23:03:25 +0300302}
Eugene Urievf5f24c82024-03-31 23:03:21 +0300303#endif
304#endif