| /* 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. | |
| */ | |
| /* | |
| * 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 |