From 0872da026a613c23354367ce466e2a60f91d781b Mon Sep 17 00:00:00 2001 From: doyle Date: Tue, 29 Aug 2023 00:52:01 +1000 Subject: [PATCH] dqn: Put ASAN poisoning behind macros to allow vetting --- Misc/dqn_unit_tests.cpp | 1 + dqn.h | 17 +++++++++++++++++ dqn_debug.cpp | 6 ++---- dqn_debug.h | 30 ++++++++++++++++++++++++++++++ dqn_external.h | 2 -- dqn_helpers.cpp | 11 +++++++++++ dqn_memory.cpp | 26 ++++++++++---------------- dqn_platform_print.cpp | 9 +++------ 8 files changed, 74 insertions(+), 28 deletions(-) diff --git a/Misc/dqn_unit_tests.cpp b/Misc/dqn_unit_tests.cpp index 9887b6a..bd60470 100644 --- a/Misc/dqn_unit_tests.cpp +++ b/Misc/dqn_unit_tests.cpp @@ -15,6 +15,7 @@ #endif #define DQN_ASAN_POISON 1 + #define DQN_ASAN_VET_POISON #define DQN_NO_CHECK_BREAK #define DQN_IMPLEMENTATION #include "dqn.h" diff --git a/dqn.h b/dqn.h index 4fd60f7..fac8b1d 100644 --- a/dqn.h +++ b/dqn.h @@ -230,6 +230,23 @@ // flag. // // DQN_LEAK_TRACING +// +// - Define this macro to 1 to enable poisoning of memory from arenas when ASAN +// `-fsanitize=address` is enabled. Enabling this will detect memory overwrite +// by padding allocated before and after with poisoned memory which will raise +// a use-after-poison in ASAN on read/write. This is a no-op if the library is +// not compiled with ASAN. +// +// DQN_ASAN_POISON 1 +// +// - Define this macro to enable sanity checks for manually poisoned memory in +// this library when ASAN `-fsanitize=address` is enabled. These sanity checks +// ensure that memory from arenas are correctly un/poisoned when pointers are +// allocated and returned to the memory arena's. This is a no-op if we are not +// compiled with ASAN or `DQN_ASAN_POISON` is not set to `1`. +// +// DQN_ASAN_VET_POISON +// // NOTE: Dqn_Strings =============================================================================== // [$CSTR] Dqn_CString8 | | C-string helpers diff --git a/dqn_debug.cpp b/dqn_debug.cpp index 8d1f62d..792e4d6 100644 --- a/dqn_debug.cpp +++ b/dqn_debug.cpp @@ -220,8 +220,7 @@ DQN_API Dqn_String8 Dqn_Log_MakeString(Dqn_Allocator allocator, header_size_no_ansi_codes = header.size - colour_esc.size - Dqn_Print_ESCResetString.size; } - // NOTE: Header padding - // ========================================================================= + // NOTE: Header padding ======================================================================== Dqn_usize header_padding = 0; { DQN_LOCAL_PERSIST Dqn_usize max_header_length = 0; @@ -229,8 +228,7 @@ DQN_API Dqn_String8 Dqn_Log_MakeString(Dqn_Allocator allocator, header_padding = max_header_length - header_size_no_ansi_codes; } - // NOTE: Construct final log - // ========================================================================= + // NOTE: Construct final log =================================================================== Dqn_String8 user_msg = Dqn_String8_InitFV(allocator, fmt, args); Dqn_String8 result = Dqn_String8_Allocate(allocator, header.size + header_padding + user_msg.size, Dqn_ZeroMem_No); DQN_MEMCPY(result.data, header.data, header.size); diff --git a/dqn_debug.h b/dqn_debug.h index 0d43cbf..9925061 100644 --- a/dqn_debug.h +++ b/dqn_debug.h @@ -38,6 +38,36 @@ #define DQN_ASAN_POISON_ALIGNMENT 8 #endif +// NOTE: MSVC does not support the feature detection macro for instance so we +// compile it out +#if !defined(__has_feature) + #define __has_feature(x) 0 +#endif + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + #include + #if defined(DQN_ASAN_VET_POISON) + #define DQN_ASAN_POISON_MEMORY_REGION(ptr, size) \ + do { \ + __asan_poison_memory_region((ptr), (size)); \ + DQN_ASSERT(__asan_address_is_poisoned((ptr))); \ + DQN_ASSERT(__asan_address_is_poisoned((char *)(ptr) + ((size) - 1))); \ + DQN_ASSERT(!__asan_address_is_poisoned((char *)(ptr) + (size))); \ + } while (0) + + #define DQN_ASAN_UNPOISON_MEMORY_REGION(ptr, size) \ + do { \ + __asan_unpoison_memory_region((ptr), (size)); \ + DQN_ASSERT(__asan_region_is_poisoned((ptr), (size)) == 0); \ + } while (0) + #else + #define DQN_ASAN_POISON_MEMORY_REGION(ptr, size) __asan_poison_memory_region(ptr, size) + #define DQN_ASAN_UNPOISON_MEMORY_REGION(ptr, size) __asan_unpoison_memory_region(ptr, size) + #endif +#else + #define DQN_ASAN_POISON_MEMORY_REGION(ptr, size) (void)(ptr); (void)(size) + #define DQN_ASAN_UNPOISON_MEMORY_REGION(ptr, size) (void)(ptr); (void)(size) +#endif // NOTE: [$CALL] Dqn_CallSite ====================================================================== struct Dqn_CallSite diff --git a/dqn_external.h b/dqn_external.h index 1271582..8ea1654 100644 --- a/dqn_external.h +++ b/dqn_external.h @@ -10,8 +10,6 @@ #include "b_stacktrace.h" // NOTE: [$OS_H] OS Headers ======================================================================== -#include - #if defined(DQN_OS_WIN32) #pragma comment(lib, "bcrypt") #pragma comment(lib, "wininet") diff --git a/dqn_helpers.cpp b/dqn_helpers.cpp index 9df2057..906110a 100644 --- a/dqn_helpers.cpp +++ b/dqn_helpers.cpp @@ -840,6 +840,17 @@ DQN_API Dqn_Library *Dqn_Library_Init() } #endif + // ============================================================================================ + + Dqn_Log_DebugF("Dqn Library initialised with features\n"); + + if (DQN_ASAN_POISON) + Dqn_Print_StdLnF(Dqn_PrintStd_Err, " - ASAN manual poisoning"); + + #if defined(DQN_ASAN_VET_POISON) + Dqn_Print_StdLnF(Dqn_PrintStd_Err, " - ASAN manual poisoning vetting"); + #endif + return result; } diff --git a/dqn_memory.cpp b/dqn_memory.cpp index 9e0a387..85aa910 100644 --- a/dqn_memory.cpp +++ b/dqn_memory.cpp @@ -1,6 +1,4 @@ // NOTE: [$ALLO] Dqn_Allocator ===================================================================== -#include -#include DQN_API void *Dqn_Allocator_Alloc(Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem) { void *result = NULL; @@ -237,8 +235,9 @@ DQN_API Dqn_MemBlock *Dqn_MemBlock_Init(Dqn_usize reserve, Dqn_usize commit, uin if (DQN_ASAN_POISON) { // NOTE: Poison (guard page + entire block), we unpoison as we allocate DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result->data, DQN_ASAN_POISON_ALIGNMENT)); DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result->size, DQN_ASAN_POISON_ALIGNMENT)); - void *poison_ptr = DQN_CAST(void *)Dqn_AlignUpPowerOfTwo(DQN_CAST(char *)result + sizeof(Dqn_MemBlock), DQN_ASAN_POISON_ALIGNMENT); - ASAN_POISON_MEMORY_REGION(poison_ptr, g_dqn_library->os_page_size + result->size); + void *poison_ptr = DQN_CAST(void *)Dqn_AlignUpPowerOfTwo(DQN_CAST(char *)result + sizeof(Dqn_MemBlock), DQN_ASAN_POISON_ALIGNMENT); + Dqn_usize bytes_to_poison = g_dqn_library->os_page_size + result->size; + DQN_ASAN_POISON_MEMORY_REGION(poison_ptr, bytes_to_poison); } } return result; @@ -261,9 +260,8 @@ DQN_API void *Dqn_MemBlock_Alloc(Dqn_MemBlock *block, Dqn_usize size, uint8_t al block->used = new_used; DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result, alignment)); - if (DQN_ASAN_POISON) { - ASAN_UNPOISON_MEMORY_REGION(result, size); - } + if (DQN_ASAN_POISON) + DQN_ASAN_UNPOISON_MEMORY_REGION(result, size); if (zero_mem == Dqn_ZeroMem_Yes) { Dqn_usize reused_bytes = DQN_MIN(block->commit - size_required.data_offset, size); @@ -286,9 +284,8 @@ DQN_API void Dqn_MemBlock_Free(Dqn_MemBlock *block) if (!block) return; Dqn_usize release_size = block->size + Dqn_MemBlock_MetadataSize(); - if (DQN_ASAN_POISON) { - ASAN_UNPOISON_MEMORY_REGION(block, release_size); - } + if (DQN_ASAN_POISON) + DQN_ASAN_UNPOISON_MEMORY_REGION(block, release_size); Dqn_VMem_Release(block, release_size); } @@ -307,13 +304,10 @@ DQN_API void Dqn_MemBlock_PopTo(Dqn_MemBlock *block, Dqn_usize to) return; if (DQN_ASAN_POISON) { - // TODO(doyle): The poison API takes addresses that are 8 byte aligned - // so there are gaps here if we are dealing with objects that aren't 8 - // byte aligned unfortunately. - void *poison_ptr = DQN_CAST(void *)Dqn_AlignUpPowerOfTwo(DQN_CAST(char *)block->data + to, DQN_ASAN_POISON_ALIGNMENT); - void *end_ptr = DQN_CAST(char *)block->data + block->used; + void *poison_ptr = DQN_CAST(char *)block->data + to; + void *end_ptr = DQN_CAST(void *)Dqn_AlignUpPowerOfTwo((DQN_CAST(uintptr_t)block->data + block->used), DQN_ASAN_POISON_ALIGNMENT); uintptr_t bytes_to_poison = DQN_CAST(uintptr_t)end_ptr - DQN_CAST(uintptr_t)poison_ptr; - ASAN_POISON_MEMORY_REGION(poison_ptr, bytes_to_poison); + DQN_ASAN_POISON_MEMORY_REGION(poison_ptr, bytes_to_poison); } block->used = to; } diff --git a/dqn_platform_print.cpp b/dqn_platform_print.cpp index cfbf2cf..6521772 100644 --- a/dqn_platform_print.cpp +++ b/dqn_platform_print.cpp @@ -31,8 +31,7 @@ DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_String8 string) DQN_ASSERT(std_handle == Dqn_PrintStd_Out || std_handle == Dqn_PrintStd_Err); #if defined(DQN_OS_WIN32) - // NOTE: Get the output handles from kernel - // ========================================================================= + // NOTE: Get the output handles from kernel ==================================================== DQN_THREAD_LOCAL void *std_out_print_handle = nullptr; DQN_THREAD_LOCAL void *std_err_print_handle = nullptr; DQN_THREAD_LOCAL bool std_out_print_to_console = false; @@ -47,8 +46,7 @@ DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_String8 string) std_err_print_to_console = GetConsoleMode(std_err_print_handle, &mode) != 0; } - // NOTE: Select the output handle - // ========================================================================= + // NOTE: Select the output handle ============================================================== void *print_handle = std_out_print_handle; bool print_to_console = std_out_print_to_console; if (std_handle == Dqn_PrintStd_Err) { @@ -56,8 +54,7 @@ DQN_API void Dqn_Print_Std(Dqn_PrintStd std_handle, Dqn_String8 string) print_to_console = std_err_print_to_console; } - // NOTE: Write the string - // ========================================================================= + // NOTE: Write the string ====================================================================== DQN_ASSERT(string.size < DQN_CAST(unsigned long)-1); unsigned long bytes_written = 0; (void)bytes_written; if (print_to_console) {