Eugene Uriev | f5f24c8 | 2024-03-31 23:03:21 +0300 | [diff] [blame] | 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 | * Heavy canary allows to catch not only memset(..)-errors, |
| 34 | * but overflow/underflow of struct-array access: |
| 35 | * { |
| 36 | * struct mystruct* p = malloc(sizeof(struct mystruct) * N); |
| 37 | * p[-1].field1 = 0; |
| 38 | * p[N].field2 = 13; |
| 39 | * } |
| 40 | * TODO: In order to guarantee full coverage of that kind of errors, a user can add variable-size |
| 41 | * canaries here. So pre- and post-canary with size >= reqested_size, could be provided |
| 42 | * (with the price of 3x heap-usage). Therefore, it would catch 100% of changes beyond |
| 43 | * an array, for index(+1/-1) errors. |
| 44 | * |
| 45 | * U-Boot is a BL, not an OS with a lib. Activity of the library is set not in runtime, |
| 46 | * rather in compile-time, by MCHECK_HEAP_PROTECTION macro. That guarantees that |
| 47 | * we haven't missed first malloc. |
| 48 | */ |
| 49 | #ifndef _MCHECKCORE_INC_H |
| 50 | #define _MCHECKCORE_INC_H 1 |
| 51 | #include "mcheck.h" |
| 52 | |
| 53 | #if defined(MCHECK_HEAP_PROTECTION) |
| 54 | #define mcheck_flood memset |
| 55 | |
| 56 | // these are from /dev/random: |
| 57 | #define MAGICWORD 0x99ccf430fa562a05ULL |
| 58 | #define MAGICFREE 0x4875e63c0c6fc08eULL |
| 59 | #define MAGICTAIL 0x918dbcd7df78dcd6ULL |
| 60 | #define MALLOCFLOOD ((char)0xb6) |
| 61 | #define FREEFLOOD ((char)0xf5) |
| 62 | #define PADDINGFLOOD ((char)0x58) |
| 63 | |
| 64 | #define CANARY_DEPTH 2 |
| 65 | |
| 66 | typedef unsigned long long mcheck_elem; |
| 67 | typedef struct { |
| 68 | mcheck_elem elems[CANARY_DEPTH]; |
| 69 | } mcheck_canary; |
| 70 | struct mcheck_hdr { |
| 71 | size_t size; /* Exact size requested by user. */ |
Eugene Uriev | c61e313 | 2024-03-31 23:03:23 +0300 | [diff] [blame^] | 72 | size_t aln_skip; /* Ignored bytes, before the mcheck_hdr, to fulfill alignment */ |
Eugene Uriev | f5f24c8 | 2024-03-31 23:03:21 +0300 | [diff] [blame] | 73 | mcheck_canary canary; /* Magic number to check header integrity. */ |
| 74 | }; |
| 75 | |
| 76 | static void mcheck_default_abort(enum mcheck_status status) |
| 77 | { |
| 78 | const char *msg; |
| 79 | |
| 80 | switch (status) { |
| 81 | case MCHECK_OK: |
| 82 | msg = "memory is consistent, library is buggy\n"; |
| 83 | break; |
| 84 | case MCHECK_HEAD: |
| 85 | msg = "memory clobbered before allocated block\n"; |
| 86 | break; |
| 87 | case MCHECK_TAIL: |
| 88 | msg = "memory clobbered past end of allocated block\n"; |
| 89 | break; |
| 90 | case MCHECK_FREE: |
| 91 | msg = "block freed twice\n"; |
| 92 | break; |
| 93 | default: |
| 94 | msg = "bogus mcheck_status, library is buggy\n"; |
| 95 | break; |
| 96 | } |
| 97 | printf("\n\nmcheck: %s!!!\n\n", msg); |
| 98 | } |
| 99 | |
| 100 | static mcheck_abortfunc_t mcheck_abortfunc = &mcheck_default_abort; |
| 101 | |
| 102 | static inline size_t allign_size_up(size_t sz, size_t grain) |
| 103 | { |
| 104 | return (sz + grain - 1) & ~(grain - 1); |
| 105 | } |
| 106 | |
| 107 | #define mcheck_allign_customer_size(SZ) allign_size_up(SZ, sizeof(mcheck_elem)) |
Eugene Uriev | c61e313 | 2024-03-31 23:03:23 +0300 | [diff] [blame^] | 108 | #define mcheck_evaluate_memalign_prefix_size(ALIGN) allign_size_up(sizeof(struct mcheck_hdr), ALIGN) |
Eugene Uriev | f5f24c8 | 2024-03-31 23:03:21 +0300 | [diff] [blame] | 109 | |
| 110 | static enum mcheck_status mcheck_OnNok(enum mcheck_status status) |
| 111 | { |
| 112 | (*mcheck_abortfunc)(status); |
| 113 | return status; |
| 114 | } |
| 115 | |
| 116 | static enum mcheck_status mcheck_checkhdr(const struct mcheck_hdr *hdr) |
| 117 | { |
| 118 | int i; |
| 119 | |
| 120 | for (i = 0; i < CANARY_DEPTH; ++i) |
| 121 | if (hdr->canary.elems[i] == MAGICFREE) |
| 122 | return mcheck_OnNok(MCHECK_FREE); |
| 123 | |
| 124 | for (i = 0; i < CANARY_DEPTH; ++i) |
| 125 | if (hdr->canary.elems[i] != MAGICWORD) |
| 126 | return mcheck_OnNok(MCHECK_HEAD); |
| 127 | |
| 128 | const size_t payload_size = hdr->size; |
| 129 | const size_t payload_size_aligned = mcheck_allign_customer_size(payload_size); |
| 130 | const size_t padd_size = payload_size_aligned - hdr->size; |
| 131 | |
| 132 | const char *payload = (const char *)&hdr[1]; |
| 133 | |
| 134 | for (i = 0; i < padd_size; ++i) |
| 135 | if (payload[payload_size + i] != PADDINGFLOOD) |
| 136 | return mcheck_OnNok(MCHECK_TAIL); |
| 137 | |
| 138 | const mcheck_canary *tail = (const mcheck_canary *)&payload[payload_size_aligned]; |
| 139 | |
| 140 | for (i = 0; i < CANARY_DEPTH; ++i) |
| 141 | if (tail->elems[i] != MAGICTAIL) |
| 142 | return mcheck_OnNok(MCHECK_TAIL); |
| 143 | return MCHECK_OK; |
| 144 | } |
| 145 | |
| 146 | enum { KEEP_CONTENT = 0, CLEAN_CONTENT, ANY_ALIGNMENT = 1 }; |
| 147 | static void *mcheck_free_helper(void *ptr, int clean_content) |
| 148 | { |
| 149 | if (!ptr) |
| 150 | return ptr; |
| 151 | |
| 152 | struct mcheck_hdr *hdr = &((struct mcheck_hdr *)ptr)[-1]; |
| 153 | int i; |
| 154 | |
| 155 | mcheck_checkhdr(hdr); |
| 156 | for (i = 0; i < CANARY_DEPTH; ++i) |
| 157 | hdr->canary.elems[i] = MAGICFREE; |
| 158 | |
| 159 | if (clean_content) |
| 160 | mcheck_flood(ptr, FREEFLOOD, mcheck_allign_customer_size(hdr->size)); |
Eugene Uriev | c61e313 | 2024-03-31 23:03:23 +0300 | [diff] [blame^] | 161 | |
| 162 | return (char *)hdr - hdr->aln_skip; |
Eugene Uriev | f5f24c8 | 2024-03-31 23:03:21 +0300 | [diff] [blame] | 163 | } |
| 164 | |
| 165 | static void *mcheck_free_prehook(void *ptr) { return mcheck_free_helper(ptr, CLEAN_CONTENT); } |
| 166 | static void *mcheck_reallocfree_prehook(void *ptr) { return mcheck_free_helper(ptr, KEEP_CONTENT); } |
| 167 | |
| 168 | static size_t mcheck_alloc_prehook(size_t sz) |
| 169 | { |
| 170 | sz = mcheck_allign_customer_size(sz); |
| 171 | return sizeof(struct mcheck_hdr) + sz + sizeof(mcheck_canary); |
| 172 | } |
| 173 | |
| 174 | static void *mcheck_allocated_helper(void *altoghether_ptr, size_t customer_sz, |
| 175 | size_t alignment, int clean_content) |
| 176 | { |
Eugene Uriev | c61e313 | 2024-03-31 23:03:23 +0300 | [diff] [blame^] | 177 | const size_t slop = alignment ? |
| 178 | mcheck_evaluate_memalign_prefix_size(alignment) - sizeof(struct mcheck_hdr) : 0; |
| 179 | struct mcheck_hdr *hdr = (struct mcheck_hdr *)((char *)altoghether_ptr + slop); |
Eugene Uriev | f5f24c8 | 2024-03-31 23:03:21 +0300 | [diff] [blame] | 180 | int i; |
| 181 | |
| 182 | hdr->size = customer_sz; |
Eugene Uriev | c61e313 | 2024-03-31 23:03:23 +0300 | [diff] [blame^] | 183 | hdr->aln_skip = slop; |
Eugene Uriev | f5f24c8 | 2024-03-31 23:03:21 +0300 | [diff] [blame] | 184 | for (i = 0; i < CANARY_DEPTH; ++i) |
| 185 | hdr->canary.elems[i] = MAGICWORD; |
| 186 | |
| 187 | char *payload = (char *)&hdr[1]; |
| 188 | |
| 189 | if (clean_content) |
| 190 | mcheck_flood(payload, MALLOCFLOOD, customer_sz); |
| 191 | |
| 192 | const size_t customer_size_aligned = mcheck_allign_customer_size(customer_sz); |
| 193 | |
| 194 | mcheck_flood(payload + customer_sz, PADDINGFLOOD, customer_size_aligned - customer_sz); |
| 195 | |
| 196 | mcheck_canary *tail = (mcheck_canary *)&payload[customer_size_aligned]; |
| 197 | |
| 198 | for (i = 0; i < CANARY_DEPTH; ++i) |
| 199 | tail->elems[i] = MAGICTAIL; |
| 200 | return payload; |
| 201 | } |
| 202 | |
| 203 | static void *mcheck_alloc_posthook(void *altoghether_ptr, size_t customer_sz) |
| 204 | { |
| 205 | return mcheck_allocated_helper(altoghether_ptr, customer_sz, ANY_ALIGNMENT, CLEAN_CONTENT); |
| 206 | } |
| 207 | |
| 208 | static void *mcheck_alloc_noclean_posthook(void *altoghether_ptr, size_t customer_sz) |
| 209 | { |
| 210 | return mcheck_allocated_helper(altoghether_ptr, customer_sz, ANY_ALIGNMENT, KEEP_CONTENT); |
| 211 | } |
| 212 | |
Eugene Uriev | c61e313 | 2024-03-31 23:03:23 +0300 | [diff] [blame^] | 213 | static size_t mcheck_memalign_prehook(size_t alig, size_t sz) |
| 214 | { |
| 215 | return mcheck_evaluate_memalign_prefix_size(alig) + sz + sizeof(mcheck_canary); |
| 216 | } |
| 217 | |
| 218 | static void *mcheck_memalign_posthook(size_t alignment, void *altoghether_ptr, size_t customer_sz) |
| 219 | { |
| 220 | return mcheck_allocated_helper(altoghether_ptr, customer_sz, alignment, CLEAN_CONTENT); |
| 221 | } |
| 222 | |
Eugene Uriev | f5f24c8 | 2024-03-31 23:03:21 +0300 | [diff] [blame] | 223 | static enum mcheck_status mcheck_mprobe(void *ptr) |
| 224 | { |
| 225 | struct mcheck_hdr *hdr = &((struct mcheck_hdr *)ptr)[-1]; |
| 226 | |
| 227 | return mcheck_checkhdr(hdr); |
| 228 | } |
| 229 | |
| 230 | static void mcheck_initialize(mcheck_abortfunc_t new_func, char pedantic_flag) |
| 231 | { |
| 232 | mcheck_abortfunc = (new_func) ? new_func : &mcheck_default_abort; |
| 233 | } |
| 234 | |
| 235 | #endif |
| 236 | #endif |