[PATCH 0/9] mcheck implementation for U-Boot

There was no "mcheck" for U-Boot before.
Since U-Boot has only 1 thread, and normally makes 4000+ - 6000+ mallocs, it's better to use havier canaries to protect heap-chunks. My variant uses 2x8 = 16byte-long protector. And the multiplier could be changed to tune speed/protection tradeoff. This protects not only against memset()-s, but against "near" wild pointers too, and makes more probable to catch "distant" ones.
The core file of the set is included into the C-file, not complied separately in order to enable (potential) coexisting of mcheck-protectors, e.g. malloc_simple(.) and dlmalloc simultaneously.
My tests were for ARM SoC, 64bit, so the patch is aware of alignment.
Primary this patch is for using by developers: to verify, if a change doesn't break the heap integrity. By default the mcheck is disabled and wouldn't affect the boot.
I used pedantic mode, canary=16byte, registry-size=6608. For my system the overhead was 230ms.
I assume, the merge window coming. So I send it now.
Eugene Uriev (9): mcheck: prepare +1 tier for mcheck-wrappers, in dl-*alloc commands mcheck: Use memset/memcpy instead of MALLOC_ZERO/MALLOC_COPY for mcheck. mcheck: introduce essentials of mcheck mcheck: integrate mcheck into dlmalloc.c mcheck: support memalign mcheck: add pedantic mode support mcheck: introduce mcheck_on_ramrelocation(.) mcheck: add stats, add a comment with test results mcheck: let mcheck_abortfunc_t print the pointer
common/board_f.c | 4 + common/dlmalloc.c | 154 ++++++++++++++++---- common/mcheck_core.inc.h | 304 +++++++++++++++++++++++++++++++++++++++ include/mcheck.h | 51 +++++++ 4 files changed, 488 insertions(+), 25 deletions(-) create mode 100644 common/mcheck_core.inc.h create mode 100644 include/mcheck.h

Signed-off-by: Eugene Uriev eugeneuriev@gmail.com
--- The file contradicts U-Boot code style a lot; so I preserve original style, according to recommendations.
common/dlmalloc.c | 66 +++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 25 deletions(-)
diff --git a/common/dlmalloc.c b/common/dlmalloc.c index de3f04225f..40acd3dfa5 100644 --- a/common/dlmalloc.c +++ b/common/dlmalloc.c @@ -32,6 +32,17 @@ void malloc_stats();
DECLARE_GLOBAL_DATA_PTR;
+#ifdef MCHECK_HEAP_PROTECTION + #define STATIC_IF_MCHECK static +#else + #define STATIC_IF_MCHECK + #define mALLOc_impl mALLOc + #define fREe_impl fREe + #define rEALLOc_impl rEALLOc + #define mEMALIGn_impl mEMALIGn + #define cALLOc_impl cALLOc +#endif + /* Emulation of sbrk for WIN32 All code within the ifdef WIN32 is untested by me. @@ -1270,10 +1281,11 @@ static void malloc_extend_top(nb) INTERNAL_SIZE_T nb;
*/
+STATIC_IF_MCHECK #if __STD_C -Void_t* mALLOc(size_t bytes) +Void_t* mALLOc_impl(size_t bytes) #else -Void_t* mALLOc(bytes) size_t bytes; +Void_t* mALLOc_impl(bytes) size_t bytes; #endif { mchunkptr victim; /* inspected/selected chunk */ @@ -1555,10 +1567,11 @@ Void_t* mALLOc(bytes) size_t bytes; */
+STATIC_IF_MCHECK #if __STD_C -void fREe(Void_t* mem) +void fREe_impl(Void_t* mem) #else -void fREe(mem) Void_t* mem; +void fREe_impl(mem) Void_t* mem; #endif { mchunkptr p; /* chunk corresponding to mem */ @@ -1696,10 +1709,11 @@ void fREe(mem) Void_t* mem; */
+STATIC_IF_MCHECK #if __STD_C -Void_t* rEALLOc(Void_t* oldmem, size_t bytes) +Void_t* rEALLOc_impl(Void_t* oldmem, size_t bytes) #else -Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes; +Void_t* rEALLOc_impl(oldmem, bytes) Void_t* oldmem; size_t bytes; #endif { INTERNAL_SIZE_T nb; /* padded request size */ @@ -1725,7 +1739,7 @@ Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes;
#ifdef REALLOC_ZERO_BYTES_FREES if (!bytes) { - fREe(oldmem); + fREe_impl(oldmem); return NULL; } #endif @@ -1733,7 +1747,7 @@ Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes; if ((long)bytes < 0) return NULL;
/* realloc of null is supposed to be same as malloc */ - if (oldmem == NULL) return mALLOc(bytes); + if (oldmem == NULL) return mALLOc_impl(bytes);
#if CONFIG_IS_ENABLED(SYS_MALLOC_F) if (!(gd->flags & GD_FLG_FULL_MALLOC_INIT)) { @@ -1758,7 +1772,7 @@ Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes; /* Note the extra SIZE_SZ overhead. */ if(oldsize - SIZE_SZ >= nb) return oldmem; /* do nothing */ /* Must alloc, copy, free. */ - newmem = mALLOc(bytes); + newmem = mALLOc_impl(bytes); if (!newmem) return NULL; /* propagate failure */ MALLOC_COPY(newmem, oldmem, oldsize - 2*SIZE_SZ); @@ -1869,7 +1883,7 @@ Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes;
/* Must allocate */
- newmem = mALLOc (bytes); + newmem = mALLOc_impl (bytes);
if (newmem == NULL) /* propagate failure */ return NULL; @@ -1886,7 +1900,7 @@ Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes;
/* Otherwise copy, free, and exit */ MALLOC_COPY(newmem, oldmem, oldsize - SIZE_SZ); - fREe(oldmem); + fREe_impl(oldmem); return newmem; } else { VALGRIND_RESIZEINPLACE_BLOCK(oldmem, 0, bytes, SIZE_SZ); @@ -1905,7 +1919,7 @@ Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes; set_inuse_bit_at_offset(remainder, remainder_size); VALGRIND_MALLOCLIKE_BLOCK(chunk2mem(remainder), remainder_size, SIZE_SZ, false); - fREe(chunk2mem(remainder)); /* let free() deal with it */ + fREe_impl(chunk2mem(remainder)); /* let free() deal with it */ } else { @@ -1939,10 +1953,11 @@ Void_t* rEALLOc(oldmem, bytes) Void_t* oldmem; size_t bytes; */
+STATIC_IF_MCHECK #if __STD_C -Void_t* mEMALIGn(size_t alignment, size_t bytes) +Void_t* mEMALIGn_impl(size_t alignment, size_t bytes) #else -Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes; +Void_t* mEMALIGn_impl(alignment, bytes) size_t alignment; size_t bytes; #endif { INTERNAL_SIZE_T nb; /* padded request size */ @@ -1965,7 +1980,7 @@ Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes;
/* If need less alignment than we give anyway, just relay to malloc */
- if (alignment <= MALLOC_ALIGNMENT) return mALLOc(bytes); + if (alignment <= MALLOC_ALIGNMENT) return mALLOc_impl(bytes);
/* Otherwise, ensure that it is at least a minimum chunk size */
@@ -1974,7 +1989,7 @@ Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes; /* Call malloc with worst case padding to hit alignment. */
nb = request2size(bytes); - m = (char*)(mALLOc(nb + alignment + MINSIZE)); + m = (char*)(mALLOc_impl(nb + alignment + MINSIZE));
/* * The attempt to over-allocate (with a size large enough to guarantee the @@ -1990,7 +2005,7 @@ Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes; * Use bytes not nb, since mALLOc internally calls request2size too, and * each call increases the size to allocate, to account for the header. */ - m = (char*)(mALLOc(bytes)); + m = (char*)(mALLOc_impl(bytes)); /* Aligned -> return it */ if ((((unsigned long)(m)) % alignment) == 0) return m; @@ -1998,10 +2013,10 @@ Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes; * Otherwise, try again, requesting enough extra space to be able to * acquire alignment. */ - fREe(m); + fREe_impl(m); /* Add in extra bytes to match misalignment of unexpanded allocation */ extra = alignment - (((unsigned long)(m)) % alignment); - m = (char*)(mALLOc(bytes + extra)); + m = (char*)(mALLOc_impl(bytes + extra)); /* * m might not be the same as before. Validate that the previous value of * extra still works for the current value of m. @@ -2010,7 +2025,7 @@ Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes; if (m) { extra2 = alignment - (((unsigned long)(m)) % alignment); if (extra2 > extra) { - fREe(m); + fREe_impl(m); m = NULL; } } @@ -2060,7 +2075,7 @@ Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes; set_head(newp, newsize | PREV_INUSE); set_inuse_bit_at_offset(newp, newsize); set_head_size(p, leadsize); - fREe(chunk2mem(p)); + fREe_impl(chunk2mem(p)); p = newp; VALGRIND_MALLOCLIKE_BLOCK(chunk2mem(p), bytes, SIZE_SZ, false);
@@ -2078,7 +2093,7 @@ Void_t* mEMALIGn(alignment, bytes) size_t alignment; size_t bytes; set_head_size(p, nb); VALGRIND_MALLOCLIKE_BLOCK(chunk2mem(remainder), remainder_size, SIZE_SZ, false); - fREe(chunk2mem(remainder)); + fREe_impl(chunk2mem(remainder)); }
check_inuse_chunk(p); @@ -2126,10 +2141,11 @@ Void_t* pvALLOc(bytes) size_t bytes;
*/
+STATIC_IF_MCHECK #if __STD_C -Void_t* cALLOc(size_t n, size_t elem_size) +Void_t* cALLOc_impl(size_t n, size_t elem_size) #else -Void_t* cALLOc(n, elem_size) size_t n; size_t elem_size; +Void_t* cALLOc_impl(n, elem_size) size_t n; size_t elem_size; #endif { mchunkptr p; @@ -2145,7 +2161,7 @@ Void_t* cALLOc(n, elem_size) size_t n; size_t elem_size; INTERNAL_SIZE_T oldtopsize = chunksize(top); #endif #endif - Void_t* mem = mALLOc (sz); + Void_t* mem = mALLOc_impl (sz);
if ((long)n < 0) return NULL;

These fast helpers sometimes breach mem-chunk boundaries. Thus they trigger mcheck alarm. Standard ones are accurate though.
Signed-off-by: Eugene Uriev eugeneuriev@gmail.com ---
common/dlmalloc.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/common/dlmalloc.c b/common/dlmalloc.c index 40acd3dfa5..0813e7e8b1 100644 --- a/common/dlmalloc.c +++ b/common/dlmalloc.c @@ -34,6 +34,10 @@ DECLARE_GLOBAL_DATA_PTR;
#ifdef MCHECK_HEAP_PROTECTION #define STATIC_IF_MCHECK static + #undef MALLOC_COPY + #undef MALLOC_ZERO +static inline void MALLOC_ZERO(void *p, size_t sz) { memset(p, 0, sz); } +static inline void MALLOC_COPY(void *dest, const void *src, size_t sz) { memcpy(dest, src, sz); } #else #define STATIC_IF_MCHECK #define mALLOc_impl mALLOc

The core part of mcheck, but without memalign. memalign - to be added in ensuing commits.
Signed-off-by: Eugene Uriev eugeneuriev@gmail.com ---
common/mcheck_core.inc.h | 220 +++++++++++++++++++++++++++++++++++++++ include/mcheck.h | 42 ++++++++ 2 files changed, 262 insertions(+) create mode 100644 common/mcheck_core.inc.h create mode 100644 include/mcheck.h
diff --git a/common/mcheck_core.inc.h b/common/mcheck_core.inc.h new file mode 100644 index 0000000000..6f26ef00d9 --- /dev/null +++ b/common/mcheck_core.inc.h @@ -0,0 +1,220 @@ +/* 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. + * 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) + +#define CANARY_DEPTH 2 + +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. */ + 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)) + +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)); + return hdr; +} + +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) +{ + struct mcheck_hdr *hdr = (struct mcheck_hdr *)altoghether_ptr; + int i; + + hdr->size = customer_sz; + 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; + 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 enum mcheck_status mcheck_mprobe(void *ptr) +{ + struct mcheck_hdr *hdr = &((struct mcheck_hdr *)ptr)[-1]; + + return mcheck_checkhdr(hdr); +} + +static void mcheck_initialize(mcheck_abortfunc_t new_func, char pedantic_flag) +{ + mcheck_abortfunc = (new_func) ? new_func : &mcheck_default_abort; +} + +#endif +#endif diff --git a/include/mcheck.h b/include/mcheck.h new file mode 100644 index 0000000000..a049745e4e --- /dev/null +++ b/include/mcheck.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.1+ */ +/* + * Copyright (C) 1996-2024 Free Software Foundation, Inc. + * This file is part of the GNU C Library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * https://www.gnu.org/licenses/. + */ +#ifndef _MCHECK_H +#define _MCHECK_H 1 + +/* + * Return values for `mprobe': these are the kinds of inconsistencies that + * `mcheck' enables detection of. + */ +enum mcheck_status { + MCHECK_DISABLED = -1, /* Consistency checking is not turned on. */ + MCHECK_OK, /* Block is fine. */ + MCHECK_FREE, /* Block freed twice. */ + MCHECK_HEAD, /* Memory before the block was clobbered. */ + MCHECK_TAIL /* Memory after the block was clobbered. */ +}; + +typedef void (*mcheck_abortfunc_t)(enum mcheck_status); + +int mcheck(mcheck_abortfunc_t func); + +/* + * Check for aberrations in a particular malloc'd block. These are the + * same checks that `mcheck' does, when you free or reallocate a block. + */ +enum mcheck_status mprobe(void *__ptr); + +#endif

This changes are probable worth to be generalized in a separate .h-file so, making it able to cover libc-mallocs and others, without too much copy-paste.
But the malloc<=>mALLOc substitutions interfere with an elegant way to do this.
Signed-off-by: Eugene Uriev eugeneuriev@gmail.com ---
common/dlmalloc.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+)
diff --git a/common/dlmalloc.c b/common/dlmalloc.c index 0813e7e8b1..8de15d7193 100644 --- a/common/dlmalloc.c +++ b/common/dlmalloc.c @@ -2225,6 +2225,73 @@ void cfree(mem) Void_t *mem; #endif
+#ifdef MCHECK_HEAP_PROTECTION + #include "mcheck_core.inc.h" + #if !__STD_C + #error "must have __STD_C" + #endif + +Void_t *mALLOc(size_t bytes) +{ + size_t fullsz = mcheck_alloc_prehook(bytes); + void *p = mALLOc_impl(fullsz); + + if (!p) + return p; + return mcheck_alloc_posthook(p, bytes); +} + +void fREe(Void_t *mem) { fREe_impl(mcheck_free_prehook(mem)); } + +Void_t *rEALLOc(Void_t *oldmem, size_t bytes) +{ + if (bytes == 0) { + if (oldmem) + fREe(oldmem); + return NULL; + } + + if (oldmem == NULL) + return mALLOc(bytes); + + void *p = mcheck_reallocfree_prehook(oldmem); + size_t newsz = mcheck_alloc_prehook(bytes); + + p = rEALLOc_impl(p, newsz); + if (!p) + return p; + return mcheck_alloc_noclean_posthook(p, bytes); +} + +Void_t *mEMALIGn(size_t alignment, size_t bytes) +{ + return NULL; +} + +// pvALLOc, vALLOc - redirect to mEMALIGn, defined here, so they need no wrapping. + +Void_t *cALLOc(size_t n, size_t elem_size) +{ + // NB: here is no overflow check. + size_t fullsz = mcheck_alloc_prehook(n * elem_size); + void *p = cALLOc_impl(1, fullsz); + + if (!p) + return p; + return mcheck_alloc_noclean_posthook(p, n * elem_size); +} + +// mcheck API { +int mcheck(mcheck_abortfunc_t f) +{ + mcheck_initialize(f, 0); + return 0; +} + +enum mcheck_status mprobe(void *__ptr) { return mcheck_mprobe(__ptr); } +// mcheck API } +#endif +
/*

Signed-off-by: Eugene Uriev eugeneuriev@gmail.com ---
common/dlmalloc.c | 7 ++++++- common/mcheck_core.inc.h | 20 ++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/common/dlmalloc.c b/common/dlmalloc.c index 8de15d7193..73c04af2a3 100644 --- a/common/dlmalloc.c +++ b/common/dlmalloc.c @@ -2265,7 +2265,12 @@ Void_t *rEALLOc(Void_t *oldmem, size_t bytes)
Void_t *mEMALIGn(size_t alignment, size_t bytes) { - return NULL; + size_t fullsz = mcheck_memalign_prehook(alignment, bytes); + void *p = mEMALIGn_impl(alignment, fullsz); + + if (!p) + return p; + return mcheck_memalign_posthook(alignment, p, bytes); }
// pvALLOc, vALLOc - redirect to mEMALIGn, defined here, so they need no wrapping. diff --git a/common/mcheck_core.inc.h b/common/mcheck_core.inc.h index 6f26ef00d9..b038bb0539 100644 --- a/common/mcheck_core.inc.h +++ b/common/mcheck_core.inc.h @@ -69,6 +69,7 @@ typedef struct { } 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. */ };
@@ -104,6 +105,7 @@ static inline size_t allign_size_up(size_t sz, size_t grain) }
#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) { @@ -156,7 +158,8 @@ static void *mcheck_free_helper(void *ptr, int clean_content)
if (clean_content) mcheck_flood(ptr, FREEFLOOD, mcheck_allign_customer_size(hdr->size)); - return hdr; + + return (char *)hdr - hdr->aln_skip; }
static void *mcheck_free_prehook(void *ptr) { return mcheck_free_helper(ptr, CLEAN_CONTENT); } @@ -171,10 +174,13 @@ static size_t mcheck_alloc_prehook(size_t sz) static void *mcheck_allocated_helper(void *altoghether_ptr, size_t customer_sz, size_t alignment, int clean_content) { - struct mcheck_hdr *hdr = (struct mcheck_hdr *)altoghether_ptr; + 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;
@@ -204,6 +210,16 @@ static void *mcheck_alloc_noclean_posthook(void *altoghether_ptr, size_t custome 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];

The pedantic mode is run-time contolled, so appropriate registry take place everytime.
Maybe it's worth to use compile-time control only. So, the registry could be optimized out by an #ifdef.
Signed-off-by: Eugene Uriev eugeneuriev@gmail.com ---
common/dlmalloc.c | 12 ++++++++++++ common/mcheck_core.inc.h | 41 ++++++++++++++++++++++++++++++++++++++++ include/mcheck.h | 9 +++++++++ 3 files changed, 62 insertions(+)
diff --git a/common/dlmalloc.c b/common/dlmalloc.c index 73c04af2a3..a0616217d4 100644 --- a/common/dlmalloc.c +++ b/common/dlmalloc.c @@ -2233,6 +2233,7 @@ void cfree(mem) Void_t *mem;
Void_t *mALLOc(size_t bytes) { + mcheck_pedantic_prehook(); size_t fullsz = mcheck_alloc_prehook(bytes); void *p = mALLOc_impl(fullsz);
@@ -2245,6 +2246,7 @@ void fREe(Void_t *mem) { fREe_impl(mcheck_free_prehook(mem)); }
Void_t *rEALLOc(Void_t *oldmem, size_t bytes) { + mcheck_pedantic_prehook(); if (bytes == 0) { if (oldmem) fREe(oldmem); @@ -2265,6 +2267,7 @@ Void_t *rEALLOc(Void_t *oldmem, size_t bytes)
Void_t *mEMALIGn(size_t alignment, size_t bytes) { + mcheck_pedantic_prehook(); size_t fullsz = mcheck_memalign_prehook(alignment, bytes); void *p = mEMALIGn_impl(alignment, fullsz);
@@ -2277,6 +2280,7 @@ Void_t *mEMALIGn(size_t alignment, size_t bytes)
Void_t *cALLOc(size_t n, size_t elem_size) { + mcheck_pedantic_prehook(); // NB: here is no overflow check. size_t fullsz = mcheck_alloc_prehook(n * elem_size); void *p = cALLOc_impl(1, fullsz); @@ -2287,12 +2291,20 @@ Void_t *cALLOc(size_t n, size_t elem_size) }
// mcheck API { +int mcheck_pedantic(mcheck_abortfunc_t f) +{ + mcheck_initialize(f, 1); + return 0; +} + int mcheck(mcheck_abortfunc_t f) { mcheck_initialize(f, 0); return 0; }
+void mcheck_check_all(void) { mcheck_pedantic_check(); } + enum mcheck_status mprobe(void *__ptr) { return mcheck_mprobe(__ptr); } // mcheck API } #endif diff --git a/common/mcheck_core.inc.h b/common/mcheck_core.inc.h index b038bb0539..85a34de295 100644 --- a/common/mcheck_core.inc.h +++ b/common/mcheck_core.inc.h @@ -30,6 +30,8 @@ * 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: * { @@ -61,8 +63,14 @@ #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]; @@ -159,6 +167,12 @@ static void *mcheck_free_helper(void *ptr, int clean_content) 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; }
@@ -197,6 +211,17 @@ static void *mcheck_allocated_helper(void *altoghether_ptr, size_t customer_sz,
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; }
@@ -227,9 +252,25 @@ static enum mcheck_status mcheck_mprobe(void *ptr) 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 diff --git a/include/mcheck.h b/include/mcheck.h index a049745e4e..f4c9b7e61c 100644 --- a/include/mcheck.h +++ b/include/mcheck.h @@ -33,6 +33,15 @@ typedef void (*mcheck_abortfunc_t)(enum mcheck_status);
int mcheck(mcheck_abortfunc_t func);
+/* + * Similar to `mcheck' but performs checks for all block whenever one of + * the memory handling functions is called. This can be very slow. + */ +int mcheck_pedantic(mcheck_abortfunc_t f); + +/* Force check of all blocks now. */ +void mcheck_check_all(void); + /* * Check for aberrations in a particular malloc'd block. These are the * same checks that `mcheck' does, when you free or reallocate a block.

The using of pre-reloc/malloc_simple heap is too hard to follow after the relocation.
So lets drop it from the pedantic registry and switch to dlmalloc, when moved.
The offset is ignored, but kept in the API for the probable case, when that early heap is relocated too.
Signed-off-by: Eugene Uriev eugeneuriev@gmail.com ---
common/board_f.c | 4 ++++ common/mcheck_core.inc.h | 11 +++++++++++ 2 files changed, 15 insertions(+)
diff --git a/common/board_f.c b/common/board_f.c index 442b8349d0..ca4d5291a9 100644 --- a/common/board_f.c +++ b/common/board_f.c @@ -719,6 +719,7 @@ static int reloc_bloblist(void) return 0; }
+void mcheck_on_ramrelocation(size_t offset); static int setup_reloc(void) { if (!(gd->flags & GD_FLG_SKIP_RELOC)) { @@ -744,6 +745,9 @@ static int setup_reloc(void) if (gd->flags & GD_FLG_SKIP_RELOC) { debug("Skipping relocation due to flag\n"); } else { +#ifdef MCHECK_HEAP_PROTECTION + mcheck_on_ramrelocation(gd->reloc_off); +#endif debug("Relocation Offset is: %08lx\n", gd->reloc_off); debug("Relocating to %08lx, new gd at %08lx, sp at %08lx\n", gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd), diff --git a/common/mcheck_core.inc.h b/common/mcheck_core.inc.h index 85a34de295..bade03598f 100644 --- a/common/mcheck_core.inc.h +++ b/common/mcheck_core.inc.h @@ -273,5 +273,16 @@ static void mcheck_initialize(mcheck_abortfunc_t new_func, char pedantic_flag) mcheck_pedantic_flag = pedantic_flag; }
+void mcheck_on_ramrelocation(size_t offset) +{ + char *p; + int i; + // Simple, but inaccurate strategy: drop the pre-reloc heap + for (i = 0; i < REGISTRY_SZ; ++i) + if ((p = mcheck_registry[i]) != NULL ) { + printf("mcheck, WRN: forgetting %p chunk\n", p); + mcheck_registry[i] = 0; + } +} #endif #endif

My tests have been run on an U-Boot (of older version) for ARM (64bits).
Signed-off-by: Eugene Uriev eugeneuriev@gmail.com ---
common/mcheck_core.inc.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/common/mcheck_core.inc.h b/common/mcheck_core.inc.h index bade03598f..2f11ac567f 100644 --- a/common/mcheck_core.inc.h +++ b/common/mcheck_core.inc.h @@ -48,6 +48,13 @@ * rather in compile-time, by MCHECK_HEAP_PROTECTION macro. That guarantees that * we haven't missed first malloc. */ + +/* + * Testing + * This library had been successfully tested for U-Boot @ ARM SoC chip / 64bits. + * Proven for both default and pedantic mode: confirms U-Boot to be clean, and catches + * intentional/testing corruptions. Working with malloc_trim is not tested. + */ #ifndef _MCHECKCORE_INC_H #define _MCHECKCORE_INC_H 1 #include "mcheck.h" @@ -70,6 +77,8 @@ // avoid problems with BSS at early stage: static char mcheck_pedantic_flag __section(".data") = 0; static void *mcheck_registry[REGISTRY_SZ] __section(".data") = {0}; +static size_t mcheck_chunk_count __section(".data") = 0; +static size_t mcheck_chunk_count_max __section(".data") = 0;
typedef unsigned long long mcheck_elem; typedef struct { @@ -102,7 +111,7 @@ static void mcheck_default_abort(enum mcheck_status status) msg = "bogus mcheck_status, library is buggy\n"; break; } - printf("\n\nmcheck: %s!!!\n\n", msg); + printf("\n\nmcheck: %s!!! [%zu]\n\n", msg, mcheck_chunk_count_max); }
static mcheck_abortfunc_t mcheck_abortfunc = &mcheck_default_abort; @@ -173,6 +182,7 @@ static void *mcheck_free_helper(void *ptr, int clean_content) break; }
+ --mcheck_chunk_count; return (char *)hdr - hdr->aln_skip; }
@@ -212,6 +222,10 @@ static void *mcheck_allocated_helper(void *altoghether_ptr, size_t customer_sz, for (i = 0; i < CANARY_DEPTH; ++i) tail->elems[i] = MAGICTAIL;
+ ++mcheck_chunk_count; + if (mcheck_chunk_count > mcheck_chunk_count_max) + mcheck_chunk_count_max = mcheck_chunk_count; + for (i = 0; i < REGISTRY_SZ; ++i) if (!mcheck_registry[i]) { mcheck_registry[i] = hdr; @@ -283,6 +297,8 @@ void mcheck_on_ramrelocation(size_t offset) printf("mcheck, WRN: forgetting %p chunk\n", p); mcheck_registry[i] = 0; } + + mcheck_chunk_count = 0; } #endif #endif

Signed-off-by: Eugene Uriev eugeneuriev@gmail.com
---
common/mcheck_core.inc.h | 16 ++++++++-------- include/mcheck.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/common/mcheck_core.inc.h b/common/mcheck_core.inc.h index 2f11ac567f..6902140992 100644 --- a/common/mcheck_core.inc.h +++ b/common/mcheck_core.inc.h @@ -90,7 +90,7 @@ struct mcheck_hdr { mcheck_canary canary; /* Magic number to check header integrity. */ };
-static void mcheck_default_abort(enum mcheck_status status) +static void mcheck_default_abort(enum mcheck_status status, const void *p) { const char *msg;
@@ -111,7 +111,7 @@ static void mcheck_default_abort(enum mcheck_status status) msg = "bogus mcheck_status, library is buggy\n"; break; } - printf("\n\nmcheck: %s!!! [%zu]\n\n", msg, mcheck_chunk_count_max); + printf("\n\nmcheck: %p:%s!!! [%zu]\n\n", p, msg, mcheck_chunk_count_max); }
static mcheck_abortfunc_t mcheck_abortfunc = &mcheck_default_abort; @@ -124,9 +124,9 @@ static inline size_t allign_size_up(size_t sz, size_t grain) #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) +static enum mcheck_status mcheck_OnNok(enum mcheck_status status, const void *p) { - (*mcheck_abortfunc)(status); + (*mcheck_abortfunc)(status, p); return status; }
@@ -136,11 +136,11 @@ static enum mcheck_status mcheck_checkhdr(const struct mcheck_hdr *hdr)
for (i = 0; i < CANARY_DEPTH; ++i) if (hdr->canary.elems[i] == MAGICFREE) - return mcheck_OnNok(MCHECK_FREE); + return mcheck_OnNok(MCHECK_FREE, hdr + 1);
for (i = 0; i < CANARY_DEPTH; ++i) if (hdr->canary.elems[i] != MAGICWORD) - return mcheck_OnNok(MCHECK_HEAD); + return mcheck_OnNok(MCHECK_HEAD, hdr + 1);
const size_t payload_size = hdr->size; const size_t payload_size_aligned = mcheck_allign_customer_size(payload_size); @@ -150,13 +150,13 @@ static enum mcheck_status mcheck_checkhdr(const struct mcheck_hdr *hdr)
for (i = 0; i < padd_size; ++i) if (payload[payload_size + i] != PADDINGFLOOD) - return mcheck_OnNok(MCHECK_TAIL); + return mcheck_OnNok(MCHECK_TAIL, hdr + 1);
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_OnNok(MCHECK_TAIL, hdr + 1); return MCHECK_OK; }
diff --git a/include/mcheck.h b/include/mcheck.h index f4c9b7e61c..bd506ae629 100644 --- a/include/mcheck.h +++ b/include/mcheck.h @@ -29,7 +29,7 @@ enum mcheck_status { MCHECK_TAIL /* Memory after the block was clobbered. */ };
-typedef void (*mcheck_abortfunc_t)(enum mcheck_status); +typedef void (*mcheck_abortfunc_t)(enum mcheck_status, const void *p);
int mcheck(mcheck_abortfunc_t func);

On Sun, 31 Mar 2024 23:03:18 +0300, Eugene Uriev wrote:
There was no "mcheck" for U-Boot before.
Since U-Boot has only 1 thread, and normally makes 4000+ - 6000+ mallocs, it's better to use havier canaries to protect heap-chunks. My variant uses 2x8 = 16byte-long protector. And the multiplier could be changed to tune speed/protection tradeoff. This protects not only against memset()-s, but against "near" wild pointers too, and makes more probable to catch "distant" ones.
[...]
Applied to u-boot/master, thanks!
participants (2)
-
Eugene Uriev
-
Tom Rini