diff options
Diffstat (limited to 'libparc/parc/algol/parc_SafeMemory.c')
-rw-r--r-- | libparc/parc/algol/parc_SafeMemory.c | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/libparc/parc/algol/parc_SafeMemory.c b/libparc/parc/algol/parc_SafeMemory.c new file mode 100644 index 00000000..1f152af9 --- /dev/null +++ b/libparc/parc/algol/parc_SafeMemory.c @@ -0,0 +1,658 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is a substitute for posix_memalign(3) that + * establishes detectable boundaries around an allocated memory segment, + * records a stack backtrace for each allocation, + * detects buffer overruns and underruns by checking the boundaries when the memory is deallocated, + * and tries to prevent a stray pointer to reference the memory again once it's been deallocated. + * + * The allocated memory consists of three contiguous segments: the prefix, the memory usable by the caller, and the suffix. + * The memory usable by the caller is aligned as specified by the caller. + * The alignment must be a power of 2 greater than or equal to the size of a {@code void *}. + * <pre> + * +--base +-prefix +-- memory +-- suffix aligned on (void *) + * v v v v + * |________|PPPPPPPPPPPP|mmmmmmmmm...mmmm|___|SSSSSSSSS + * ^ + * +-- variable padding + * </pre> + * Where '-' indicates padding, 'P' indicates the prefix data structure, 'm' + * indicates contiguous memory for use by the caller, and 'S" indicates the suffix data structure. + * + */ +#include <config.h> + +#include <LongBow/runtime.h> + +#if defined(_WIN64) +# define backtrace(...) (0) +# define backtrace_symbols(...) 0 +# define backtrace_symbols_fd(...) ((void) 0) +#elif defined(_WIN32) +# define backtrace(...) (0) +# define backtrace_symbols(...) 0 +# define backtrace_symbols_fd(...) ((void) 0) +#elif defined(__ANDROID__) +# define backtrace(...) (0) +# define backtrace_symbols(...) 0 +# define backtrace_symbols_fd(...) ((void) 0) +#elif defined(__APPLE__) +# include <execinfo.h> +#elif defined(__linux) +# include <execinfo.h> +#elif defined(__unix) // all unices not caught above +# define backtrace(...) (0) +# define backtrace_symbols(...) 0 +# define backtrace_symbols_fd(...) ((void) 0) +#elif defined(__posix) +# include <execinfo.h> +#endif + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <sys/errno.h> +#include <sys/queue.h> +#include <pthread.h> + +#include <stdint.h> +#include <inttypes.h> +#include <stdbool.h> + +#include <parc/algol/parc_StdlibMemory.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_DisplayIndented.h> + +typedef struct memory_backtrace { + void **callstack; + int maximumFrameCount; + int actualFrameCount; +} _MemoryBacktrace; + +static const uint32_t _parcSafeMemory_SuffixGuard = 0xcafecafe; +typedef struct memory_suffix { + uint32_t guard; +} _MemorySuffix; + +static const uint64_t _parcSafeMemory_PrefixMagic = 0xfacefacefaceface; +static const uint64_t _parcSafeMemory_Guard = 0xdeaddeaddeaddead; +static const uint64_t _parcSafeMemory_GuardAlreadyFreed = 0xBADDCAFEBADDCAFE; +typedef struct memory_prefix { + uint64_t magic; // A magic number indicating the start of this data structure. + size_t requestedLength; // The number of bytes the caller requested. + size_t actualLength; // The number of bytes >= requestedLength to ensure the right alignment for the suffix. + size_t alignment; // The aligment required by the caller. Must be a power of 2 and >= sizeof(void *). + _MemoryBacktrace *backtrace; // A record of the caller's stack trace at the time of allocation. + uint64_t guard; // Try to detect underrun of the allocated memory. +} _MemoryPrefix; + +typedef void *PARCSafeMemoryOrigin; + +typedef void *PARCSafeMemoryUsable; + +static PARCMemoryInterface *_parcMemory = &PARCStdlibMemoryAsPARCMemory; + +static pthread_mutex_t _parcSafeMemory_Mutex = PTHREAD_MUTEX_INITIALIZER; + + +/** + * Return true if the given alignment value is greater than or equal to {@code sizeof(void *)} and + * is a power of 2. + * + * @param alignment + * @return + */ +static bool +_alignmentIsValid(size_t alignment) +{ + return alignment >= sizeof(void *) && (alignment & (~alignment + 1)) == alignment; +} + +/** + * Add two pointers arithmetically. + */ +// Should increment be a ssize_t, instead of size_t? +static void * +_pointerAdd(const void *base, const size_t increment) +{ + void *result = (void *) &((char *) base)[increment]; + return result; +} + +static size_t +_computePrefixLength(const size_t alignment) +{ + return (sizeof(_MemoryPrefix) + alignment - 1) & ~(alignment - 1); +} + +static size_t +_computeUsableMemoryLength(const size_t requestedLength, const size_t alignment) +{ + return (requestedLength + alignment - 1) & ~(alignment - 1); +} + +/** + * Compute the size of the suffix on an allocated chunk of managed memory that causes the + * first byte after this size to be aligned according to the given alignment value. + */ +static size_t +_computeSuffixLength(size_t alignment __attribute__((unused))) +{ + return sizeof(_MemorySuffix); +} + +/** + * Compute the total number of bytes necessary to store the entire Safe Memory structure. + * Given a size number of bytes for use by a client function, + * produce the total number of bytes necessary to store @p size + * number of bytes for use by a client function and the `_MemoryPrefix` + * and `_MemorySuffix` structures. + */ +static size_t +_computeMemoryTotalLength(size_t requestedLength, size_t alignment) +{ + size_t result = + _computePrefixLength(alignment) + + _computeUsableMemoryLength(requestedLength, sizeof(void*)) + + _computeSuffixLength(alignment); + + return result; +} + +/** + * Given the safe memory address, return a pointer to the _MemoryPrefix structure. + */ +static _MemoryPrefix * +_parcSafeMemory_GetPrefix(const PARCSafeMemoryUsable *usable) +{ + _MemoryPrefix *prefix = _pointerAdd(usable, -sizeof(_MemoryPrefix)); + return prefix; +} + +/** + * Given a base address for memory Return a pointer to the {@link _MemorySuffix} + * structure for the given base pointer to allocated memory. + * + * @param base + * @return + */ +static _MemorySuffix * +_parcSafeMemory_GetSuffix(const PARCSafeMemoryUsable *memory) +{ + _MemoryPrefix *prefix = _parcSafeMemory_GetPrefix(memory); + + _MemorySuffix *suffix = _pointerAdd(memory, prefix->actualLength); + return suffix; +} + +static PARCSafeMemoryState +_parcSafeMemory_GetPrefixState(const PARCSafeMemoryUsable *usable) +{ + _MemoryPrefix *prefix = _parcSafeMemory_GetPrefix(usable); + + if (prefix->guard == _parcSafeMemory_GuardAlreadyFreed) { + return PARCSafeMemoryState_ALREADYFREE; + } + if (prefix->guard != _parcSafeMemory_Guard) { + return PARCSafeMemoryState_UNDERRUN; + } + if (prefix->magic != _parcSafeMemory_PrefixMagic) { + return PARCSafeMemoryState_UNDERRUN; + } + if (!_alignmentIsValid(prefix->alignment)) { + return PARCSafeMemoryState_UNDERRUN; + } + + return PARCSafeMemoryState_OK; +} + +static PARCSafeMemoryOrigin * +_parcSafeMemory_GetOrigin(const PARCSafeMemoryUsable *memory) +{ + _MemoryPrefix *prefix = _parcSafeMemory_GetPrefix(memory); + + return _pointerAdd(memory, -_computePrefixLength(prefix->alignment)); +} + +static PARCSafeMemoryState +_parcSafeMemory_GetSuffixState(const PARCSafeMemoryUsable *memory) +{ + PARCSafeMemoryState result = PARCSafeMemoryState_OK;; + _MemorySuffix *suffix = _parcSafeMemory_GetSuffix(memory); + if (suffix->guard != _parcSafeMemory_SuffixGuard) { + result = PARCSafeMemoryState_OVERRUN; + } + return result; +} + +/** + * Given a pointer to the base address of an allocated memory segment, + * compute and return a pointer to the corresponding {@link _MemorySuffix} for that same memory. + */ +static _MemorySuffix * +_parcSafeMemory_FormatSuffix(const PARCSafeMemoryUsable *memory) +{ + _MemorySuffix *suffix = _parcSafeMemory_GetSuffix(memory); + + suffix->guard = _parcSafeMemory_SuffixGuard; + return suffix; +} + +static void +_backtraceReport(const _MemoryBacktrace *backtrace, int outputFd) +{ + if (outputFd != -1) { + // Ignore the first entry as it points to this function and we just need to start at the calling function. + backtrace_symbols_fd(&backtrace->callstack[1], backtrace->actualFrameCount - 1, outputFd); + } +} + +// This is a list of all memory allocations that were created by calls to Safe Memory. +// Each element of the list is the pointer to the result returned to the caller of the memory allocation, +// not a pointer to the base. +struct safememory_entry { + LIST_ENTRY(safememory_entry) entries; // List + void *memory; +}; +LIST_HEAD(, safememory_entry) head = LIST_HEAD_INITIALIZER(head); +static pthread_mutex_t head_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void +_parcSafeMemory_AddAllocation(void *memory) +{ + if (parcSafeMemory_Outstanding() == 0) { + LIST_INIT(&head); // Initialize the list. + } + + struct safememory_entry *e = malloc(sizeof(struct safememory_entry)); // Insert this at the head. + e->memory = memory; + + pthread_mutex_lock(&head_mutex); + LIST_INSERT_HEAD(&head, e, entries); + pthread_mutex_unlock(&head_mutex); +} + +static void +_parcSafeMemory_RemoveAllocation(void *memory) +{ + struct safememory_entry *e; + pthread_mutex_lock(&head_mutex); + LIST_FOREACH(e, &head, entries) + { + if (e->memory == memory) { + LIST_REMOVE(e, entries); + free(e); + pthread_mutex_unlock(&head_mutex); + return; + } + } + + pthread_mutex_unlock(&head_mutex); + fprintf(stderr, "parcSafeMemory_RemoveAllocation: Destroying memory (%p) which is NOT in the allocated memory record. Double free?\n", memory); +} + +static PARCSafeMemoryState +_parcSafeMemory_GetState(const PARCSafeMemoryUsable *memory) +{ + PARCSafeMemoryState prefixState = _parcSafeMemory_GetPrefixState(memory); + if (prefixState != PARCSafeMemoryState_OK) { + return prefixState; + } + return _parcSafeMemory_GetSuffixState(memory); +} + +static const char * +_parcSafeMemory_StateToString(PARCSafeMemoryState status) +{ + switch (status) { + case PARCSafeMemoryState_OK: return "OK"; + case PARCSafeMemoryState_MISMATCHED: return "MISMATCHED"; + case PARCSafeMemoryState_UNDERRUN: return "UNDERRUN"; + case PARCSafeMemoryState_OVERRUN: return "OVERRUN"; + case PARCSafeMemoryState_NOTHINGALLOCATED: return "NOTHINGALLOCATED"; + case PARCSafeMemoryState_ALREADYFREE: return "ALREADYFREE"; + } + return "?"; +} + +static void +_parcSafeMemory_Report(const PARCSafeMemoryUsable *safeMemory, int outputFd) +{ + _MemoryPrefix *prefix = _parcSafeMemory_GetPrefix(safeMemory); + + if (outputFd != -1) { + int charactersPrinted = dprintf(outputFd, "Memory %p (base %p) %s\n", + (void *) safeMemory, + (void *) prefix, + _parcSafeMemory_StateToString(_parcSafeMemory_GetState(safeMemory))); + trapUnexpectedStateIf(charactersPrinted < 0, "Cannot write to file descriptor %d", outputFd); + } + _backtraceReport(prefix->backtrace, outputFd); +} + +uint32_t +parcSafeMemory_ReportAllocation(int outputFd) +{ + uint32_t index = 0; + struct safememory_entry *e; + + pthread_mutex_lock(&head_mutex); + LIST_FOREACH(e, &head, entries) + { + _MemoryPrefix *prefix = _parcSafeMemory_GetPrefix(e->memory); + if (outputFd != -1) { + int charactersPrinted = dprintf(outputFd, + "\n%u SafeMemory@%p: %p={ .requestedLength=%zd, .actualLength=%zd, .alignment=%zd }\n", + index, e->memory, (void *) prefix, prefix->requestedLength, prefix->actualLength, prefix->alignment); + trapUnexpectedStateIf(charactersPrinted < 0, "Cannot write to file descriptor %d", outputFd) + { + pthread_mutex_unlock(&head_mutex); + } + } + _parcSafeMemory_Report(e->memory, outputFd); + index++; + } + pthread_mutex_unlock(&head_mutex); + return parcSafeMemory_Outstanding(); +} + +static void +_backtraceDestroy(_MemoryBacktrace **backtrace) +{ + free((*backtrace)->callstack); + free(*backtrace); + *backtrace = 0; +} + +static PARCSafeMemoryState +_parcSafeMemory_Destroy(void **memoryPointer) +{ + pthread_mutex_lock(&_parcSafeMemory_Mutex); + + if (parcSafeMemory_Outstanding() == 0) { + pthread_mutex_unlock(&_parcSafeMemory_Mutex); + return PARCSafeMemoryState_NOTHINGALLOCATED; + } + + _parcSafeMemory_RemoveAllocation(*memoryPointer); + + PARCSafeMemoryUsable *memory = *memoryPointer; + + PARCSafeMemoryState state = _parcSafeMemory_GetState(memory); + trapUnexpectedStateIf(state != PARCSafeMemoryState_OK, + "Expected PARCSafeMemoryState_OK, actual %s (see parc_SafeMemory.h)", + _parcSafeMemory_StateToString(state)) + { + pthread_mutex_unlock(&_parcSafeMemory_Mutex); + } + + _MemoryPrefix *prefix = _parcSafeMemory_GetPrefix(memory); + _backtraceDestroy(&prefix->backtrace); + + PARCSafeMemoryOrigin *base = _parcSafeMemory_GetOrigin(memory); + + memset(base, 0, _computeMemoryTotalLength(prefix->requestedLength, prefix->alignment)); + prefix->guard = _parcSafeMemory_GuardAlreadyFreed; + + ((PARCMemoryDeallocate *) _parcMemory->Deallocate)((void **) &base); + + *memoryPointer = 0; + + pthread_mutex_unlock(&_parcSafeMemory_Mutex); + return PARCSafeMemoryState_OK; +} + +__attribute__((unused)) +static void +_parcSafeMemory_DeallocateAll(void) +{ + struct safememory_entry *e; + + pthread_mutex_lock(&head_mutex); + LIST_FOREACH(e, &head, entries) + { + _parcSafeMemory_Destroy(&e->memory); + } + pthread_mutex_unlock(&head_mutex); +} + +static _MemoryBacktrace * +_backtraceCreate(int maximumFrameCount) +{ + _MemoryBacktrace *result = malloc(sizeof(_MemoryBacktrace)); + result->maximumFrameCount = maximumFrameCount; + result->callstack = calloc(result->maximumFrameCount, sizeof(void *)); + + result->actualFrameCount = backtrace(result->callstack, result->maximumFrameCount); + + return result; +} + +/** + * Format memory with a MemoryPrefix structure. + * + * @param origin The origin of the allocated memory (which is not the same as the start of usable memory). + * @param requestedLength The length of the extent of memory for general purpose use by the caller. + * @param alignment A power of 2 greater than or equal to {@code sizeof(void *)}. + * @return The pointer to the first address suitable for general purpose use by the caller. + */ +static PARCSafeMemoryUsable * +_parcSafeMemory_FormatPrefix(PARCSafeMemoryOrigin *origin, size_t requestedLength, size_t alignment) +{ + int backTraceDepth = 20; + + if (!_alignmentIsValid(alignment)) { + return NULL; + } + size_t prefixSize = _computePrefixLength(alignment); + + // This abuts the prefix to the user memory, it does not start at the beginning + // of the aligned prefix region. + _MemoryPrefix *prefix = (_MemoryPrefix *) (origin + (prefixSize - sizeof(_MemoryPrefix))); + + prefix->magic = _parcSafeMemory_PrefixMagic; + prefix->requestedLength = requestedLength; + prefix->actualLength = _computeUsableMemoryLength(requestedLength, sizeof(void*)); + prefix->alignment = alignment; + prefix->backtrace = _backtraceCreate(backTraceDepth); + prefix->guard = _parcSafeMemory_Guard; + + PARCSafeMemoryUsable *result = _pointerAdd(origin, prefixSize); + + assertAligned(result, alignment, "Return value is not properly aligned to %zu", alignment); + return result; +} + +/** + * Given a pointer to allocated memory and the length of bytes that will be used by the caller, + * format the prefix and suffix structures returning a pointer to the first properly aligned + * byte available to the client function. + */ +static void * +_parcSafeMemory_FormatMemory(PARCSafeMemoryOrigin *origin, size_t length, size_t alignment) +{ + PARCSafeMemoryUsable *memory = _parcSafeMemory_FormatPrefix(origin, length, alignment); + if (memory != NULL) { + _parcSafeMemory_FormatSuffix(memory); + } + + return memory; +} + +int +parcSafeMemory_MemAlign(void **memptr, size_t alignment, size_t requestedSize) +{ + if (!_alignmentIsValid(alignment)) { + return EINVAL; + } + if (requestedSize == 0) { + return EINVAL; + } + + size_t totalSize = _computeMemoryTotalLength(requestedSize, alignment); + if (totalSize < requestedSize) { + return ERANGE; + } + + pthread_mutex_lock(&_parcSafeMemory_Mutex); + + void *base; + int failure = ((PARCMemoryMemAlign *) _parcMemory->MemAlign)(&base, alignment, totalSize); + + if (failure != 0 || base == NULL) { + pthread_mutex_unlock(&_parcSafeMemory_Mutex); + return ENOMEM; + } + + *memptr = _parcSafeMemory_FormatMemory(base, requestedSize, alignment); + + _parcSafeMemory_AddAllocation(*memptr); + pthread_mutex_unlock(&_parcSafeMemory_Mutex); + + return 0; +} + +void * +parcSafeMemory_Allocate(size_t requestedSize) +{ + void *result = NULL; + + if (requestedSize != 0) { + size_t totalSize = _computeMemoryTotalLength(requestedSize, sizeof(void *)); + + if (totalSize >= requestedSize) { + pthread_mutex_lock(&_parcSafeMemory_Mutex); + + void *base = ((PARCMemoryAllocate *) _parcMemory->Allocate)(totalSize); + if (base != NULL) { + result = _parcSafeMemory_FormatMemory(base, requestedSize, sizeof(void *)); + + _parcSafeMemory_AddAllocation(result); + } + pthread_mutex_unlock(&_parcSafeMemory_Mutex); + } + } + return result; +} + +void * +parcSafeMemory_AllocateAndClear(size_t requestedSize) +{ + void *memptr = parcSafeMemory_Allocate(requestedSize); + if (memptr != NULL) { + memset(memptr, 0, requestedSize); + } + return memptr; +} + +bool +parcSafeMemory_IsValid(const void *memory) +{ + bool result = true; + + PARCSafeMemoryState state = _parcSafeMemory_GetState(memory); + if (state != PARCSafeMemoryState_OK) { + return false; + } + + return result; +} + + +uint32_t +parcSafeMemory_Outstanding(void) +{ + return ((PARCMemoryOutstanding *) _parcMemory->Outstanding)(); +} + +void * +parcSafeMemory_Reallocate(void *original, size_t newSize) +{ + void *result; + + result = parcSafeMemory_Allocate(newSize); + + if (original == NULL) { + return result; + } + + if (result != NULL) { + _MemoryPrefix *prefix = _parcSafeMemory_GetPrefix(original); + size_t originalSize = prefix->requestedLength; + + memcpy(result, original, originalSize); + parcSafeMemory_Deallocate(&original); + } + return result; +} + +char * +parcSafeMemory_StringDuplicate(const char *string, size_t length) +{ + size_t actualLength = strlen(string); + if (length < actualLength) { + actualLength = length; + } + + char *result = parcSafeMemory_Allocate(actualLength + 1); + if (result != NULL) { + memcpy(result, string, actualLength); + result[actualLength] = 0; + } + return result; +} + +void +parcSafeMemory_Deallocate(void **pointer) +{ + _parcSafeMemory_Destroy(pointer); +} + +void +parcSafeMemory_Display(const void *memory, int indentation) +{ + if (memory == NULL) { + parcDisplayIndented_PrintLine(indentation, "PARCSafeMemory@NULL"); + } else { + _MemoryPrefix *prefix = _parcSafeMemory_GetPrefix(memory); + + parcDisplayIndented_PrintLine(indentation, "PARCSafeMemory@%p {", (void *) memory); + parcDisplayIndented_PrintLine(indentation + 1, + "%p=[ magic=0x%" PRIx64 " requestedLength=%zd, actualLength=%zd, alignment=%zd, guard=0x%" PRIx64 "]", + _parcSafeMemory_GetOrigin(memory), + prefix->magic, + prefix->requestedLength, + prefix->actualLength, + prefix->alignment, + prefix->guard); + + parcDisplayIndented_PrintMemory(indentation + 1, prefix->requestedLength, memory); + + parcDisplayIndented_PrintLine(indentation, "}"); + } +} + +PARCMemoryInterface PARCSafeMemoryAsPARCMemory = { + .Allocate = (uintptr_t) parcSafeMemory_Allocate, + .AllocateAndClear = (uintptr_t) parcSafeMemory_AllocateAndClear, + .MemAlign = (uintptr_t) parcSafeMemory_MemAlign, + .Deallocate = (uintptr_t) parcSafeMemory_Deallocate, + .Reallocate = (uintptr_t) parcSafeMemory_Reallocate, + .Outstanding = (uintptr_t) parcSafeMemory_Outstanding, + .StringDuplicate = (uintptr_t) parcSafeMemory_StringDuplicate +}; |