/* 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. * */ /* * 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. */ /* * 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" #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}; 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 { 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 void *p) { 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: %p:%s!!! [%zu]\n\n", p, msg, mcheck_chunk_count_max); } 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, const void *p) { (*mcheck_abortfunc)(status, p); 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, hdr + 1); for (i = 0; i < CANARY_DEPTH; ++i) if (hdr->canary.elems[i] != MAGICWORD) 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); 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, 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, hdr + 1); 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; } --mcheck_chunk_count; 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; ++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; 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; } 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; } mcheck_chunk_count = 0; } #endif #endif