aboutsummaryrefslogtreecommitdiff
path: root/common/mcheck_core.inc.h
blob: 85a34de2958576cc886c02ef613bbd0bc1ec3550 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * Copyright (C) 2024 Free Software Foundation, Inc.
 * Written by Eugene Uriev, based on glibc 2.0 prototype of Mike Haertel.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 * <https://www.gnu.org/licenses/>
 */

/*
 * TL;DR: this is a porting of glibc mcheck into U-Boot
 *
 * This file contains no entities for external linkage.
 * So mcheck protection may be used in parallel, e.g. for "malloc_simple(..)" and "malloc(..)".
 * To do so, the file should be shared/include twice, - without linkage conflicts.
 * I.e. "core"-part is shared as a source, but not as a binary.
 * Maybe some optimization here make sense, to engage more binary sharing too.
 * But, currently I strive to keep it as simple, as possible.
 * And this, programmers'-only, mode don't pretend to be main.
 *
 * This library is aware of U-Boot specific. It's also aware of ARM alignment concerns.
 * Unlike glibc-clients, U-Boot has limited malloc-usage, and only one thread.
 * So it's better to make the protection heavier.
 * Thus overflow canary here is greater, than glibc's one. Underflow canary is bigger too.
 * U-Boot also allows to use fixed-size heap-registry, instead of double-linked list in glibc.
 *
 * Heavy canary allows to catch not only memset(..)-errors,
 * but overflow/underflow of struct-array access:
 *	{
 *		struct mystruct* p = malloc(sizeof(struct mystruct) * N);
 *		p[-1].field1 = 0;
 *		p[N].field2 = 13;
 *	}
 * TODO: In order to guarantee full coverage of that kind of errors, a user can add variable-size
 *       canaries here. So pre- and post-canary with size >= reqested_size, could be provided
 *       (with the price of 3x heap-usage). Therefore, it would catch 100% of changes beyond
 *       an array, for index(+1/-1) errors.
 *
 * U-Boot is a BL, not an OS with a lib. Activity of the library is set not in runtime,
 * rather in compile-time, by MCHECK_HEAP_PROTECTION macro. That guarantees that
 * we haven't missed first malloc.
 */
#ifndef _MCHECKCORE_INC_H
#define _MCHECKCORE_INC_H      1
#include "mcheck.h"

#if defined(MCHECK_HEAP_PROTECTION)
#define mcheck_flood memset

// these are from /dev/random:
#define MAGICWORD	0x99ccf430fa562a05ULL
#define MAGICFREE	0x4875e63c0c6fc08eULL
#define MAGICTAIL	0x918dbcd7df78dcd6ULL
#define MALLOCFLOOD	((char)0xb6)
#define FREEFLOOD	((char)0xf5)
#define PADDINGFLOOD	((char)0x58)

// my normal run demands 4427-6449 chunks:
#define REGISTRY_SZ	6608
#define CANARY_DEPTH	2

// avoid problems with BSS at early stage:
static char mcheck_pedantic_flag __section(".data") = 0;
static void *mcheck_registry[REGISTRY_SZ] __section(".data") = {0};

typedef unsigned long long mcheck_elem;
typedef struct {
	mcheck_elem elems[CANARY_DEPTH];
} mcheck_canary;
struct mcheck_hdr {
	size_t size; /* Exact size requested by user.  */
	size_t aln_skip; /* Ignored bytes, before the mcheck_hdr, to fulfill alignment */
	mcheck_canary canary; /* Magic number to check header integrity.  */
};

static void mcheck_default_abort(enum mcheck_status status)
{
	const char *msg;

	switch (status) {
	case MCHECK_OK:
		msg = "memory is consistent, library is buggy\n";
		break;
	case MCHECK_HEAD:
		msg = "memory clobbered before allocated block\n";
		break;
	case MCHECK_TAIL:
		msg = "memory clobbered past end of allocated block\n";
		break;
	case MCHECK_FREE:
		msg = "block freed twice\n";
		break;
	default:
		msg = "bogus mcheck_status, library is buggy\n";
		break;
	}
	printf("\n\nmcheck: %s!!!\n\n", msg);
}

static mcheck_abortfunc_t mcheck_abortfunc = &mcheck_default_abort;

static inline size_t allign_size_up(size_t sz, size_t grain)
{
	return (sz + grain - 1) & ~(grain - 1);
}

#define mcheck_allign_customer_size(SZ) allign_size_up(SZ, sizeof(mcheck_elem))
#define mcheck_evaluate_memalign_prefix_size(ALIGN) allign_size_up(sizeof(struct mcheck_hdr), ALIGN)

static enum mcheck_status mcheck_OnNok(enum mcheck_status status)
{
	(*mcheck_abortfunc)(status);
	return status;
}

static enum mcheck_status mcheck_checkhdr(const struct mcheck_hdr *hdr)
{
	int i;

	for (i = 0; i < CANARY_DEPTH; ++i)
		if (hdr->canary.elems[i] == MAGICFREE)
			return mcheck_OnNok(MCHECK_FREE);

	for (i = 0; i < CANARY_DEPTH; ++i)
		if (hdr->canary.elems[i] != MAGICWORD)
			return mcheck_OnNok(MCHECK_HEAD);

	const size_t payload_size = hdr->size;
	const size_t payload_size_aligned = mcheck_allign_customer_size(payload_size);
	const size_t padd_size = payload_size_aligned - hdr->size;

	const char *payload = (const char *)&hdr[1];

	for (i = 0; i < padd_size; ++i)
		if (payload[payload_size + i] != PADDINGFLOOD)
			return mcheck_OnNok(MCHECK_TAIL);

	const mcheck_canary *tail = (const mcheck_canary *)&payload[payload_size_aligned];

	for (i = 0; i < CANARY_DEPTH; ++i)
		if (tail->elems[i] != MAGICTAIL)
			return mcheck_OnNok(MCHECK_TAIL);
	return MCHECK_OK;
}

enum { KEEP_CONTENT = 0, CLEAN_CONTENT, ANY_ALIGNMENT = 1 };
static void *mcheck_free_helper(void *ptr, int clean_content)
{
	if (!ptr)
		return ptr;

	struct mcheck_hdr *hdr = &((struct mcheck_hdr *)ptr)[-1];
	int i;

	mcheck_checkhdr(hdr);
	for (i = 0; i < CANARY_DEPTH; ++i)
		hdr->canary.elems[i] = MAGICFREE;

	if (clean_content)
		mcheck_flood(ptr, FREEFLOOD, mcheck_allign_customer_size(hdr->size));

	for (i = 0; i < REGISTRY_SZ; ++i)
		if (mcheck_registry[i] == hdr) {
			mcheck_registry[i] = 0;
			break;
		}

	return (char *)hdr - hdr->aln_skip;
}

static void *mcheck_free_prehook(void *ptr) { return mcheck_free_helper(ptr, CLEAN_CONTENT); }
static void *mcheck_reallocfree_prehook(void *ptr) { return mcheck_free_helper(ptr, KEEP_CONTENT); }

static size_t mcheck_alloc_prehook(size_t sz)
{
	sz = mcheck_allign_customer_size(sz);
	return sizeof(struct mcheck_hdr) + sz + sizeof(mcheck_canary);
}

static void *mcheck_allocated_helper(void *altoghether_ptr, size_t customer_sz,
				     size_t alignment, int clean_content)
{
	const size_t slop = alignment ?
		mcheck_evaluate_memalign_prefix_size(alignment) - sizeof(struct mcheck_hdr) : 0;
	struct mcheck_hdr *hdr = (struct mcheck_hdr *)((char *)altoghether_ptr + slop);
	int i;

	hdr->size = customer_sz;
	hdr->aln_skip = slop;
	for (i = 0; i < CANARY_DEPTH; ++i)
		hdr->canary.elems[i] = MAGICWORD;

	char *payload = (char *)&hdr[1];

	if (clean_content)
		mcheck_flood(payload, MALLOCFLOOD, customer_sz);

	const size_t customer_size_aligned = mcheck_allign_customer_size(customer_sz);

	mcheck_flood(payload + customer_sz, PADDINGFLOOD, customer_size_aligned - customer_sz);

	mcheck_canary *tail = (mcheck_canary *)&payload[customer_size_aligned];

	for (i = 0; i < CANARY_DEPTH; ++i)
		tail->elems[i] = MAGICTAIL;

	for (i = 0; i < REGISTRY_SZ; ++i)
		if (!mcheck_registry[i]) {
			mcheck_registry[i] = hdr;
			return payload; // normal end
		}

	static char *overflow_msg = "\n\n\nERROR: mcheck registry overflow, pedantic check would be incomplete!!\n\n\n\n";

	printf("%s", overflow_msg);
	overflow_msg = "(mcheck registry full)";
	return payload;
}

static void *mcheck_alloc_posthook(void *altoghether_ptr, size_t customer_sz)
{
	return mcheck_allocated_helper(altoghether_ptr, customer_sz, ANY_ALIGNMENT, CLEAN_CONTENT);
}

static void *mcheck_alloc_noclean_posthook(void *altoghether_ptr, size_t customer_sz)
{
	return mcheck_allocated_helper(altoghether_ptr, customer_sz, ANY_ALIGNMENT, KEEP_CONTENT);
}

static size_t mcheck_memalign_prehook(size_t alig, size_t sz)
{
	return mcheck_evaluate_memalign_prefix_size(alig) + sz + sizeof(mcheck_canary);
}

static void *mcheck_memalign_posthook(size_t alignment, void *altoghether_ptr, size_t customer_sz)
{
	return mcheck_allocated_helper(altoghether_ptr, customer_sz, alignment, CLEAN_CONTENT);
}

static enum mcheck_status mcheck_mprobe(void *ptr)
{
	struct mcheck_hdr *hdr = &((struct mcheck_hdr *)ptr)[-1];

	return mcheck_checkhdr(hdr);
}

static void mcheck_pedantic_check(void)
{
	int i;

	for (i = 0; i < REGISTRY_SZ; ++i)
		if (mcheck_registry[i])
			mcheck_checkhdr(mcheck_registry[i]);
}

static void mcheck_pedantic_prehook(void)
{
	if (mcheck_pedantic_flag)
		mcheck_pedantic_check();
}

static void mcheck_initialize(mcheck_abortfunc_t new_func, char pedantic_flag)
{
	mcheck_abortfunc = (new_func) ? new_func : &mcheck_default_abort;
	mcheck_pedantic_flag = pedantic_flag;
}

#endif
#endif