|  | /* | 
|  | * Copyright 2017, Data61 | 
|  | * Commonwealth Scientific and Industrial Research Organisation (CSIRO) | 
|  | * ABN 41 687 119 230. | 
|  | * | 
|  | * This software may be distributed and modified according to the terms of | 
|  | * the BSD 2-Clause license. Note that NO WARRANTY is provided. | 
|  | * See "LICENSE_BSD2.txt" for details. | 
|  | * | 
|  | * @TAG(DATA61_BSD) | 
|  | */ | 
|  |  | 
|  | /* This file contains functionality for debugging heap corruption issues. It is | 
|  | * styled after Electric Fence [0], but we can't check as thoroughly as eFence | 
|  | * can because we don't have support for mapping/unmapping/mprotecting regions. | 
|  | * The basic concept is that we stick a 'canary' value before and after | 
|  | * allocated memory. During free, we check that these canaries haven't been | 
|  | * overwritten, which would imply buffer underflow or overflow. This checking | 
|  | * is limited in that we don't catch heap corruption until memory is actually | 
|  | * freed. | 
|  | * | 
|  | * Essentially any allocated region actually looks like this: | 
|  | *   p           q         r | 
|  | *   ↓           ↓         ↓ | 
|  | *   |canary|size|memory...|canary| | 
|  | * | 
|  | * Internally we deal in 'unboxed' pointers, like p. Externally the caller is | 
|  | * given 'boxed' pointers, like q. We effectively play the same tricks malloc | 
|  | * does to store some book keeping information surrounding the malloced memory. | 
|  | * The 'size' stored is the size the user requested, not the size including | 
|  | * the canaries etc. We need to track this in order to calculate the pointer r | 
|  | * on free. | 
|  | * | 
|  | * In addition to checking for corruption within the heap, this functionality | 
|  | * also tracks pointers that have been allocated. If you try and free (or | 
|  | * realloc) a pointer that was never allocated, it will be detected. | 
|  | * | 
|  | * To use this, you will need to instruct the linker to wrap your calls to | 
|  | * allocation functions. You will need to append something like the following | 
|  | * to your LD_FALGS: | 
|  | *  -Wl,--wrap=malloc -Wl,--wrap=free -Wl,--wrap=realloc -Wl,--wrap=calloc | 
|  | * | 
|  | * Ensure you do not call any functions that malloc or free memory within this | 
|  | * file or you will infinitely recurse. | 
|  | */ | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <autoconf.h> | 
|  | #include <sel4debug/gen_config.h> | 
|  | #include <stdint.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> /* for size_t */ | 
|  | #include <string.h> | 
|  |  | 
|  | /* Maximum alignment of a data type. The malloc spec requires that returned | 
|  | * pointers are aligned to this. | 
|  | */ | 
|  | #define MAX_ALIGNMENT (sizeof(void*) * 2) /* bytes */ | 
|  |  | 
|  | /* Random bits to OR into the pre- and post-canary values so we're not storing | 
|  | * an exact pointer value. Note that these should be less than MAX_ALIGNMENT so | 
|  | * that we're ORing into known 0 bits. Preserving the pointer is not actually | 
|  | * necessary so nothing will break if you change these values to any random | 
|  | * word-sized value. | 
|  | */ | 
|  | #define PRE_EXTRA_BITS  0x7 | 
|  | #define POST_EXTRA_BITS 0x3 | 
|  |  | 
|  | /* Algorithms for calculating a canary value to use before and after the | 
|  | * allocated region. The actual algorithm used here is more or less irrelevant | 
|  | * as long as it is deterministic. | 
|  | */ | 
|  | static uintptr_t pre_canary(void *ptr) | 
|  | { | 
|  | return ((uintptr_t)ptr) | PRE_EXTRA_BITS; | 
|  | } | 
|  | static uintptr_t post_canary(void *ptr) | 
|  | { | 
|  | return ((uintptr_t)ptr) | POST_EXTRA_BITS; | 
|  | } | 
|  |  | 
|  | typedef struct { | 
|  | uintptr_t canary; | 
|  | size_t size; | 
|  | } __attribute__((aligned(MAX_ALIGNMENT))) metadata_t; | 
|  |  | 
|  | /* A `uintptr_t` that is not naturally aligned. It is necessary to explicitly | 
|  | * tag unaligned pointers because, e.g., on ARM the compiler is free to | 
|  | * transform `memcpy` calls into a variant that assumes its inputs are aligned | 
|  | * and performs word-sized accesses. See the following for more information: | 
|  | *   http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/4972.html | 
|  | */ | 
|  | typedef uintptr_t unaligned_uintptr_t __attribute__((aligned(1))); | 
|  |  | 
|  | /* Whether we've already encountered heap corruption. See uses of this below, | 
|  | * where we abandon all instrumentation if we've hit an error. The reasoning | 
|  | * behind this is that error reporting functions may call malloc. | 
|  | */ | 
|  | static int err = 0; | 
|  |  | 
|  | /* Adjust a size that is about to be passed to the real allocation functions in | 
|  | * order to account for our instrumentation. | 
|  | */ | 
|  | static size_t adjust_size(size_t size) | 
|  | { | 
|  | return size + sizeof(metadata_t) + sizeof(uintptr_t); | 
|  | } | 
|  |  | 
|  | /* Wrap a (just-allocated) region with canary values. */ | 
|  | static void *box(void *ptr, size_t size) | 
|  | { | 
|  | if (ptr == NULL) { | 
|  | return ptr; | 
|  | } | 
|  |  | 
|  | metadata_t *pre = (metadata_t *)ptr; | 
|  | ptr += sizeof(*pre); | 
|  | unaligned_uintptr_t *post = ptr + size; | 
|  |  | 
|  | /* Write the leading canary. */ | 
|  | pre->canary = pre_canary(ptr); | 
|  | pre->size = size; | 
|  |  | 
|  | /* Use memcpy for the trailing canary so we don't need to care about | 
|  | * alignment. | 
|  | */ | 
|  | uintptr_t canary = post_canary(ptr); | 
|  | memcpy(post, &canary, sizeof(canary)); | 
|  |  | 
|  | return ptr; | 
|  | } | 
|  |  | 
|  | /* Throw an error resulting from a bad user invocation. */ | 
|  | #define error(args...) do { \ | 
|  | assert(!err); /* We should not already be handling an error. */ \ | 
|  | err = 1; \ | 
|  | fprintf(stderr, args); \ | 
|  | abort(); \ | 
|  | } while (0) | 
|  |  | 
|  | /* Unwrap a canary-endowed region into the original allocated pointer. */ | 
|  | static void *unbox(void *ptr, void *ret_addr) | 
|  | { | 
|  | if (ptr == NULL) { | 
|  | return ptr; | 
|  | } | 
|  |  | 
|  | metadata_t *pre = (metadata_t *)(ptr - sizeof(*pre)); | 
|  | unaligned_uintptr_t *post = ptr + pre->size; | 
|  |  | 
|  | /* Check the leading canary (underflow). */ | 
|  | if (pre->canary != pre_canary(ptr)) { | 
|  | error("Leading corruption in heap memory pointed to by %p (called " | 
|  | "prior to %p)\n", ptr, ret_addr); | 
|  | } | 
|  |  | 
|  | /* Check the trailing canary with memcmp so we don't need to care about | 
|  | * alignment. If the memcmp VM faults here, it's likely that you underran | 
|  | * your buffer and overwrote the 'size' metadata, causing this function to | 
|  | * derive an incorrect 'post' pointer. | 
|  | */ | 
|  | uintptr_t canary = post_canary(ptr); | 
|  | if (memcmp(post, &canary, sizeof(canary)) != 0) { | 
|  | error("Buffer overflow in heap memory pointed to by %p (called prior " | 
|  | "to %p)\n", ptr, ret_addr); | 
|  | } | 
|  |  | 
|  | return (void *)pre; | 
|  | } | 
|  |  | 
|  | /* Buffer for tracking currently live heap pointers. This is used to detect | 
|  | * when the user attempts to free an invalid pointer. Note that we always track | 
|  | * *boxed* pointers as these are the ones seen by the user. | 
|  | */ | 
|  | #ifndef CONFIG_LIBSEL4DEBUG_ALLOC_BUFFER_ENTRIES | 
|  | #define CONFIG_LIBSEL4DEBUG_ALLOC_BUFFER_ENTRIES 128 | 
|  | #endif | 
|  | static uintptr_t alloced[CONFIG_LIBSEL4DEBUG_ALLOC_BUFFER_ENTRIES]; | 
|  |  | 
|  | /* Track the given heap pointer as currently live. */ | 
|  | static void track(void *ptr) | 
|  | { | 
|  | if (sizeof(alloced) == 0 || ptr == NULL) { | 
|  | /* Disable tracking if we have no buffer and never track NULL. */ | 
|  | return; | 
|  | } | 
|  | for (unsigned int i = 0; i < sizeof(alloced) / sizeof(uintptr_t); i++) { | 
|  | if (alloced[i] == 0) { | 
|  | /* Found a free slot. */ | 
|  | alloced[i] = (uintptr_t)ptr; | 
|  | return; | 
|  | } | 
|  | } | 
|  | /* Failed to find a free slot. */ | 
|  | error("Exhausted pointer tracking buffer; try increasing " | 
|  | "CONFIG_LIBSEL4DEBUG_ALLOC_BUFFER_ENTRIES value\n"); | 
|  | } | 
|  |  | 
|  | /* Stop tracking the given pointer (mark it as dead). */ | 
|  | static void untrack(void *ptr, void *ret_addr) | 
|  | { | 
|  | if (sizeof(alloced) == 0 || ptr == NULL) { | 
|  | /* Ignore tracking if we have no buffer or are freeing NULL. */ | 
|  | return; | 
|  | } | 
|  | for (unsigned int i = 0; i < sizeof(alloced) / sizeof(uintptr_t); i++) { | 
|  | if (alloced[i] == (uintptr_t)ptr) { | 
|  | /* Found it. */ | 
|  | alloced[i] = 0; | 
|  | return; | 
|  | } | 
|  | } | 
|  | /* Failed to find it. */ | 
|  | error("Attempt to free pointer %p that was never malloced (called prior " | 
|  | "to %p)\n", ptr, ret_addr); | 
|  | } | 
|  |  | 
|  | /* Wrapped functions that will be exported to us from libmuslc. */ | 
|  | void *__real_malloc(size_t size); | 
|  | void __real_free(void *ptr); | 
|  | void *__real_calloc(size_t num, size_t size); | 
|  | void *__real_realloc(void *ptr, size_t size); | 
|  |  | 
|  | /* Actual allocation wrappers follow. */ | 
|  |  | 
|  | void *__wrap_malloc(size_t size) | 
|  | { | 
|  | if (err) { | 
|  | return __real_malloc(size); | 
|  | } | 
|  |  | 
|  | size_t new_size = adjust_size(size); | 
|  | void *ptr = __real_malloc(new_size); | 
|  | ptr = box(ptr, size); | 
|  | track(ptr); | 
|  | return ptr; | 
|  | } | 
|  |  | 
|  | void __wrap_free(void *ptr) | 
|  | { | 
|  | if (err) { | 
|  | __real_free(ptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | void *ret = __builtin_extract_return_addr(__builtin_return_address(0)); | 
|  |  | 
|  | untrack(ptr, ret); | 
|  |  | 
|  | /* Write garbage all over the region we were handed back to try to expose | 
|  | * use-after-free bugs. If we fault while doing this, it probably means the | 
|  | * user underran their buffer and overwrote the 'size' metadata. | 
|  | */ | 
|  | metadata_t *pre = (metadata_t *)(ptr - sizeof(*pre)); | 
|  | for (unsigned int i = 0; i < pre->size; i++) { | 
|  | ((char *)ptr)[i] ^= (char)~i; | 
|  | } | 
|  |  | 
|  | ptr = unbox(ptr, ret); | 
|  | __real_free(ptr); | 
|  | } | 
|  |  | 
|  | void *__wrap_calloc(size_t num, size_t size) | 
|  | { | 
|  | if (err) { | 
|  | return __real_calloc(num, size); | 
|  | } | 
|  |  | 
|  | size_t sz = adjust_size(num * size); | 
|  | size_t new_num = sz / size; | 
|  | if (sz % size != 0) { | 
|  | new_num++; | 
|  | } | 
|  |  | 
|  | void *ptr = __real_calloc(new_num, size); | 
|  | ptr = box(ptr, num * size); | 
|  | track(ptr); | 
|  | return ptr; | 
|  | } | 
|  |  | 
|  | void *__wrap_realloc(void *ptr, size_t size) | 
|  | { | 
|  | if (err) { | 
|  | return __real_realloc(ptr, size); | 
|  | } | 
|  |  | 
|  | void *ret = __builtin_extract_return_addr(__builtin_return_address(0)); | 
|  |  | 
|  | untrack(ptr, ret); | 
|  | ptr = unbox(ptr, ret); | 
|  | size_t new_size = adjust_size(size); | 
|  | ptr = __real_realloc(ptr, new_size); | 
|  | ptr = box(ptr, size); | 
|  | track(ptr); | 
|  | return ptr; | 
|  | } |