mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-31 20:18:18 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			305 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			305 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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
 |