Skip to content

Commit a4658d3

Browse files
authored
Merge pull request #117 from struct/memset_sanity
add memset_sanity and build cleanups to enable it
2 parents b815a2e + 3fdccc3 commit a4658d3

9 files changed

Lines changed: 131 additions & 12 deletions

File tree

Makefile

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,13 @@ SCHED_GETCPU =
103103
## much of a performance penalty
104104
ALLOC_SANITY = -DALLOC_SANITY=0
105105

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

111114
## Enable the userfaultfd based uninitialized read detection
112115
## feature. This samples calls to malloc, and allocates raw
@@ -219,7 +222,7 @@ CFLAGS = $(COMMON_CFLAGS) $(SECURITY_FLAGS) $(BUILD_ERROR_FLAGS) $(HOOKS) $(HEAP
219222
-std=c11 $(SANITIZER_SUPPORT) $(ALLOC_SANITY) $(MEMCPY_SANITY) $(UNINIT_READ_SANITY) $(CPU_PIN) $(SCHED_GETCPU) \
220223
$(EXPERIMENTAL) $(UAF_PTR_PAGE) $(VERIFY_BIT_SLOT_CACHE) $(NAMED_MAPPINGS) $(ABORT_ON_NULL) $(NO_ZERO_ALLOCATIONS) \
221224
$(ABORT_NO_ENTROPY) $(ISO_DTOR_CLEANUP) $(SHUFFLE_BIT_SLOT_CACHE) $(USE_SPINLOCK) $(HUGE_PAGES) $(USE_MLOCK) \
222-
$(MEMORY_TAGGING) $(STRONG_SIZE_ISOLATION)
225+
$(MEMORY_TAGGING) $(STRONG_SIZE_ISOLATION) $(MEMSET_SANITY)
223226
CXXFLAGS = $(COMMON_CFLAGS) -DCPP_SUPPORT=1 -std=c++17 $(SANITIZER_SUPPORT) $(HOOKS)
224227
EXE_CFLAGS = -fPIE
225228
GDB_FLAGS = -g -ggdb3 -fno-omit-frame-pointer
@@ -304,9 +307,13 @@ tests: clean library_debug_unit_tests
304307
$(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(OS_FLAGS) tests/incorrect_chunk_size_multiple.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/incorrect_chunk_size_multiple $(LDFLAGS)
305308
$(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(OS_FLAGS) tests/zero_alloc.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/zero_alloc $(LDFLAGS)
306309
$(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(OS_FLAGS) tests/uninit_read.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/uninit_read $(LDFLAGS)
307-
$(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(OS_FLAGS) tests/sized_free.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/sized_free $(LDFLAGS)
310+
$(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(OS_FLAGS) tests/sized_free.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/sized_free $(LDFLAGS)
308311
utils/run_tests.sh
309312

313+
libc_sanity_tests: clean library_debug_unit_tests
314+
$(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(OS_FLAGS) tests/memset_sanity.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/memset_sanity $(LDFLAGS)
315+
$(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) $(OS_FLAGS) tests/memcpy_sanity.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/memcpy_sanity $(LDFLAGS)
316+
310317
fuzz_test: clean library_debug_unit_tests
311318
@echo "make fuzz_test"
312319
$(CC) $(CFLAGS) $(EXE_CFLAGS) $(DEBUG_LOG_FLAGS) $(GDB_FLAGS) tests/alloc_fuzz.c $(ISO_ALLOC_PRINTF_SRC) -o $(BUILD_DIR)/alloc_fuzz $(LDFLAGS)

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ When enabled, the `CPU_PIN` feature will restrict allocations from a given zone
8585
* When destroying private zones if `NEVER_REUSE_ZONES` is enabled IsoAlloc won't attempt to repurpose the zone
8686
* Zones are retired and replaced after they've allocated and freed a specific number of chunks. This is calculated as `ZONE_ALLOC_RETIRE * max_chunk_count_for_zone`.
8787
* `MEMORY_TAGGING` When enabled IsoAlloc will create a 1 byte tag for each chunk in private zones. See the [MEMORY_TAGGING.md](MEMORY_TAGGING.md) documentation, or [this test](tests/tagged_ptr_test.cpp) for an example of how to use it.
88-
* `MEMCPY_SANITY` Configures the allocator will hook all calls to `memcpy` and check for out of bounds r/w operations when either src or dst points to a chunk allocated by IsoAlloc
88+
* `MEMCPY_SANITY` and `MEMSET_SANITY` Configures the allocator will hook all calls to `memcpy`/`memset` and check for out of bounds r/w operations when either src or dst points to a chunk allocated by IsoAlloc
8989
* `STRONG_SIZE_ISOLATION` Enables a policy that enforces stronger memory isolation by size
9090

9191
## Building
@@ -102,6 +102,8 @@ The Makefile targets are very simple:
102102

103103
`make tests` - Builds and runs all tests
104104

105+
`make libc_sanity_tests` - Builds the memcpy/memset libc hook sanity tests
106+
105107
`make perf_tests` - Builds and runs a simple performance test that uses gprof. Linux only
106108

107109
`make malloc_cmp_test` - Builds and runs a test that uses both iso_alloc and malloc for comparison

include/iso_alloc_sanity.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,8 @@ INTERNAL_HIDDEN _sane_allocation_t *_get_sane_alloc(void *p);
8686
INTERNAL_HIDDEN void *__iso_memcpy(void *restrict dest, const void *restrict src, size_t n);
8787
INTERNAL_HIDDEN void *_iso_alloc_memcpy(void *restrict dest, const void *restrict src, size_t n);
8888
#endif
89+
90+
#if MEMSET_SANITY
91+
INTERNAL_HIDDEN void *__iso_memset(void *dest, int b, size_t n);
92+
INTERNAL_HIDDEN void *_iso_alloc_memset(void *dest, int b, size_t n);
93+
#endif

src/iso_alloc_sanity.c

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,9 @@ INTERNAL_HIDDEN void *_iso_alloc_sample(const size_t size) {
277277
#endif
278278

279279
#if MEMCPY_SANITY
280+
281+
#define MEMCPY_SANITY_CHK(p) (user_pages_start <= p && (user_pages_start + ZONE_USER_SIZE) - n > p && n > zone->chunk_size)
282+
280283
INTERNAL_HIDDEN void *__iso_memcpy(void *restrict dest, const void *restrict src, size_t n) {
281284
char *p_dest = (char *) dest;
282285
char const *p_src = (char const *) src;
@@ -299,18 +302,46 @@ INTERNAL_HIDDEN void *_iso_alloc_memcpy(void *restrict dest, const void *restric
299302
iso_alloc_zone_t *zone = search_chunk_lookup_table(dest);
300303
void *user_pages_start = UNMASK_USER_PTR(zone);
301304

302-
if(user_pages_start <= dest && (user_pages_start + ZONE_USER_SIZE) > dest && n > zone->chunk_size) {
305+
if(MEMCPY_SANITY_CHK(dest)) {
303306
LOG_AND_ABORT("Detected an out of bounds write memcpy: dest=0x%p (%d bytes) src=0x%p size=%d", dest, zone->chunk_size, src, n);
304307
}
305308

306309
zone = search_chunk_lookup_table(src);
307310
user_pages_start = UNMASK_USER_PTR(zone);
308311

309-
if(user_pages_start <= src && (user_pages_start + ZONE_USER_SIZE) > src && n > zone->chunk_size) {
312+
if(MEMCPY_SANITY_CHK(src)) {
310313
LOG_AND_ABORT("Detected an out of bounds read memcpy: dest=0x%p src=0x%p (%d bytes) size=%d", dest, src, zone->chunk_size, n);
311314
}
312315
}
313316

314317
return __iso_memcpy(dest, src, n);
315318
}
316319
#endif
320+
321+
#if MEMSET_SANITY
322+
323+
#define MEMSET_SANITY_CHK(p) (user_pages_start <= p && (user_pages_start + ZONE_USER_SIZE) - n > p && n > zone->chunk_size)
324+
325+
INTERNAL_HIDDEN void *__iso_memset(void *dest, int b, size_t n) {
326+
char *p_dest = (char *) dest;
327+
328+
while(n--) {
329+
*p_dest++ = b;
330+
}
331+
332+
return dest;
333+
}
334+
335+
INTERNAL_HIDDEN void *_iso_alloc_memset(void *dest, int b, size_t n) {
336+
if(n > SMALLEST_CHUNK_SZ) {
337+
iso_alloc_zone_t *zone = search_chunk_lookup_table(dest);
338+
void *user_pages_start = UNMASK_USER_PTR(zone);
339+
340+
if(MEMSET_SANITY_CHK(dest)) {
341+
LOG_AND_ABORT("Detected an out of bounds write memset: dest=0x%p (%d bytes) size=%d", dest, zone->chunk_size, n);
342+
}
343+
}
344+
345+
return __iso_memset(dest, b, n);
346+
}
347+
#endif

src/libc_hook.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
#include "iso_alloc_sanity.h"
66

77
#if MEMCPY_SANITY
8-
98
EXTERNAL_API void *memcpy(void *restrict dest, const void *restrict src, size_t n) {
109
return _iso_alloc_memcpy(dest, src, n);
1110
}
11+
#endif
1212

13+
#if MEMSET_SANITY
14+
EXTERNAL_API void *memset(void *dest, int b, size_t n) {
15+
return _iso_alloc_memset(dest, b, n);
16+
}
1317
#endif

tests/heap_overflow.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ int main(int argc, char *argv[]) {
1414

1515
p = (uint8_t *) iso_alloc(32);
1616

17-
#if MEMCPY_SANITY
1817
const char *A = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
1918
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
2019
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
20+
#if MEMCPY_SANITY
2121
memcpy(p, A, strlen(A));
2222
#else
23-
memset(p, 0x41, 65535);
23+
size_t n = strlen(A);
24+
while(n--) {
25+
*p++ = *A++;
26+
}
2427
#endif
2528

2629
iso_free(p);

tests/heap_underflow.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,18 @@ int main(int argc, char *argv[]) {
1313
}
1414

1515
p = (uint8_t *) iso_alloc(32);
16+
17+
#if MEMSET_SANITY
18+
uint8_t *p_dest = p - 65535;
19+
size_t n = 65535;
20+
21+
while(n--) {
22+
*p_dest++ = 0;
23+
}
24+
#else
1625
memset(p - 65535, 0x42, 65535);
26+
#endif
27+
1728
iso_free(p);
1829
iso_verify_zones();
1930

tests/memcpy_sanity.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* iso_alloc memcpy_sanity.c
2+
* Copyright 2022 - chris.rohlf@gmail.com */
3+
4+
#include "iso_alloc.h"
5+
#include "iso_alloc_internal.h"
6+
7+
#if !MEMCPY_SANITY
8+
#error "This test intended to be run with -DMEMCPY_SANITY=1"
9+
#endif
10+
11+
int main(int argc, char *argv[]) {
12+
uint8_t *p = NULL;
13+
14+
for(int32_t i = 0; i < 1024; i++) {
15+
p = (uint8_t *) iso_alloc(8);
16+
iso_free(p);
17+
}
18+
19+
p = (uint8_t *) iso_alloc(8);
20+
21+
const char *A = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
22+
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
23+
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
24+
memcpy(p, A, strlen(A));
25+
26+
iso_free(p);
27+
iso_verify_zones();
28+
29+
return OK;
30+
}

tests/memset_sanity.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/* iso_alloc memset_sanity.c
2+
* Copyright 2022 - chris.rohlf@gmail.com */
3+
4+
#include "iso_alloc.h"
5+
#include "iso_alloc_internal.h"
6+
7+
#if !MEMSET_SANITY
8+
#error "This test intended to be run with -DMEMSET_SANITY=1"
9+
#endif
10+
11+
int main(int argc, char *argv[]) {
12+
uint8_t *p = NULL;
13+
14+
for(int32_t i = 0; i < 1024; i++) {
15+
p = (uint8_t *) iso_alloc(32);
16+
iso_free(p);
17+
}
18+
19+
p = (uint8_t *) iso_alloc(32);
20+
memset(p, 0x41, 65535);
21+
22+
iso_free(p);
23+
iso_verify_zones();
24+
25+
return OK;
26+
}

0 commit comments

Comments
 (0)