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: 78static char mcheck_pedantic_flag __section(".data") = 0; 79static void *mcheck_registry[REGISTRY_SZ] __section(".data") = {0}; 80static size_t mcheck_chunk_count __section(".data") = 0; 81static size_t mcheck_chunk_count_max __section(".data") = 0; 82 83typedef 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. */ 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 93static 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 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)) 125#define mcheck_evaluate_memalign_prefix_size(ALIGN) allign_size_up(sizeof(struct mcheck_hdr), ALIGN) 126 127static enum mcheck_status mcheck_OnNok(enum mcheck_status status, const void *p) 128{ 129 (*mcheck_abortfunc)(status, p); 130 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) 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 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)); 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 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{ 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 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 252static 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 262static 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 269static 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 284static 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 290void 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