diff --git a/include/msgpack/zone.h b/include/msgpack/zone.h index 500698f3b..a9ea8516a 100644 --- a/include/msgpack/zone.h +++ b/include/msgpack/zone.h @@ -12,6 +12,37 @@ #include "sysdep.h" +/* AddressSanitizer interior redzones. + * + * The zone carves many objects out of one malloc()ed chunk, so an overflow + * from one zone-allocated object into its neighbour stays inside a live heap + * block and is invisible to ASan. When building with ASan, every allocation + * is followed by a poisoned redzone, and chunks of the configured size are + * kept poisoned until handed out, so that msgpack_zone_clear() also poisons + * stale objects. Builds without ASan are not affected. + * Define MSGPACK_ZONE_NO_ASAN_ANNOTATIONS to disable this, e.g. when + * instrumented and uninstrumented code share a zone. + */ +#if !defined(MSGPACK_ZONE_NO_ASAN_ANNOTATIONS) +#if defined(__SANITIZE_ADDRESS__) +#define MSGPACK_ZONE_ASAN_ANNOTATIONS +#elif defined(__has_feature) +#if __has_feature(address_sanitizer) +#define MSGPACK_ZONE_ASAN_ANNOTATIONS +#endif +#endif +#endif + +#if defined(MSGPACK_ZONE_ASAN_ANNOTATIONS) +#include +/* ASan shadow granularity: objects start on a granule boundary. */ +#define MSGPACK_ZONE_ASAN_GRANULE 8 +/* Minimum number of poisoned bytes between two zone allocations. */ +#ifndef MSGPACK_ZONE_ASAN_REDZONE_SIZE +#define MSGPACK_ZONE_ASAN_REDZONE_SIZE 16 +#endif +#endif + #ifdef __cplusplus extern "C" { #endif @@ -87,8 +118,50 @@ void msgpack_zone_clear(msgpack_zone* zone); MSGPACK_DLLEXPORT void* msgpack_zone_malloc_expand(msgpack_zone* zone, size_t size); +#if defined(MSGPACK_ZONE_ASAN_ANNOTATIONS) +/* Same as msgpack_zone_malloc(), but each object starts on an ASan granule + * and is followed by MSGPACK_ZONE_ASAN_REDZONE_SIZE bytes that stay + * poisoned, so writing past the object is reported as use-after-poison. */ +static inline void* msgpack_zone_asan_malloc(msgpack_zone* zone, size_t size, size_t align) +{ + msgpack_zone_chunk_list* const cl = &zone->chunk_list; + const size_t redzone = MSGPACK_ZONE_ASAN_REDZONE_SIZE; + char* aligned; + size_t adjusted_size; + + if (align < MSGPACK_ZONE_ASAN_GRANULE) { + align = MSGPACK_ZONE_ASAN_GRANULE; + } + if (size > (size_t)-1 - (align - 1) - redzone) { + return NULL; + } + aligned = (char*)( + ((uintptr_t)cl->ptr + (align - 1)) & ~(uintptr_t)(align - 1)); + adjusted_size = size + redzone + (size_t)(aligned - cl->ptr); + if (cl->free >= adjusted_size) { + cl->free -= adjusted_size; + cl->ptr += adjusted_size; + ASAN_UNPOISON_MEMORY_REGION(aligned, size); + } else { + /* A new chunk is unpoisoned where it is handed out. */ + char* ptr = (char*)msgpack_zone_malloc_expand(zone, size + redzone + (align - 1)); + if (ptr == NULL) { + return NULL; + } + aligned = (char*)( + ((uintptr_t)ptr + (align - 1)) & ~(uintptr_t)(align - 1)); + ASAN_POISON_MEMORY_REGION(ptr, (size_t)(aligned - ptr)); + } + ASAN_POISON_MEMORY_REGION(aligned + size, redzone); + return aligned; +} +#endif + static inline void* msgpack_zone_malloc_no_align(msgpack_zone* zone, size_t size) { +#if defined(MSGPACK_ZONE_ASAN_ANNOTATIONS) + return msgpack_zone_asan_malloc(zone, size, 1); +#else char* ptr; msgpack_zone_chunk_list* cl = &zone->chunk_list; @@ -101,10 +174,14 @@ static inline void* msgpack_zone_malloc_no_align(msgpack_zone* zone, size_t size cl->ptr += size; return ptr; +#endif } static inline void* msgpack_zone_malloc(msgpack_zone* zone, size_t size) { +#if defined(MSGPACK_ZONE_ASAN_ANNOTATIONS) + return msgpack_zone_asan_malloc(zone, size, MSGPACK_ZONE_ALIGN); +#else char* aligned = (char*)( (uintptr_t)( @@ -129,6 +206,7 @@ static inline void* msgpack_zone_malloc(msgpack_zone* zone, size_t size) } } return NULL; +#endif } diff --git a/src/zone.c b/src/zone.c index d16a42bb5..2f05aabbe 100644 --- a/src/zone.c +++ b/src/zone.c @@ -28,6 +28,9 @@ static inline bool init_chunk_list(msgpack_zone_chunk_list* cl, size_t chunk_siz cl->free = chunk_size; cl->ptr = ((char*)chunk) + sizeof(msgpack_zone_chunk); chunk->next = NULL; +#if defined(MSGPACK_ZONE_ASAN_ANNOTATIONS) + ASAN_POISON_MEMORY_REGION(cl->ptr, cl->free); +#endif return true; } @@ -62,6 +65,9 @@ static inline void clear_chunk_list(msgpack_zone_chunk_list* cl, size_t chunk_si cl->head->next = NULL; cl->free = chunk_size; cl->ptr = ((char*)cl->head) + sizeof(msgpack_zone_chunk); +#if defined(MSGPACK_ZONE_ASAN_ANNOTATIONS) + ASAN_POISON_MEMORY_REGION(cl->ptr, cl->free); +#endif } void* msgpack_zone_malloc_expand(msgpack_zone* zone, size_t size) @@ -96,6 +102,14 @@ void* msgpack_zone_malloc_expand(msgpack_zone* zone, size_t size) cl->head = chunk; cl->free = sz - size; cl->ptr = ptr + size; +#if defined(MSGPACK_ZONE_ASAN_ANNOTATIONS) + /* Only poison the rest of chunks of the configured size, so that a + * single huge allocation costs no shadow updates proportional to + * its size. */ + if (size <= zone->chunk_size) { + ASAN_POISON_MEMORY_REGION(cl->ptr, cl->free); + } +#endif return ptr; }