1 /* 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.
33  * U-Boot also allows to use fixed-size heap-registry, instead of double-linked list in glibc.
34  *
35  * 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  */
51 
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  */
58 #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 
73 // my normal run demands 4427-6449 chunks:
74 #define REGISTRY_SZ	6608
75 #define CANARY_DEPTH	2
76 
77 // avoid problems with BSS at early stage:
78 static char mcheck_pedantic_flag __section(".data") = 0;
79 static void *mcheck_registry[REGISTRY_SZ] __section(".data") = {0};
80 static size_t mcheck_chunk_count __section(".data") = 0;
81 static size_t mcheck_chunk_count_max __section(".data") = 0;
82 
83 typedef unsigned long long mcheck_elem;
84 typedef struct {
85 	mcheck_elem elems[CANARY_DEPTH];
86 } mcheck_canary;
87 struct mcheck_hdr {
88 	size_t size; /* Exact size requested by user.  */
89 	size_t aln_skip; /* Ignored bytes, before the mcheck_hdr, to fulfill alignment */
90 	mcheck_canary canary; /* Magic number to check header integrity.  */
91 };
92 
mcheck_default_abort(enum mcheck_status status,const void * p)93 static void mcheck_default_abort(enum mcheck_status status, const void *p)
94 {
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 	}
114 	printf("\n\nmcheck: %p:%s!!! [%zu]\n\n", p, msg, mcheck_chunk_count_max);
115 }
116 
117 static mcheck_abortfunc_t mcheck_abortfunc = &mcheck_default_abort;
118 
allign_size_up(size_t sz,size_t grain)119 static 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))
125 #define mcheck_evaluate_memalign_prefix_size(ALIGN) allign_size_up(sizeof(struct mcheck_hdr), ALIGN)
126 
mcheck_OnNok(enum mcheck_status status,const void * p)127 static enum mcheck_status mcheck_OnNok(enum mcheck_status status, const void *p)
128 {
129 	(*mcheck_abortfunc)(status, p);
130 	return status;
131 }
132 
mcheck_checkhdr(const struct mcheck_hdr * hdr)133 static 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)
139 			return mcheck_OnNok(MCHECK_FREE, hdr + 1);
140 
141 	for (i = 0; i < CANARY_DEPTH; ++i)
142 		if (hdr->canary.elems[i] != MAGICWORD)
143 			return mcheck_OnNok(MCHECK_HEAD, hdr + 1);
144 
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)
153 			return mcheck_OnNok(MCHECK_TAIL, hdr + 1);
154 
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)
159 			return mcheck_OnNok(MCHECK_TAIL, hdr + 1);
160 	return MCHECK_OK;
161 }
162 
163 enum { KEEP_CONTENT = 0, CLEAN_CONTENT, ANY_ALIGNMENT = 1 };
mcheck_free_helper(void * ptr,int clean_content)164 static 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));
178 
179 	for (i = 0; i < REGISTRY_SZ; ++i)
180 		if (mcheck_registry[i] == hdr) {
181 			mcheck_registry[i] = 0;
182 			break;
183 		}
184 
185 	--mcheck_chunk_count;
186 	return (char *)hdr - hdr->aln_skip;
187 }
188 
mcheck_free_prehook(void * ptr)189 static void *mcheck_free_prehook(void *ptr) { return mcheck_free_helper(ptr, CLEAN_CONTENT); }
mcheck_reallocfree_prehook(void * ptr)190 static void *mcheck_reallocfree_prehook(void *ptr) { return mcheck_free_helper(ptr, KEEP_CONTENT); }
191 
mcheck_alloc_prehook(size_t sz)192 static 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 
mcheck_allocated_helper(void * altoghether_ptr,size_t customer_sz,size_t alignment,int clean_content)198 static void *mcheck_allocated_helper(void *altoghether_ptr, size_t customer_sz,
199 				     size_t alignment, int clean_content)
200 {
201 	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);
204 	int i;
205 
206 	hdr->size = customer_sz;
207 	hdr->aln_skip = slop;
208 	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;
224 
225 	++mcheck_chunk_count;
226 	if (mcheck_chunk_count > mcheck_chunk_count_max)
227 		mcheck_chunk_count_max = mcheck_chunk_count;
228 
229 	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)";
239 	return payload;
240 }
241 
mcheck_alloc_posthook(void * altoghether_ptr,size_t customer_sz)242 static 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 
mcheck_alloc_noclean_posthook(void * altoghether_ptr,size_t customer_sz)247 static 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 
mcheck_memalign_prehook(size_t alig,size_t sz)252 static 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 
mcheck_memalign_posthook(size_t alignment,void * altoghether_ptr,size_t customer_sz)257 static 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 
mcheck_mprobe(void * ptr)262 static 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 
mcheck_pedantic_check(void)269 static 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 
mcheck_pedantic_prehook(void)278 static void mcheck_pedantic_prehook(void)
279 {
280 	if (mcheck_pedantic_flag)
281 		mcheck_pedantic_check();
282 }
283 
mcheck_initialize(mcheck_abortfunc_t new_func,char pedantic_flag)284 static void mcheck_initialize(mcheck_abortfunc_t new_func, char pedantic_flag)
285 {
286 	mcheck_abortfunc = (new_func) ? new_func : &mcheck_default_abort;
287 	mcheck_pedantic_flag = pedantic_flag;
288 }
289 
mcheck_on_ramrelocation(size_t offset)290 void 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 		}
300 
301 	mcheck_chunk_count = 0;
302 }
303 #endif
304 #endif
305