From 65460fc99d4257fed9bc437fad6ae190935c4e68 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sun, 30 Aug 2015 19:32:55 +0200 Subject: malloc: add overflow checks and add aligned alloc-functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- include/malloc.h | 129 ++++++++++++++++++++++++++++++++++++- src/malloc.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/slibc-alloc.c | 11 ++-- 3 files changed, 313 insertions(+), 12 deletions(-) diff --git a/include/malloc.h b/include/malloc.h index c18f885..0e17817 100644 --- a/include/malloc.h +++ b/include/malloc.h @@ -97,7 +97,8 @@ void* realloc(void*, size_t) * beginning of a memory allocation on the heap. * However, if it is `NULL`, nothing will happen. */ -void free(void*) __slibc_warning("Use 'fast_free' or 'secure_free' instead."); +void free(void*) + __slibc_warning("Use 'fast_free' or 'secure_free' instead."); /** * This function is identical to `free`. @@ -112,7 +113,131 @@ void free(void*) __slibc_warning("Use 'fast_free' or 'secure_free' instead."); * However, if it is `NULL`, nothing will happen. */ #ifndef _PORTABLE_SOURCE -void cfree(void*, ...) __deprecated("'cfree' is deprecated and not portable, use 'free' instead."); +void cfree(void*, ...) + __deprecated("'cfree' is deprecated and not portable, use 'free' instead."); +#endif + + +#ifndef _PORTABLE_SOURCE +/** + * Variant of `malloc` that returns an address with a + * specified alignment. + * + * It is unspecified how the function works. This implemention + * will allocate a bit of extra memory and shift the returned + * pointer so that it is aligned. + * + * As a GNU-compliant slibc extension, memory allocated + * with this function can be freed with `free`. + * + * @param boundary The alignment. + * @param size The number of bytes to allocated. + * @return Pointer to the beginning of the new allocation. + * If `size` is zero, this function will either return + * `NULL` (that is what this implement does) or return + * a unique pointer that can later be freed with `free`. + * `NULL` is returned on error, and `errno` is set to + * indicate the error. + * + * @throws ENOMEM The process cannot allocate more memory. + * @throws EINVAL If `boundary` is not a power of two. + */ +void* memalign(size_t, size_t) +#ifdef __C11__ + __deprecated("'memalign' has be deprecated by 'aligned_alloc' in C11.") +#endif + __GCC_ONLY(__attribute__((malloc, warn_unused_result))); +#endif + +/** + * `posix_memalign(p, b, n)` is equivalent to + * `(*p = memalign(b, n), *p ? 0 : errno)`, except + * `boundary` must also be a multiple of `sizeof(void*)`, + * and `errno` is unspecified. + * + * As a GNU-compliant slibc extension, memory allocated + * with this function can be freed with `free`. + * + * @param ptrptr Output parameter for the allocated memory. + * @param boundary The alignment. + * @param size The number of bytes to allocated. + * @return Zero on success, a value for `errno` on error. + * + * @throws ENOMEM The process cannot allocate more memory. + * @throws EINVAL If `boundary` is not a power-of-two multiple of `sizeof(void*)`. + */ +int posix_memalign(void**, size_t, size_t) + __GCC_ONLY(__attribute__((nonnull))); + +#ifndef _PORTABLE_SOURCE +/** + * `valloc(n)` is equivalent to `memalign(sysconf(_SC_PAGESIZE), n)`. + * + * As a GNU-compliant slibc extension, memory allocated + * with this function can be freed with `free`. + * + * @param size The number of bytes to allocated. + * @return Pointer to the beginning of the new allocation. + * If `size` is zero, this function will either return + * `NULL` (that is what this implement does) or return + * a unique pointer that can later be freed with `free`. + * `NULL` is returned on error, and `errno` is set to + * indicate the error. + * + * @throws ENOMEM The process cannot allocate more memory. + */ +void* valloc(size_t) + __GCC_ONLY(__attribute__((malloc, warn_unused_result))) + __deprecated("'valloc' is deprecated, use 'memalign' or 'posix_memalign' instead."); + +#ifdef _GNU_SOURCE +/** + * This function works like `valloc`, except the allocation size, + * including auxiliary space, is rounded up to the next multiple + * of the page size. + * + * @param size The number of bytes to allocated. + * @return Pointer to the beginning of the new allocation. + * If `size` is zero, this function will either return + * `NULL` (that is what this implement does) or return + * a unique pointer that can later be freed with `free`. + * `NULL` is returned on error, and `errno` is set to + * indicate the error. + * + * @throws ENOMEM The process cannot allocate more memory. + */ +void* pvalloc(size_t) + __GCC_ONLY(__attribute__((malloc, warn_unused_result))) + __deprecated("'pvalloc' is deprecated, use 'memalign' or 'posix_memalign' instead."); +#endif +#endif + +#ifdef __C11__ +/** + * This function is identical to `memalign`, + * except it can be freed with `free`. + * + * Variant of `malloc` that returns an address with a + * specified alignment. + * + * It is unspecified how the function works. This implemention + * will allocate a bit of extra memory and shift the returned + * pointer so that it is aligned. + * + * @param boundary The alignment. + * @param size The number of bytes to allocated. + * @return Pointer to the beginning of the new allocation. + * If `size` is zero, this function will either return + * `NULL` (that is what this implement does) or return + * a unique pointer that can later be freed with `free`. + * `NULL` is returned on error, and `errno` is set to + * indicate the error. + * + * @throws ENOMEM The process cannot allocate more memory. + * @throws EINVAL If `boundary` is not a power of two. + */ +void* aligned_alloc(size_t, size_t) + __GCC_ONLY(__attribute__((malloc, warn_unused_result))); #endif diff --git a/src/malloc.c b/src/malloc.c index 82a6f54..f7a2e95 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include @@ -40,12 +42,19 @@ void* malloc(size_t size) { /* TODO implement implementation of malloc */ char* ptr; + size_t full_size; + if (size == 0) return NULL; - ptr = mmap(NULL, sizeof(size_t) + size, (PROT_READ | PROT_WRITE), + if (__builtin_uaddl_overflow(2 * sizeof(size_t), size, &full_size)) + return errno = ENOMEM, NULL; + + ptr = mmap(NULL, full_size, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANONYMOUS), -1, 0); - *(size_t*)ptr = size; - return ptr + sizeof(size_t); + + ((size_t*)ptr)[0] = size; + ((size_t*)ptr)[1] = 0; + return ptr + 2 * sizeof(size_t); } @@ -68,9 +77,16 @@ void* malloc(size_t size) */ void* calloc(size_t elem_count, size_t elem_size) { - void* ptr = malloc(elem_count * elem_size); + void* ptr; + size_t size; + + if (__builtin_umull_overflow(elem_count, elem_size, &size)) + return errno = ENOMEM, NULL; + + ptr = malloc(size); if (ptr != NULL) - explicit_bzero(ptr, elem_count * elem_size); + explicit_bzero(ptr, size); + return ptr; } @@ -132,3 +148,162 @@ void cfree(void* ptr, ...) fast_free(ptr); } + +/** + * Variant of `malloc` that returns an address with a + * specified alignment. + * + * It is unspecified how the function works. This implemention + * will allocate a bit of extra memory and shift the returned + * pointer so that it is aligned. + * + * As a GNU-compliant slibc extension, memory allocated + * with this function can be freed with `free`. + * + * @param boundary The alignment. + * @param size The number of bytes to allocated. + * @return Pointer to the beginning of the new allocation. + * If `size` is zero, this function will either return + * `NULL` (that is what this implement does) or return + * a unique pointer that can later be freed with `free`. + * `NULL` is returned on error, and `errno` is set to + * indicate the error. + * + * @throws ENOMEM The process cannot allocate more memory. + * @throws EINVAL If `boundary` is not a power of two. + */ +void* memalign(size_t boundary, size_t size) +{ + char* ptr; + size_t full_size; + size_t address; + size_t shift = 0; + + if (!boundary || (__builtin_ffsl(boundary) != boundary)) + return errno = EINVAL, NULL; + if (__builtin_uaddl_overflow(boundary - 1, size, &full_size)) + return errno = ENOMEM, NULL; + + ptr = malloc(full_size); + if (ptr == NULL) + return NULL; + + address = (size_t)ptr; + if (address % boundary != 0) + { + shift = boundary - (address % boundary); + ptr += shift; + *(size_t*)(ptr - sizeof(size_t)) = shift; + } + + return ptr; +} + + +/** + * `posix_memalign(p, b, n)` is equivalent to + * `(*p = memalign(b, n), *p ? 0 : errno)`, except + * `boundary` must also be a multiple of `sizeof(void*)`, + * and `errno` is unspecified. + * + * As a GNU-compliant slibc extension, memory allocated + * with this function can be freed with `free`. + * + * @param ptrptr Output parameter for the allocated memory. + * @param boundary The alignment. + * @param size The number of bytes to allocated. + * @return Zero on success, a value for `errno` on error. + * + * @throws ENOMEM The process cannot allocate more memory. + * @throws EINVAL If `boundary` is not a power-of-two multiple of `sizeof(void*)`. + */ +int posix_memalign(void** ptrptr, size_t boundary, size_t size) +{ + if (boundary < sizeof(void*)) + return EINVAL; + *ptrptr = memalign(boundary, size); + return *ptrptr ? 0 : errno; +} + + +/** + * `valloc(n)` is equivalent to `memalign(sysconf(_SC_PAGESIZE), n)`. + * + * As a GNU-compliant slibc extension, memory allocated + * with this function can be freed with `free`. + * + * @param size The number of bytes to allocated. + * @return Pointer to the beginning of the new allocation. + * If `size` is zero, this function will either return + * `NULL` (that is what this implement does) or return + * a unique pointer that can later be freed with `free`. + * `NULL` is returned on error, and `errno` is set to + * indicate the error. + * + * @throws ENOMEM The process cannot allocate more memory. + */ +void* valloc(size_t size) +{ + return memalign((size_t)sysconf(_SC_PAGESIZE), size); +} + + +/** + * This function works like `valloc`, except the allocation size, + * including auxiliary space, is rounded up to the next multiple + * of the page size. + * + * @param size The number of bytes to allocated. + * @return Pointer to the beginning of the new allocation. + * If `size` is zero, this function will either return + * `NULL` (that is what this implement does) or return + * a unique pointer that can later be freed with `free`. + * `NULL` is returned on error, and `errno` is set to + * indicate the error. + * + * @throws ENOMEM The process cannot allocate more memory. + */ +void* pvalloc(size_t size) +{ + size_t boundary = (size_t)sysconf(_SC_PAGESIZE); + size_t full_size = 2 * sizeof(size_t) + boundary - 1 + size; + size_t rounding = 0; + + if (full_size % boundary != 0) + rounding = boundary - (full_size % boundary); + + if (__builtin_uaddl_overflow(size, rounding, &full_size)) + return errno = ENOMEM, NULL; + + return memalign(boundary, full_size); +} + + +/** + * This function is identical to `memalign`, + * except it can be freed with `free`. + * + * Variant of `malloc` that returns an address with a + * specified alignment. + * + * It is unspecified how the function works. This implemention + * will allocate a bit of extra memory and shift the returned + * pointer so that it is aligned. + * + * @param boundary The alignment. + * @param size The number of bytes to allocated. + * @return Pointer to the beginning of the new allocation. + * If `size` is zero, this function will either return + * `NULL` (that is what this implement does) or return + * a unique pointer that can later be freed with `free`. + * `NULL` is returned on error, and `errno` is set to + * indicate the error. + * + * @throws ENOMEM The process cannot allocate more memory. + * @throws EINVAL If `boundary` is not a power of two. + */ +void* aligned_alloc(size_t boundary, size_t size) +{ + return memalign(boundary, size); +} + diff --git a/src/slibc-alloc.c b/src/slibc-alloc.c index 0cbf623..08e6209 100644 --- a/src/slibc-alloc.c +++ b/src/slibc-alloc.c @@ -23,8 +23,9 @@ -#define PURE_ALLOC(p) (((char*)(p)) - sizeof(size_t)) -#define PURE_SIZE(z) ((z) + sizeof(size_t)) +#define __ALIGN(p) (*(size_t*)(((char*)(p)) - sizeof(size_t))) +#define PURE_ALLOC(p) (((char*)(p)) - (__ALIGN(p) + 2 * sizeof(size_t))) +#define PURE_SIZE(p) (*(size_t*)PURE_ALLOC(p) + 2 * sizeof(size_t)) @@ -38,7 +39,7 @@ void fast_free(void* segment) { if (segument == NULL) return; - munmap(PURE_ALLOC(segment), PURE_SIZE(*(size_t*)segment)); + munmap(PURE_ALLOC(segment), PURE_SIZE(segment)); } @@ -52,7 +53,7 @@ void secure_free(void* segment) { if (segument == NULL) return; - explicit_bzero(PURE_ALLOC(segment), PURE_SIZE(allocsize(segment))); + explicit_bzero(PURE_ALLOC(segment), PURE_SIZE(segment)); fast_free(segment); } @@ -105,7 +106,7 @@ size_t allocsize(void* segment) if (new_ptr != ptr) \ { \ if (CLEAR_FREE) \ - explicit_bzero(PURE_ALLOC(ptr), PURE_SIZE(old_size)); \ + explicit_bzero(PURE_ALLOC(ptr), PURE_SIZE(ptr)); \ fast_free(new_ptr); \ } \ \ -- cgit v1.2.3-70-g09d2