From dac6058ab99a5a07728d785b7d50fd5cdac7e340 Mon Sep 17 00:00:00 2001 From: struct Date: Sat, 14 Mar 2026 14:52:28 -0400 Subject: [PATCH 1/2] Add new build option to not abort when we dont own a pointer, and build target that enables it --- Makefile | 19 +++++++++-- include/iso_alloc_hook.h | 36 ++++++++++++++++++++ src/iso_alloc.c | 18 +++++++++- src/iso_alloc_interfaces.c | 6 ++++ src/malloc_hook.c | 70 +++++++++++++++++--------------------- 5 files changed, 107 insertions(+), 42 deletions(-) create mode 100644 include/iso_alloc_hook.h diff --git a/Makefile b/Makefile index b45fb43..6041ed4 100644 --- a/Makefile +++ b/Makefile @@ -170,6 +170,10 @@ NAMED_MAPPINGS = -DNAMED_MAPPINGS=0 ## Abort when the allocator cannot return a valid chunk ABORT_ON_NULL = -DABORT_ON_NULL=0 +## Abort when we detect a pointer not owned by IsoAlloc +## Recommend setting this to 0 when using LD_PRELOAD +ABORT_ON_UNOWNED_PTR = -DABORT_ON_UNOWNED_PTR=1 + ## Enable protection against misusing 0 sized allocations NO_ZERO_ALLOCATIONS = -DNO_ZERO_ALLOCATIONS=1 @@ -316,7 +320,7 @@ BUILD_ERROR_FLAGS := $(BUILD_ERROR_FLAGS) -Wno-attributes -Wno-unused-variable endif CFLAGS += $(COMMON_CFLAGS) $(DISABLE_CANARY) $(BUILD_ERROR_FLAGS) $(HOOKS) $(HEAP_PROFILER) -fvisibility=hidden \ -std=$(STDC) $(SANITIZER_SUPPORT) $(ALLOC_SANITY) $(MEMCPY_SANITY) $(UNINIT_READ_SANITY) $(CPU_PIN) $(SCHED_GETCPU) \ - $(EXPERIMENTAL) $(UAF_PTR_PAGE) $(VERIFY_FREE_BIT_SLOTS) $(NAMED_MAPPINGS) $(ABORT_ON_NULL) $(NO_ZERO_ALLOCATIONS) \ + $(EXPERIMENTAL) $(UAF_PTR_PAGE) $(VERIFY_FREE_BIT_SLOTS) $(NAMED_MAPPINGS) $(ABORT_ON_NULL) $(ABORT_ON_UNOWNED_PTR) $(NO_ZERO_ALLOCATIONS) \ $(ABORT_NO_ENTROPY) $(ISO_DTOR_CLEANUP) $(RANDOMIZE_FREELIST) $(USE_SPINLOCK) $(HUGE_PAGES) ${THP_PAGES} $(USE_MLOCK) \ $(MEMORY_TAGGING) $(STRONG_SIZE_ISOLATION) $(MEMSET_SANITY) $(AUTO_CTOR_DTOR) $(SIGNAL_HANDLER) \ $(BIG_ZONE_META_DATA_GUARD) $(BIG_ZONE_GUARD) $(PROTECT_UNUSED_BIG_ZONE) $(MASK_PTRS) $(SANITIZE_CHUNKS) $(FUZZ_MODE) \ @@ -328,7 +332,7 @@ GDB_FLAGS = -g -ggdb3 -fno-omit-frame-pointer PERF_FLAGS = -pg -DPERF_TEST_BUILD=1 LIBRARY = -fPIC -shared SRC_DIR = src -C_SRCS = $(SRC_DIR)/*.c +C_SRCS = $(filter-out $(SRC_DIR)/malloc_hook.c, $(wildcard $(SRC_DIR)/*.c)) CXX_SRCS = $(SRC_DIR)/*.cpp ISO_ALLOC_PRINTF_SRC = $(SRC_DIR)/iso_alloc_printf.c BUILD_DIR = build @@ -342,6 +346,16 @@ library: clean $(CC) $(CFLAGS) $(LIBRARY) $(OPTIMIZE) $(OS_FLAGS) $(C_SRCS) -o $(BUILD_DIR)/$(LIBNAME) $(STRIP) +## Build a release library suitable for LD_PRELOAD use. +## ABORT_ON_UNOWNED_PTR=0 silently drops pointers not owned by isoalloc +## (e.g. those allocated by libc before the isoalloc constructor fires) +## instead of aborting. All other flags are identical to 'library'. +library_perf: ABORT_ON_UNOWNED_PTR = -DABORT_ON_UNOWNED_PTR=0 +library_perf: clean + @echo "make library_perf" + $(CC) $(CFLAGS) $(LIBRARY) $(OPTIMIZE) $(OS_FLAGS) $(C_SRCS) -o $(BUILD_DIR)/$(LIBNAME) + $(STRIP) + ## Build a debug version of the library library_debug: clean @echo "make library debug" @@ -410,6 +424,7 @@ tests: clean library_debug_unit_tests $(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(OS_FLAGS) tests/pool_test.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/pool_test $(LDFLAGS) utils/run_tests.sh + mte_test: clean @echo "make mte_test" $(CC) $(CFLAGS) $(C_SRCS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(EXE_CFLAGS) $(OS_FLAGS) tests/tests.c -o $(BUILD_DIR)/tests diff --git a/include/iso_alloc_hook.h b/include/iso_alloc_hook.h new file mode 100644 index 0000000..3373781 --- /dev/null +++ b/include/iso_alloc_hook.h @@ -0,0 +1,36 @@ +/* iso_alloc_hook.h - A secure memory allocator + * Copyright 2023 - chris.rohlf@gmail.com */ + +#pragma once + +/* Use direct function aliasing on GCC >= 9 and Clang >= 10. + * This makes malloc/free/calloc/realloc etc. linker-level aliases + * for their iso_ counterparts rather than thin wrapper functions. + * + * Benefits over wrapper functions: + * - copy(fun) propagates all function attributes (malloc, alloc_size, + * nothrow, etc.) from the iso_ target to the exported public symbol + * - No wrapper call overhead; same code, two symbol names + * + * Falls back to inline wrapper bodies on older compilers. + * + * Note: alias() targets must live in the same final DSO. Both + * malloc_hook.c and iso_alloc_interfaces.c are compiled into + * libisoalloc.so, so the linker resolves the alias within the DSO. */ + +#if !defined(__APPLE__) && ((defined(__GNUC__) && __GNUC__ >= 9) || (defined(__clang__) && __clang_major__ >= 10)) +#pragma GCC diagnostic ignored "-Wattributes" +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic ignored "-Wattribute-alias" +#endif +#define ISO_FORWARD(fun) __attribute__((alias(#fun), used, visibility("default"), copy(fun))); +#define ISO_FORWARD0(fun, x) ISO_FORWARD(fun) +#define ISO_FORWARD1(fun, x) ISO_FORWARD(fun) +#define ISO_FORWARD2(fun, x, y) ISO_FORWARD(fun) +#define ISO_FORWARD3(fun, x, y, z) ISO_FORWARD(fun) +#else +#define ISO_FORWARD0(fun, x) { fun(x); } +#define ISO_FORWARD1(fun, x) { return fun(x); } +#define ISO_FORWARD2(fun, x, y) { return fun(x, y); } +#define ISO_FORWARD3(fun, x, y, z) { return fun(x, y, z); } +#endif diff --git a/src/iso_alloc.c b/src/iso_alloc.c index 5f7415a..aff0d77 100644 --- a/src/iso_alloc.c +++ b/src/iso_alloc.c @@ -1615,7 +1615,11 @@ INTERNAL_HIDDEN void _iso_free_size(void *p, size_t size) { iso_alloc_big_zone_t *big_zone = iso_find_big_zone(p, true); if(UNLIKELY(big_zone == NULL)) { +#if ABORT_ON_UNOWNED_PTR LOG_AND_ABORT("Could not find any zone for allocation at 0x%p", p); +#else + return; +#endif } if(UNLIKELY(big_zone->size < size)) { @@ -1631,7 +1635,12 @@ INTERNAL_HIDDEN void _iso_free_size(void *p, size_t size) { iso_alloc_zone_t *zone = iso_find_zone_range(p); if(UNLIKELY(zone == NULL)) { +#if ABORT_ON_UNOWNED_PTR LOG_AND_ABORT("Could not find zone for 0x%p", p); +#else + UNLOCK_ROOT(); + return; +#endif } /* We can't check for an exact size match because @@ -1724,7 +1733,11 @@ INTERNAL_HIDDEN iso_alloc_zone_t *_iso_free_internal_unlocked(void *p, bool perm iso_alloc_big_zone_t *big_zone = iso_find_big_zone(p, true); if(UNLIKELY(big_zone == NULL)) { +#if ABORT_ON_UNOWNED_PTR LOG_AND_ABORT("Could not find any zone for allocation at 0x%p", p); +#else + return NULL; +#endif } iso_free_big_zone(big_zone, permanent); @@ -2044,7 +2057,6 @@ INTERNAL_HIDDEN size_t _iso_chunk_size(void *p) { LOCK_ROOT(); - /* We cannot return NULL here, we abort instead */ iso_alloc_zone_t *zone = iso_find_zone_range(p); if(UNLIKELY(zone == NULL)) { @@ -2052,7 +2064,11 @@ INTERNAL_HIDDEN size_t _iso_chunk_size(void *p) { iso_alloc_big_zone_t *big_zone = iso_find_big_zone(p, false); if(UNLIKELY(big_zone == NULL)) { +#if ABORT_ON_UNOWNED_PTR LOG_AND_ABORT("Could not find any zone for allocation at 0x%p", p); +#else + return 0; +#endif } return big_zone->size; diff --git a/src/iso_alloc_interfaces.c b/src/iso_alloc_interfaces.c index 7db679e..65d4c9b 100644 --- a/src/iso_alloc_interfaces.c +++ b/src/iso_alloc_interfaces.c @@ -319,3 +319,9 @@ EXTERNAL_API FLATTEN void iso_alloc_search_stack(void *p) { _iso_alloc_search_stack(p); } #endif + +/* Include malloc hooks here so alias targets (iso_alloc, iso_free, etc.) + * are defined in the same translation unit. See malloc_hook.c for details. */ +#define ISO_IN_INTERFACES_C +#include "malloc_hook.c" +#undef ISO_IN_INTERFACES_C diff --git a/src/malloc_hook.c b/src/malloc_hook.c index b383d42..1151fd4 100644 --- a/src/malloc_hook.c +++ b/src/malloc_hook.c @@ -1,8 +1,15 @@ /* malloc_hook.c - Provides low level hooks for malloc/free * Copyright 2023 - chris.rohlf@gmail.com */ -#include "iso_alloc.h" -#include "iso_alloc_internal.h" +/* This file must be included from iso_alloc_interfaces.c so that + * alias targets (iso_alloc, iso_free, etc.) are defined in the same + * translation unit. When compiled directly the guard + * below produces an empty translation unit with no effect. */ +#if !defined(ISO_IN_INTERFACES_C) +#error "malloc_hook.c must be included from iso_alloc_interfaces.c (so aliases can work)" +#endif + +#include "iso_alloc_hook.h" /* The MALLOC_HOOK configuration allows us to hook the usual * malloc interfaces and redirect them to the iso_alloc API. @@ -13,45 +20,31 @@ */ #if MALLOC_HOOK -EXTERNAL_API void *__libc_malloc(size_t s) { - return iso_alloc(s); -} +/* malloc/free/calloc/realloc and their __libc_ variants are + * direct linker aliases for the iso_ equivalents on GCC >= 9 + * and Clang >= 10 (see iso_alloc_hook.h). This propagates all + * function attributes (malloc, alloc_size, nothrow, etc.) from + * the iso_ symbol to the exported symbol via copy(fun), and + * eliminates the wrapper call entirely. + * + * Functions with differing signatures or custom logic (posix_memalign, + * aligned_alloc, memalign, malloc_size, malloc_good_size) remain + * as wrapper functions. */ -EXTERNAL_API void *malloc(size_t s) { - return iso_alloc(s); -} +EXTERNAL_API void *__libc_malloc(size_t s) ISO_FORWARD1(iso_alloc, s) +EXTERNAL_API void *malloc(size_t s) ISO_FORWARD1(iso_alloc, s) -EXTERNAL_API void __libc_free(void *p) { - iso_free(p); -} +EXTERNAL_API void __libc_free(void *p) ISO_FORWARD0(iso_free, p) +EXTERNAL_API void free(void *p) ISO_FORWARD0(iso_free, p) -EXTERNAL_API void free(void *p) { - iso_free(p); -} +EXTERNAL_API void *__libc_calloc(size_t n, size_t s) ISO_FORWARD2(iso_calloc, n, s) +EXTERNAL_API void *calloc(size_t n, size_t s) ISO_FORWARD2(iso_calloc, n, s) -EXTERNAL_API void *__libc_calloc(size_t n, size_t s) { - return iso_calloc(n, s); -} +EXTERNAL_API void *__libc_realloc(void *p, size_t s) ISO_FORWARD2(iso_realloc, p, s) +EXTERNAL_API void *realloc(void *p, size_t s) ISO_FORWARD2(iso_realloc, p, s) -EXTERNAL_API void *calloc(size_t n, size_t s) { - return iso_calloc(n, s); -} - -EXTERNAL_API void *__libc_realloc(void *p, size_t s) { - return iso_realloc(p, s); -} - -EXTERNAL_API void *realloc(void *p, size_t s) { - return iso_realloc(p, s); -} - -EXTERNAL_API void *__libc_reallocarray(void *p, size_t n, size_t s) { - return iso_reallocarray(p, n, s); -} - -EXTERNAL_API void *reallocarray(void *p, size_t n, size_t s) { - return iso_reallocarray(p, n, s); -} +EXTERNAL_API void *__libc_reallocarray(void *p, size_t n, size_t s) ISO_FORWARD3(iso_reallocarray, p, n, s) +EXTERNAL_API void *reallocarray(void *p, size_t n, size_t s) ISO_FORWARD3(iso_reallocarray, p, n, s) EXTERNAL_API int __posix_memalign(void **r, size_t a, size_t s) { if(is_pow2(a) == false) { @@ -104,9 +97,8 @@ EXTERNAL_API size_t malloc_good_size(size_t size) { return ALIGN_SZ_UP(size); } #else -EXTERNAL_API size_t malloc_usable_size(void *ptr) { - return iso_chunksz(ptr); -} +/* On Linux (non-Android) malloc_usable_size takes void* matching iso_chunksz */ +EXTERNAL_API size_t malloc_usable_size(void *ptr) ISO_FORWARD1(iso_chunksz, ptr) #endif static void *libc_malloc(size_t s, const void *caller) { From ae8784380049ad216c2e272a7271c29d8b14ea77 Mon Sep 17 00:00:00 2001 From: struct Date: Sat, 14 Mar 2026 14:57:45 -0400 Subject: [PATCH 2/2] clang format --- include/iso_alloc_hook.h | 18 +++++++++++------- src/malloc_hook.c | 2 ++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/iso_alloc_hook.h b/include/iso_alloc_hook.h index 3373781..08619bd 100644 --- a/include/iso_alloc_hook.h +++ b/include/iso_alloc_hook.h @@ -24,13 +24,17 @@ #pragma GCC diagnostic ignored "-Wattribute-alias" #endif #define ISO_FORWARD(fun) __attribute__((alias(#fun), used, visibility("default"), copy(fun))); -#define ISO_FORWARD0(fun, x) ISO_FORWARD(fun) -#define ISO_FORWARD1(fun, x) ISO_FORWARD(fun) -#define ISO_FORWARD2(fun, x, y) ISO_FORWARD(fun) +#define ISO_FORWARD0(fun, x) ISO_FORWARD(fun) +#define ISO_FORWARD1(fun, x) ISO_FORWARD(fun) +#define ISO_FORWARD2(fun, x, y) ISO_FORWARD(fun) #define ISO_FORWARD3(fun, x, y, z) ISO_FORWARD(fun) #else -#define ISO_FORWARD0(fun, x) { fun(x); } -#define ISO_FORWARD1(fun, x) { return fun(x); } -#define ISO_FORWARD2(fun, x, y) { return fun(x, y); } -#define ISO_FORWARD3(fun, x, y, z) { return fun(x, y, z); } +#define ISO_FORWARD0(fun, x) \ + { fun(x); } +#define ISO_FORWARD1(fun, x) \ + { return fun(x); } +#define ISO_FORWARD2(fun, x, y) \ + { return fun(x, y); } +#define ISO_FORWARD3(fun, x, y, z) \ + { return fun(x, y, z); } #endif diff --git a/src/malloc_hook.c b/src/malloc_hook.c index 1151fd4..6110481 100644 --- a/src/malloc_hook.c +++ b/src/malloc_hook.c @@ -5,6 +5,7 @@ * alias targets (iso_alloc, iso_free, etc.) are defined in the same * translation unit. When compiled directly the guard * below produces an empty translation unit with no effect. */ +// clang-format off #if !defined(ISO_IN_INTERFACES_C) #error "malloc_hook.c must be included from iso_alloc_interfaces.c (so aliases can work)" #endif @@ -121,3 +122,4 @@ void (*__free_hook)(void *, const void *) = &libc_free; void *(*__memalign_hook)(size_t, size_t, const void *) = &libc_memalign; #endif #endif +// clang-format on