From 10a69ba23d6fdb7cd99a14282ff1e082cd094d23 Mon Sep 17 00:00:00 2001 From: omicron Date: Sat, 18 Oct 2025 23:53:20 +0200 Subject: [PATCH] Add memory allocation wrappers Provides a consistent interface to allocate, resize and free heap memory. --- src/common/memory.c | 109 ++++++++++++++++++++++++++++++++++++++++++++ src/common/memory.h | 68 +++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 src/common/memory.c create mode 100644 src/common/memory.h diff --git a/src/common/memory.c b/src/common/memory.c new file mode 100644 index 0000000..28c8453 --- /dev/null +++ b/src/common/memory.c @@ -0,0 +1,109 @@ +#include "memory.h" +#include "error.h" +#include +#include +#include +#include + +constexpr uint32_t static_canary_value = 0xFF000A00; + +/** + * Describes a block of memory and its meta data. The user will only ever see + * the data pointer. + * + * FIXME: It might be possible to ensure alignment of data here so that assuming + * the underlying malloc() call is aligned to 16 bytes we also guarantee + * alignment to 16 bytes. This is not currently implemented. + */ +typedef struct memory { + uint32_t canary; + size_t size; + uint8_t data[]; +} mem_t; + +static bool would_overflow_mul(size_t a, size_t b) { + if (a == 0 || b == 0) + return false; + return a > SIZE_MAX / b; +} + +static bool would_overflow_add(size_t a, size_t b) { + return a > SIZE_MAX - b; +} + +static inline mem_t *mem_get_metadata(void *user_ptr) { + return (mem_t *)((char *)user_ptr - sizeof(mem_t)); +} + +[[noreturn]] void die_with_ptr_msg(void *ptr, uint32_t canary) { + fprintf(stderr, "*** HEAP CORRUPTION at %p ***\n", ptr); + fprintf(stderr, "Unexpected heap canary value %08x\n", canary); + fflush(stderr); + abort(); +} + +error_t *mem_alloc(void **ptr, size_t n, size_t size) { + if (ptr == nullptr) + return err_invalid_parameters; + *ptr = nullptr; + if (n == 0 || size == 0) + return err_invalid_parameters; + if (would_overflow_mul(n, size)) + return err_integer_overflow; + size_t total = n * size; + if (would_overflow_add(total, sizeof(mem_t))) + return err_integer_overflow; + + mem_t *mem = malloc(total + sizeof(mem_t)); + if (mem == nullptr) + return err_allocation_failed; + + mem->canary = static_canary_value; + mem->size = total; + memset(&mem->data[0], 0, total); + + *ptr = &mem->data[0]; + return nullptr; +} + +error_t *mem_resize(void **ptr, size_t n, size_t size) { + if (ptr == nullptr || *ptr == nullptr) + return err_invalid_parameters; + if (n == 0 || size == 0) + return err_invalid_parameters; + + mem_t *mem = mem_get_metadata(*ptr); + if (mem->canary != static_canary_value) + die_with_ptr_msg(&mem->data[0], mem->canary); + + if (would_overflow_mul(n, size)) + return err_integer_overflow; + size_t total = n * size; + if (would_overflow_add(total, sizeof(mem_t))) + return err_integer_overflow; + + mem_t *new = realloc(mem, total + sizeof(mem_t)); + if (new == nullptr) + return err_allocation_failed; + if (new != mem) { + *ptr = &new->data[0]; + } + if (total > new->size) + memset(&new->data[new->size], 0, total - new->size); + new->size = total; + return nullptr; +} + +void mem_free(void **ptr) { + if (ptr == nullptr || *ptr == nullptr) { + return; + } + + mem_t *mem = mem_get_metadata(*ptr); + if (mem->canary != static_canary_value) + die_with_ptr_msg(&mem->data[0], mem->canary); + + mem->canary = 0; + free(mem); + *ptr = nullptr; +} diff --git a/src/common/memory.h b/src/common/memory.h new file mode 100644 index 0000000..6d917f1 --- /dev/null +++ b/src/common/memory.h @@ -0,0 +1,68 @@ +#ifndef INCLUDE_COMMON_MEMORY_H_ +#define INCLUDE_COMMON_MEMORY_H_ + +#include "error.h" + +/** + * Allocates n*size bytes of memory and initializes them to 0. + * + * Note that the allocated memory is not guaranteed to be 16 byte aligned. + * + * ptr (out): + * If successful, *ptr will contain the pointer to the allocated memory. Must + * be a valid pointer and specifically must not be nullptr. + * n: + * The amount of consecutive objects to allocate. Must not be 0. + * size: + * The size of objects to allocate. Must not be 0. + * return: + * - nullptr on success + * - err_allocation_failed when the memory cannot be allocated. + * - err_invalid_parameter when either n or size are 0 or ptr is nullptr. + * - err_integer_overflow when n*size overflows size_t. + */ +error_t *mem_alloc(void **ptr, size_t n, size_t size); + +/** + * Resizes previously allocated memory to n*size bytes. + * + * When resizing existing data is preserved but may be truncated if the new size + * is smaller. If the memory grows the newly allocated memory is initialized to + * 0. Note that the allocated memory is not guaranteed to be 16 byte aligned. + * + * If memory overflows happened or if invalid pointers are passed then this + * function may exit with abort() + * + * ptr (in, out): + * Pointer to the memory to resize. If successful, *ptr will contain the + * pointer to the resized memory (which may differ from the original). + * *ptr must have been allocated with mem_alloc or mem_resize. + * If the new size is larger, the additional bytes are initialized to 0. + * If this function returns an error, *ptr remains unchanged and points to + * the original, valid allocation. + * n: + * The amount of consecutive objects to allocate. Must not be 0. + * size: + * The size of objects to allocate. Must not be 0. + * return: + * - nullptr on success + * - err_allocation_failed when the memory cannot be allocated. + * - err_invalid_parameter when either n or size are 0, or *ptr is nullptr. + * - err_integer_overflow when n*size overflows size_t. + */ +error_t *mem_resize(void **ptr, size_t n, size_t size); + +/** + * Frees memory previously allocated with mem_alloc or mem_resize. + * + * If memory overflows happened or if invalid pointers are passed then this + * function may exit with abort() + * + * ptr (in, out) + * Pointer to the memory to free. If ptr and *ptr are not nullptr, *ptr will + * be set to nullptr after freeing. Both ptr and *ptr can be nullptr (no-op). + * *ptr must have been allocated with mem_alloc or mem_resize. + */ +void mem_free(void **ptr); + +#endif // INCLUDE_COMMON_MEMORY_H_