Skip to content

adding runtime options feature. #220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 3 additions & 31 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ CXX = clang++
## DISABLE_CANARY - Disables the use of canaries, improves performance
DISABLE_CANARY = -DDISABLE_CANARY=0

## Clear user chunks upon free
SANITIZE_CHUNKS = -DSANITIZE_CHUNKS=0

## Call verify_all_zones upon alloc/free, never reuse private zones
## Adds significant performance over head
FUZZ_MODE = -DFUZZ_MODE=0
Expand All @@ -23,9 +20,6 @@ PERM_FREE_REALLOC = -DPERM_FREE_REALLOC=0
## with ARM MTE and PAC. See MEMORY_TAGGING.md for more information
MEMORY_TAGGING = -DMEMORY_TAGGING=0

## Enable abort() when isoalloc can't gather enough entropy.
ABORT_NO_ENTROPY = -DABORT_NO_ENTROPY=1

## This enables Address Sanitizer support for manually
## poisoning and unpoisoning zones. It adds significant
## performance and memory overhead
Expand Down Expand Up @@ -100,23 +94,6 @@ HUGE_PAGES = -DHUGE_PAGES=1
CPU_PIN = -DCPU_PIN=0
SCHED_GETCPU =

## Enable the allocation sanity feature. This works a lot
## like GWP-ASAN does. It samples calls to iso_alloc and
## randomly swaps them out for raw page allocations that
## are surrounded by guard pages. These pages are unmapped
## upon free. Much like GWP-ASAN this is designed to be
## used in production builds and should not incur too
## much of a performance penalty
ALLOC_SANITY = -DALLOC_SANITY=0

## Enable hooking of memcpy/memmove/memset to detect out of bounds
## r/w operations on chunks allocated with IsoAlloc. Does
## not require ALLOC_SANITY is enabled. On MacOS you need
## to set FORTIFY_SOURCE to 0. Leave these commented if
## you aren't enabling them.
MEMCPY_SANITY = -DMEMCPY_SANITY=0
MEMSET_SANITY = -DMEMSET_SANITY=0

## Enable the userfaultfd based uninitialized read detection
## feature. This samples calls to malloc, and allocates raw
## pages of memory with mmap which are registered with the
Expand All @@ -125,7 +102,7 @@ MEMSET_SANITY = -DMEMSET_SANITY=0
## previous call to write. Think of it as GWP-ASAN but for
## uninitialized reads. Enabling this feature does incur a
## performance penalty. This requires that both ALLOC_SANITY
## and THREAD_SUPPORT are enabled. Linux only
## option and THREAD_SUPPORT are enabled. Linux only
UNINIT_READ_SANITY = -DUNINIT_READ_SANITY=0

## By default IsoAlloc may select a zone that holds chunks
Expand All @@ -148,11 +125,6 @@ UAF_PTR_PAGE = -DUAF_PTR_PAGE=0
## performance penalty
VERIFY_FREE_BIT_SLOTS = -DVERIFY_FREE_BIT_SLOTS=0

## Randomizes the free bit slot list upon creation. This can
## impact perf. You can control the minimum size of the list
## to be randomized with MIN_RAND_FREELIST in conf.h
RANDOMIZE_FREELIST = -DRANDOMIZE_FREELIST=1

## Enable experimental features that are not guaranteed to
## compile, or introduce stability and performance bugs
EXPERIMENTAL = -DEXPERIMENTAL=0
Expand Down Expand Up @@ -290,11 +262,11 @@ else
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) \
-std=$(STDC) $(SANITIZER_SUPPORT) $(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) \
$(ABORT_NO_ENTROPY) $(ISO_DTOR_CLEANUP) $(RANDOMIZE_FREELIST) $(USE_SPINLOCK) $(HUGE_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) \
$(BIG_ZONE_META_DATA_GUARD) $(BIG_ZONE_GUARD) $(PROTECT_UNUSED_BIG_ZONE) $(MASK_PTRS) $(FUZZ_MODE) \
$(PERM_FREE_REALLOC)
CXXFLAGS = $(COMMON_CFLAGS) -DCPP_SUPPORT=1 -std=$(STDCXX) $(SANITIZER_SUPPORT) $(HOOKS)

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ If all else fails please file an issue on the [github project](https://github.co

`size_t iso_zone_chunk_count(iso_alloc_zone_handle *zone)` - Returns the total number of chunks a private zone can hold not including canary chunks. If canaries are disabled this number is absolute, otherwise it is a safe lower bound and actual number may be higher due to canary creation random seed.

### Runtime options APIs

It is possible to set a handful of global runtime options with the following.

`uint64_t iso_option_get(iso_option_t id)` - Fetches the current value of the option `id`.
`void iso_option_set(iso_option_t id, uint64_t val)` - Set the current value of the option `id`.

### Experimental APIs

These APIs are exposed via the public header `iso_alloc.h` but are subject to backward breaking changes at any time.
Expand Down
16 changes: 8 additions & 8 deletions android/jni/Android.mk
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_CFLAGS := -DTHREAD_SUPPORT=1 -pthread \
-DPRE_POPULATE_PAGES=0 -DSMALL_MEM_STARTUP=0 -DSANITIZE_CHUNKS=0 \
LOCAL_CFLAGS := -DTHREAD_SUPPORT=1 -pthread \
-DPRE_POPULATE_PAGES=0 -DSMALL_MEM_STARTUP=0 \
-DFUZZ_MODE=0 -DPERM_FREE_REALLOC=0 -DDISABLE_CANARY=0 -Werror \
-pedantic -Wno-pointer-arith -Wno-gnu-zero-variadic-macro-arguments \
-Wno-format-pedantic -DMALLOC_HOOK=1 -fvisibility=hidden -std=c11 \
-DALLOC_SANITY=0 -DUNINIT_READ_SANITY=0 -DCPU_PIN=0 -DEXPERIMENTAL=0 \
-DUNINIT_READ_SANITY=0 -DCPU_PIN=0 -DEXPERIMENTAL=0 \
-DUAF_PTR_PAGE=0 -DVERIFY_FREE_BIT_SLOTS=0 -DNAMED_MAPPINGS=1 -fPIC \
-shared -DDEBUG=1 -DLEAK_DETECTOR=1 -DMEM_USAGE=1 -DUSE_MLOCK=1 \
-DMEMORY_TAGGING=0 -DSCHED_GETCPU -g -ggdb3 -fno-omit-frame-pointer \
-DRANDOMIZE_FREELIST=1 -DBIG_ZONE_META_DATA_GUARD=0 -DBIG_ZONE_GUARD=0 \
-DPROTECT_FREE_BIG_ZONES=0 -DMASK_PTRS=1 -DSIGNAL_HANDLER=0 \
-DUSE_MLOCK=1 -DNO_ZERO_ALLOCATIONS=1 -DABORT_ON_NULL=0 \
-DABORT_NO_ENTROPY=1 -DMEMCPY_SANITY=0 -DMEMSET_SANITY=0 \
-DRANDOMIZE_FREELIST=1 -DBIG_ZONE_META_DATA_GUARD=0 -DBIG_ZONE_GUARD=0 \
-DPROTECT_FREE_BIG_ZONES=0 -DMASK_PTRS=1 -DSIGNAL_HANDLER=0 \
-DUSE_MLOCK=1 -DNO_ZERO_ALLOCATIONS=1 -DABORT_ON_NULL=0 \
-DMEMCPY_SANITY=0 -DMEMSET_SANITY=0 \
-DSTRONG_SIZE_ISOLATION=0 -DISO_DTOR_CLEANUP=0

LOCAL_SRC_FILES := ../../src/iso_alloc.c ../../src/iso_alloc_printf.c ../../src/iso_alloc_random.c \
../../src/iso_alloc_search.c ../../src/iso_alloc_interfaces.c ../../src/iso_alloc_profiler.c \
../../src/iso_alloc_sanity.c ../../src/iso_alloc_util.c ../../src/malloc_hook.c \
../../src/libc_hook.c ../../src/iso_alloc_mem_tags.c
../../src/libc_hook.c ../../src/iso_alloc_mem_tags.c ../../src/iso_alloc_options.c

LOCAL_C_INCLUDES := ../../include/

Expand Down
4 changes: 2 additions & 2 deletions include/conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@
#define ZONE_ALLOC_RETIRE 32

/* This byte value will overwrite the contents
* of all free'd user chunks if -DSANITIZE_CHUNKS
* is enabled in the Makefile. The value is completely
* of all free'd user chunks if the SANITIZE_CHUNKS
* option is enabled. The value is completely
* arbitrary, but non-zero since this could mask
* some bugs. */
#define POISON_BYTE 0xde
Expand Down
24 changes: 24 additions & 0 deletions include/iso_alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@ static_assert(sizeof(size_t) == 8, "IsoAlloc requires 64 bit size_t");
#define UNMASK_ZONE_HANDLE(zone) zone = zone;
#endif

typedef enum iso_option {
// Clear user chunks upon free
SANITIZE_CHUNKS,
// Enable the allocation sanity feature. This works a lot
// like GWP-ASAN does. It samples calls to iso_alloc and
// randomly swaps them out for raw page allocations that
// are surrounded by guard pages. These pages are unmapped
// upon free. Much like GWP-ASAN this is designed to be
// used in production builds and should not incur too
// much of a performance penalty
ALLOC_SANITY,
// Randomizes the free bit slot list upon creation. This can
// impact perf. You can control the minimum size of the list
// to be randomized with MIN_RAND_FREELIST in conf.h
RANDOMIZE_FREELIST,
// Enable abort() when isoalloc can't gather enough entropy.
ABORT_NO_ENTROPY,
OPTION_FIRST = SANITIZE_CHUNKS,
OPTION_LAST = ABORT_NO_ENTROPY
} iso_option_t;

typedef void iso_alloc_zone_handle;

#if CPP_SUPPORT
Expand Down Expand Up @@ -112,6 +133,9 @@ EXTERNAL_API void iso_alloc_reset_traces(void);
EXTERNAL_API void iso_alloc_search_stack(void *p);
#endif

EXTERNAL_API uint64_t iso_option_get(iso_option_t);
EXTERNAL_API void iso_option_set(iso_option_t, uint64_t);

#if CPP_SUPPORT
}
#endif
3 changes: 3 additions & 0 deletions include/iso_alloc_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,6 @@ INTERNAL_HIDDEN void _iso_alloc_search_stack(uint8_t *stack_start);
#if UNIT_TESTING
EXTERNAL_API iso_alloc_root *_get_root(void);
#endif

INTERNAL_HIDDEN unsigned long _iso_option_get(iso_option_t);
INTERNAL_HIDDEN void _iso_option_set(iso_option_t, unsigned long);
2 changes: 0 additions & 2 deletions include/iso_alloc_sanity.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

#include "iso_alloc_util.h"

#if ALLOC_SANITY
#if UNINIT_READ_SANITY
#include <fcntl.h>
#include <linux/userfaultfd.h>
Expand Down Expand Up @@ -72,7 +71,6 @@ INTERNAL_HIDDEN void *_iso_alloc_sample(const size_t size);
INTERNAL_HIDDEN int32_t _iso_alloc_free_sane_sample(void *p);
INTERNAL_HIDDEN int32_t _remove_from_sane_trace(void *p);
INTERNAL_HIDDEN _sane_allocation_t *_get_sane_alloc(void *p);
#endif

INTERNAL_HIDDEN INLINE void *__iso_memcpy(void *dest, const void *src, size_t n);
INTERNAL_HIDDEN void *_iso_alloc_memcpy(void *dest, const void *src, size_t n);
Expand Down
3 changes: 3 additions & 0 deletions include/iso_alloc_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

#pragma once
#include "compiler.h"
#include <sys/types.h>
#include <inttypes.h>
#include <stdbool.h>

#if !NAMED_MAPPINGS
#define SAMPLED_ALLOC_NAME ""
Expand Down
90 changes: 47 additions & 43 deletions src/iso_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ INTERNAL_HIDDEN void _iso_alloc_initialize(void) {
pthread_mutex_init(&root_busy_mutex, NULL);
pthread_mutex_init(&_root->big_zone_free_mutex, NULL);
pthread_mutex_init(&_root->big_zone_used_mutex, NULL);
#if ALLOC_SANITY
pthread_mutex_init(&sane_cache_mutex, NULL);
#endif
if(_iso_option_get(ALLOC_SANITY)) {
pthread_mutex_init(&sane_cache_mutex, NULL);
}
#endif

#if HEAP_PROFILER
Expand All @@ -159,13 +159,15 @@ INTERNAL_HIDDEN void _iso_alloc_initialize(void) {
_root->uaf_ptr_page = mmap_pages(g_page_size, false, NULL, PROT_NONE);
#endif

#if ALLOC_SANITY && UNINIT_READ_SANITY
_iso_alloc_setup_userfaultfd();
#if UNINIT_READ_SANITY
if(_iso_option_get(ALLOC_SANITY)) {
_iso_alloc_setup_userfaultfd();
}
#endif

#if ALLOC_SANITY
_sanity_canary = us_rand_uint64(&_root->seed);
#endif
if(_iso_option_get(ALLOC_SANITY)) {
_sanity_canary = us_rand_uint64(&_root->seed);
}

#if SIGNAL_HANDLER
struct sigaction sa;
Expand Down Expand Up @@ -595,19 +597,19 @@ INTERNAL_HIDDEN void fill_free_bit_slots(iso_alloc_zone_t *zone) {
}
}

#if RANDOMIZE_FREELIST
static_assert(MIN_RAND_FREELIST >= 2, "MIN_RAND_FREELIST should be at least 2");

/* Randomize the list of free bitslots */
if(free_bit_slots_index > MIN_RAND_FREELIST) {
for(free_bit_slot_t i = free_bit_slots_index - 1; i > 0; i--) {
free_bit_slot_t j = ((free_bit_slot_t) us_rand_uint64(&_root->seed) * i) >> FREE_LIST_SHF;
bit_slot_t t = free_bit_slots[j];
free_bit_slots[j] = free_bit_slots[i];
free_bit_slots[i] = t;
if(_iso_option_get(RANDOMIZE_FREELIST)) {
/* Randomize the list of free bitslots */
if(free_bit_slots_index > MIN_RAND_FREELIST) {
for(free_bit_slot_t i = free_bit_slots_index - 1; i > 0; i--) {
free_bit_slot_t j = ((free_bit_slot_t) us_rand_uint64(&_root->seed) * i) >> FREE_LIST_SHF;
bit_slot_t t = free_bit_slots[j];
free_bit_slots[j] = free_bit_slots[i];
free_bit_slots[i] = t;
}
}
}
#endif

zone->free_bit_slots_index = free_bit_slots_index;
}
Expand Down Expand Up @@ -1003,9 +1005,8 @@ INTERNAL_HIDDEN ASSUME_ALIGNED void *_iso_alloc(iso_alloc_zone_t *zone, size_t s
#endif
}

#if ALLOC_SANITY
/* We don't sample if we are allocating from a private zone */
if(zone != NULL) {
if(_iso_option_get(ALLOC_SANITY) && zone != NULL) {
if(size < g_page_size && _sane_sampled < MAX_SANE_SAMPLES) {
/* If we chose to sample this allocation then
* _iso_alloc_sample will call UNLOCK_ROOT() */
Expand All @@ -1016,7 +1017,6 @@ INTERNAL_HIDDEN ASSUME_ALIGNED void *_iso_alloc(iso_alloc_zone_t *zone, size_t s
}
}
}
#endif

#if HEAP_PROFILER
_iso_alloc_profile(size);
Expand Down Expand Up @@ -1341,8 +1341,10 @@ INTERNAL_HIDDEN void iso_free_chunk_from_zone(iso_alloc_zone_t *zone, void *rest
if(LIKELY(permanent == false)) {
UNSET_BIT(b, which_bit);
insert_free_bit_slot(zone, bit_slot);
#if !ENABLE_ASAN && SANITIZE_CHUNKS
__iso_memset(p, POISON_BYTE, zone->chunk_size);
#if !ENABLE_ASAN
if(_iso_option_get(SANITIZE_CHUNKS)) {
__iso_memset(p, POISON_BYTE, zone->chunk_size);
}
#endif
} else {
__iso_memset(p, POISON_BYTE, zone->chunk_size);
Expand Down Expand Up @@ -1434,13 +1436,13 @@ INTERNAL_HIDDEN void _iso_free(void *p, bool permanent) {
}
#endif

#if ALLOC_SANITY
int32_t r = _iso_alloc_free_sane_sample(p);
if(_iso_option_get(ALLOC_SANITY)) {
int32_t r = _iso_alloc_free_sane_sample(p);

if(r == OK) {
return;
if(r == OK) {
return;
}
}
#endif

#if HEAP_PROFILER
_iso_free_profile();
Expand Down Expand Up @@ -1481,13 +1483,13 @@ INTERNAL_HIDDEN void _iso_free_size(void *p, size_t size) {
}
#endif

#if ALLOC_SANITY
int32_t r = _iso_alloc_free_sane_sample(p);
if(_iso_option_get(ALLOC_SANITY)) {
int32_t r = _iso_alloc_free_sane_sample(p);

if(r == OK) {
return;
if(r == OK) {
return;
}
}
#endif

if(UNLIKELY(size > SMALL_SIZE_MAX)) {
iso_alloc_big_zone_t *big_zone = iso_find_big_zone(p, true);
Expand Down Expand Up @@ -1662,8 +1664,10 @@ INTERNAL_HIDDEN void iso_free_big_zone(iso_alloc_big_zone_t *big_zone, bool perm
LOG_AND_ABORT("Double free of big zone 0x%p has been detected!", big_zone);
}

#if !ENABLE_ASAN && SANITIZE_CHUNKS
__iso_memset(big_zone->user_pages_start, POISON_BYTE, big_zone->size);
#if !ENABLE_ASAN
if(_iso_option_get(SANITIZE_CHUNKS)) {
__iso_memset(big_zone->user_pages_start, POISON_BYTE, big_zone->size);
}
#endif
/* If this isn't a permanent free then all we need
* to do is sanitize the mapping and mark it free.
Expand Down Expand Up @@ -1874,19 +1878,19 @@ INTERNAL_HIDDEN size_t _iso_chunk_size(void *p) {
}
#endif

#if ALLOC_SANITY
LOCK_SANITY_CACHE();
_sane_allocation_t *sane_alloc = _get_sane_alloc(p);
if(_iso_option_get(ALLOC_SANITY)) {
LOCK_SANITY_CACHE();
_sane_allocation_t *sane_alloc = _get_sane_alloc(p);

if(sane_alloc != NULL) {
size_t orig_size = sane_alloc->orig_size;
UNLOCK_SANITY_CACHE();
return orig_size;
}

if(sane_alloc != NULL) {
size_t orig_size = sane_alloc->orig_size;
UNLOCK_SANITY_CACHE();
return orig_size;
}

UNLOCK_SANITY_CACHE();
#endif

LOCK_ROOT();

/* We cannot return NULL here, we abort instead */
Expand Down
8 changes: 8 additions & 0 deletions src/iso_alloc_interfaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,11 @@ EXTERNAL_API FLATTEN void iso_alloc_search_stack(void *p) {
_iso_alloc_search_stack(p);
}
#endif

EXTERNAL_API uint64_t iso_option_get(iso_option_t id) {
return _iso_option_get(id);
}

EXTERNAL_API void iso_option_set(iso_option_t id, uint64_t val) {
_iso_option_set(id, val);
}
Loading