https://github.com/ainozaki created https://github.com/llvm/llvm-project/pull/237683
None >From def4515d7bc697567f144536d01daa3372a4b87b Mon Sep 17 00:00:00 2001 From: ainozaki <[email protected]> Date: Sun, 1 Nov 2026 05:25:32 +0000 Subject: [PATCH] Asan reports AllocToken --- clang/docs/AllocToken.md | 8 + clang/docs/ReleaseNotes.md | 4 + clang/lib/Driver/SanitizerArgs.cpp | 22 ++- clang/test/Driver/fsanitize-alloc-token.c | 15 +- compiler-rt/lib/asan/CMakeLists.txt | 1 + compiler-rt/lib/asan/asan.syms.extra | 1 + compiler-rt/lib/asan/asan_alloc_token.cpp | 171 ++++++++++++++++++ compiler-rt/lib/asan/asan_allocator.cpp | 120 ++++++++---- compiler-rt/lib/asan/asan_allocator.h | 43 +++-- compiler-rt/lib/asan/asan_descriptions.cpp | 3 + compiler-rt/lib/asan/asan_descriptions.h | 2 + .../TestCases/Linux/alloc-token-runtime.cpp | 52 ++++++ .../test/asan/TestCases/Linux/alloc-token.cpp | 71 ++++++++ 13 files changed, 454 insertions(+), 59 deletions(-) create mode 100644 compiler-rt/lib/asan/asan_alloc_token.cpp create mode 100644 compiler-rt/test/asan/TestCases/Linux/alloc-token-runtime.cpp create mode 100644 compiler-rt/test/asan/TestCases/Linux/alloc-token.cpp diff --git a/clang/docs/AllocToken.md b/clang/docs/AllocToken.md index cca7136b652abd..f86be324f4dc0d 100644 --- a/clang/docs/AllocToken.md +++ b/clang/docs/AllocToken.md @@ -144,6 +144,14 @@ void *__alloc_token_2_Znwm(size_t size); This ABI provides a more efficient alternative where `-falloc-token-max` is small. +### AddressSanitizer Integration + +`-fsanitize=address,alloc-token` includes allocation tokens in heap reports, +including freed allocations in quarantine. It supports non-Android Linux +x86-64 and AArch64 with 64-bit pointers and the default ABI. The fast ABI and +extended instrumentation are unsupported with AddressSanitizer. +Token-enabled allocations reserve one additional shadow granule for metadata. + ### Instrumenting Non-Standard Allocation Functions By default, AllocToken only instruments standard library allocation functions. diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index a2ad6d06815c7c..7de3b520a01b40 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -1453,6 +1453,10 @@ The `alpha.cplusplus.UseAfterLifetimeEnd` checker was renamed to `alpha.core.Use ### Sanitizers +- AddressSanitizer supports `-fsanitize=address,alloc-token` to report heap + allocation tokens on non-Android Linux x86-64 and AArch64 with 64-bit pointers + using the default AllocToken ABI. + - Added the `typefunchash` and `typefunchashpointersplit` token assignment modes for `-fsanitize=alloc-token`, selected with `-Xclang -falloc-token-mode=<mode>`. These modes derive token IDs from both diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index a1bfa90e5dceb2..70c60bfeba5a72 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -777,10 +777,8 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, SanitizerKind::Undefined | SanitizerKind::Memory | SanitizerKind::Type), std::make_pair(SanitizerKind::AllocToken, - SanitizerKind::Address | SanitizerKind::HWAddress | - SanitizerKind::KernelAddress | - SanitizerKind::KernelHWAddress | - SanitizerKind::Memory)}; + SanitizerKind::HWAddress | SanitizerKind::KernelAddress | + SanitizerKind::KernelHWAddress | SanitizerKind::Memory)}; // Enable toolchain specific default sanitizers if not explicitly disabled. SanitizerMask Default = TC.getDefaultSanitizers() & ~AllRemove; @@ -1322,6 +1320,22 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, AllocTokenExtended = Args.hasFlag( options::OPT_fsanitize_alloc_token_extended, options::OPT_fno_sanitize_alloc_token_extended, AllocTokenExtended); + if ((Kinds & SanitizerKind::Address) && + (Kinds & SanitizerKind::AllocToken) && DiagnoseErrors) { + const llvm::Triple &Triple = TC.getTriple(); + bool SupportedArch = + (Triple.getArch() == llvm::Triple::x86_64 && !Triple.isX32()) || + (Triple.getArch() == llvm::Triple::aarch64 && Triple.isAArch64(64)); + if (!Triple.isOSLinux() || Triple.isAndroid() || !SupportedArch) + D.Diag(diag::err_drv_unsupported_opt_for_target) + << "-fsanitize=address,alloc-token" << Triple.str(); + if (AllocTokenFastABI) + D.Diag(diag::err_drv_argument_not_allowed_with) + << "-fsanitize-alloc-token-fast-abi" << "-fsanitize=address"; + if (AllocTokenExtended) + D.Diag(diag::err_drv_argument_not_allowed_with) + << "-fsanitize-alloc-token-extended" << "-fsanitize=address"; + } } if (AllAddedKinds & SanitizerKind::Type) { diff --git a/clang/test/Driver/fsanitize-alloc-token.c b/clang/test/Driver/fsanitize-alloc-token.c index 0ffe9abad8053b..586624cc8ae94a 100644 --- a/clang/test/Driver/fsanitize-alloc-token.c +++ b/clang/test/Driver/fsanitize-alloc-token.c @@ -16,8 +16,19 @@ // RUN: %clang --target=aarch64-none-elf -fsanitize=alloc-token %s -### 2>&1 | FileCheck %s -check-prefix=CHECK-BAREMETAL // CHECK-BAREMETAL: "-fsanitize=alloc-token" -// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=alloc-token,address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INCOMPATIBLE-ADDRESS -// CHECK-INCOMPATIBLE-ADDRESS: error: invalid argument '-fsanitize=alloc-token' not allowed with '-fsanitize=address' +// RUN: %clang --target=x86_64-linux-gnu -fsanitize=alloc-token,address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ADDRESS +// RUN: %clang --target=aarch64-linux-gnu -fsanitize=address,alloc-token %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ADDRESS +// CHECK-ADDRESS: "-fsanitize=address,alloc-token" + +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=address,alloc-token -fsanitize-alloc-token-fast-abi %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ADDRESS-FAST +// CHECK-ADDRESS-FAST: error: invalid argument '-fsanitize-alloc-token-fast-abi' not allowed with '-fsanitize=address' +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=address,alloc-token -fsanitize-alloc-token-extended %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ADDRESS-EXTENDED +// CHECK-ADDRESS-EXTENDED: error: invalid argument '-fsanitize-alloc-token-extended' not allowed with '-fsanitize=address' +// RUN: not %clang --target=x86_64-apple-darwin -fsanitize=address,alloc-token %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ADDRESS-TARGET +// RUN: not %clang --target=aarch64-linux-android -fsanitize=address,alloc-token %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ADDRESS-TARGET +// RUN: not %clang --target=i386-linux-gnu -fsanitize=address,alloc-token %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ADDRESS-TARGET +// RUN: not %clang --target=x86_64-linux-gnux32 -fsanitize=address,alloc-token %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ADDRESS-TARGET +// CHECK-ADDRESS-TARGET: error: unsupported option '-fsanitize=address,alloc-token' for target // RUN: not %clang --target=x86_64-linux-gnu -fsanitize=alloc-token,memory %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INCOMPATIBLE-MEMORY // CHECK-INCOMPATIBLE-MEMORY: error: invalid argument '-fsanitize=alloc-token' not allowed with '-fsanitize=memory' diff --git a/compiler-rt/lib/asan/CMakeLists.txt b/compiler-rt/lib/asan/CMakeLists.txt index 580418fa64e312..14fd39bc42d1a2 100644 --- a/compiler-rt/lib/asan/CMakeLists.txt +++ b/compiler-rt/lib/asan/CMakeLists.txt @@ -3,6 +3,7 @@ set(ASAN_SOURCES asan_aix.cpp asan_allocator.cpp + asan_alloc_token.cpp asan_activation.cpp asan_debugging.cpp asan_descriptions.cpp diff --git a/compiler-rt/lib/asan/asan.syms.extra b/compiler-rt/lib/asan/asan.syms.extra index f8e9b3aedcc3b7..2fb970b5f3ec55 100644 --- a/compiler-rt/lib/asan/asan.syms.extra +++ b/compiler-rt/lib/asan/asan.syms.extra @@ -1,4 +1,5 @@ __asan_* +__alloc_token_* __lsan_* __ubsan_* __sancov_* diff --git a/compiler-rt/lib/asan/asan_alloc_token.cpp b/compiler-rt/lib/asan/asan_alloc_token.cpp new file mode 100644 index 00000000000000..75f44c6c857b24 --- /dev/null +++ b/compiler-rt/lib/asan/asan_alloc_token.cpp @@ -0,0 +1,171 @@ +//===-- asan_alloc_token.cpp ---------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Allocation entry points for the default AllocToken ABI. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_platform.h" + +#if SANITIZER_LINUX && !SANITIZER_ANDROID && SANITIZER_WORDSIZE == 64 && \ + (SANITIZER_X64 || defined(__aarch64__)) + +# include "asan_allocator.h" +# include "asan_report.h" +# include "asan_stack.h" + +using namespace __asan; + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE +void* __alloc_token_malloc(uptr size, uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_malloc(size, &stack, &token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void* __alloc_token_calloc(uptr count, uptr size, uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_calloc(count, size, &stack, &token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void* __alloc_token_realloc(void* ptr, uptr size, uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_realloc(ptr, size, &stack, &token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void* __alloc_token_reallocarray(void* ptr, uptr count, uptr size, uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_reallocarray(ptr, count, size, &stack, &token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void* __alloc_token_memalign(uptr alignment, uptr size, uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_memalign(alignment, size, &stack, &token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void* __alloc_token_aligned_alloc(uptr alignment, uptr size, uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_aligned_alloc(alignment, size, &stack, &token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +int __alloc_token_posix_memalign(void** ptr, uptr alignment, uptr size, + uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_posix_memalign(ptr, alignment, size, &stack, &token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void* __alloc_token_valloc(uptr size, uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_valloc(size, &stack, &token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void* __alloc_token_pvalloc(uptr size, uptr token) { + GET_STACK_TRACE_MALLOC; + return asan_pvalloc(size, &stack, &token); +} +} // extern "C" + +// Use ABI-level types for the mangled C++ allocation entry points. +# define ALLOC_TOKEN_NEW(Name, Args, Allocate, CanReturnNull) \ + extern "C" SANITIZER_INTERFACE_ATTRIBUTE void* Name Args { \ + GET_STACK_TRACE_MALLOC; \ + void* ptr = Allocate; \ + if (!CanReturnNull && UNLIKELY(!ptr)) \ + ReportOutOfMemory(size, &stack); \ + return ptr; \ + } + +# define ALLOC_TOKEN_NEW_VARIANTS(Name, New, AlignedNew) \ + ALLOC_TOKEN_NEW(Name, (uptr size, uptr token), New(size, &stack, &token), \ + false) \ + ALLOC_TOKEN_NEW(Name##RKSt9nothrow_t, \ + (uptr size, const void*, uptr token), \ + New(size, &stack, &token), true) \ + ALLOC_TOKEN_NEW(Name##St11align_val_t, \ + (uptr size, uptr alignment, uptr token), \ + AlignedNew(size, alignment, &stack, &token), false) \ + ALLOC_TOKEN_NEW(Name##St11align_val_tRKSt9nothrow_t, \ + (uptr size, uptr alignment, const void*, uptr token), \ + AlignedNew(size, alignment, &stack, &token), true) + +ALLOC_TOKEN_NEW_VARIANTS(__alloc_token__Znwm, asan_new, asan_new_aligned) +ALLOC_TOKEN_NEW_VARIANTS(__alloc_token__Znam, asan_new_array, + asan_new_array_aligned) + +// Hot/cold hints do not affect ASan's allocation policy. +# define ALLOC_TOKEN_HOT_COLD_NEW_VARIANTS(Name, New, AlignedNew) \ + ALLOC_TOKEN_NEW(Name##12__hot_cold_t, (uptr size, u8, uptr token), \ + New(size, &stack, &token), false) \ + ALLOC_TOKEN_NEW(Name##RKSt9nothrow_t12__hot_cold_t, \ + (uptr size, const void*, u8, uptr token), \ + New(size, &stack, &token), true) \ + ALLOC_TOKEN_NEW(Name##St11align_val_t12__hot_cold_t, \ + (uptr size, uptr alignment, u8, uptr token), \ + AlignedNew(size, alignment, &stack, &token), false) \ + ALLOC_TOKEN_NEW(Name##St11align_val_tRKSt9nothrow_t12__hot_cold_t, \ + (uptr size, uptr alignment, const void*, u8, uptr token), \ + AlignedNew(size, alignment, &stack, &token), true) + +ALLOC_TOKEN_HOT_COLD_NEW_VARIANTS(__alloc_token__Znwm, asan_new, + asan_new_aligned) +ALLOC_TOKEN_HOT_COLD_NEW_VARIANTS(__alloc_token__Znam, asan_new_array, + asan_new_array_aligned) +# undef ALLOC_TOKEN_HOT_COLD_NEW_VARIANTS + +# undef ALLOC_TOKEN_NEW_VARIANTS +# undef ALLOC_TOKEN_NEW + +struct SizeReturningNewResult { + void* ptr; + uptr size; +}; + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE +SizeReturningNewResult __alloc_token___size_returning_new(uptr size, + uptr token) { + GET_STACK_TRACE_MALLOC; + void* ptr = asan_new(size, &stack, &token); + if (UNLIKELY(!ptr)) + ReportOutOfMemory(size, &stack); + return {ptr, size}; +} + +SANITIZER_INTERFACE_ATTRIBUTE +SizeReturningNewResult __alloc_token___size_returning_new_aligned( + uptr size, uptr alignment, uptr token) { + GET_STACK_TRACE_MALLOC; + void* ptr = asan_new_aligned(size, alignment, &stack, &token); + if (UNLIKELY(!ptr)) + ReportOutOfMemory(size, &stack); + return {ptr, size}; +} + +SANITIZER_INTERFACE_ATTRIBUTE +SizeReturningNewResult __alloc_token___size_returning_new_hot_cold(uptr size, + u8, + uptr token) { + return __alloc_token___size_returning_new(size, token); +} + +SANITIZER_INTERFACE_ATTRIBUTE +SizeReturningNewResult __alloc_token___size_returning_new_aligned_hot_cold( + uptr size, uptr alignment, u8, uptr token) { + return __alloc_token___size_returning_new_aligned(size, alignment, token); +} +} // extern "C" + +#endif // Supported Linux targets. diff --git a/compiler-rt/lib/asan/asan_allocator.cpp b/compiler-rt/lib/asan/asan_allocator.cpp index b75a8ea09e34af..ec76d2a2811ef6 100644 --- a/compiler-rt/lib/asan/asan_allocator.cpp +++ b/compiler-rt/lib/asan/asan_allocator.cpp @@ -93,6 +93,9 @@ class ChunkHeader { atomic_uint8_t chunk_state; u8 alloc_type : 2; u8 lsan_tag : 2; +#if SANITIZER_LINUX + u8 has_alloc_token : 1; +#endif #if SANITIZER_WINDOWS // True if this was a zero-size allocation upgraded to size 1. // Used to report the original size (0) to the user via HeapSize/RtlSizeHeap. @@ -165,6 +168,11 @@ enum { class AsanChunk : public ChunkBase { public: uptr Beg() { return reinterpret_cast<uptr>(this) + kChunkHeaderSize; } + // The token follows both the user region and ChunkBase. + uptr* AllocTokenAddress() { + return reinterpret_cast<uptr*>(RoundUpTo( + Beg() + Max(UsedSize(), kChunkHeader2Size), ASAN_SHADOW_GRANULARITY)); + } bool AddrIsInside(uptr addr) { return (addr >= Beg()) && (addr < Beg() + UsedSize()); } @@ -540,7 +548,7 @@ struct Allocator { // (true) or a fatal Report*+Die() (false). void* AllocateImpl(uptr size, uptr alignment, BufferedStackTrace* stack, AllocType alloc_type, bool can_fill, - bool may_return_null) { + bool may_return_null, const uptr* alloc_token = nullptr) { if (UNLIKELY(!AsanInited())) AsanInitFromRtl(); if (UNLIKELY(IsRssLimitExceeded())) { @@ -573,6 +581,8 @@ struct Allocator { uptr needed_size = rounded_size + rz_size; if (alignment > min_alignment) needed_size += alignment; + if (alloc_token) + needed_size += ASAN_SHADOW_GRANULARITY; bool from_primary = PrimaryAllocator::CanAllocate(needed_size, alignment); // If we are allocating from the secondary allocator, there will be no // automatic right redzone, so add the right redzone manually. @@ -624,6 +634,11 @@ struct Allocator { CHECK(size); m->SetUsedSize(size); m->user_requested_alignment_log = user_requested_alignment_log; +#if SANITIZER_LINUX + m->has_alloc_token = alloc_token != nullptr; + if (alloc_token) + *m->AllocTokenAddress() = *alloc_token; +#endif m->SetAllocContext(t ? t->tid() : kMainTid, StackDepotPut(*stack)); @@ -684,9 +699,10 @@ struct Allocator { // Defer to the global, flag controlled, OOM policy. void* Allocate(uptr size, uptr alignment, BufferedStackTrace* stack, - AllocType alloc_type, bool can_fill) { + AllocType alloc_type, bool can_fill, + const uptr* alloc_token = nullptr) { return AllocateImpl(size, alignment, stack, alloc_type, can_fill, - AllocatorMayReturnNull()); + AllocatorMayReturnNull(), alloc_token); } // Set quarantine flag if chunk is allocated, issue ASan error report on @@ -799,7 +815,8 @@ struct Allocator { QuarantineChunk(m, ptr, stack); } - void* Reallocate(void* old_ptr, uptr new_size, BufferedStackTrace* stack) { + void* Reallocate(void* old_ptr, uptr new_size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr) { CHECK(old_ptr && new_size); uptr p = reinterpret_cast<uptr>(old_ptr); uptr chunk_beg = p - kChunkHeaderSize; @@ -810,7 +827,7 @@ struct Allocator { thread_stats.realloced += new_size; void* new_ptr = Allocate(new_size, /*alignment=*/8, stack, FROM_MALLOC, - /*can_fill=*/true); + /*can_fill=*/true, alloc_token); if (new_ptr) { u8 chunk_state = atomic_load(&m->chunk_state, memory_order_acquire); if (chunk_state != CHUNK_ALLOCATED) @@ -826,14 +843,14 @@ struct Allocator { } void* Calloc(uptr nmemb, uptr size, BufferedStackTrace* stack, - uptr align = 8) { + uptr align = 8, const uptr* alloc_token = nullptr) { if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) { if (AllocatorMayReturnNull()) return nullptr; ReportCallocOverflow(nmemb, size, stack); } void* ptr = Allocate(nmemb * size, align, stack, FROM_MALLOC, - /*can_fill=*/false); + /*can_fill=*/false, alloc_token); // If the memory comes from the secondary allocator no need to clear it // as it comes directly from mmap. if (ptr && allocator.FromPrimary(ptr)) @@ -1018,6 +1035,16 @@ AllocType AsanChunkView::GetAllocType() const { return (AllocType)chunk_->alloc_type; } +bool AsanChunkView::GetAllocToken(uptr* token) const { +#if SANITIZER_LINUX + if (chunk_->has_alloc_token) { + *token = *chunk_->AllocTokenAddress(); + return true; + } +#endif + return false; +} + u32 AsanChunkView::GetAllocStackId() const { u32 tid = 0; u32 stack = 0; @@ -1078,13 +1105,17 @@ void asan_free_aligned_sized(void* ptr, uptr alignment, uptr size, instance.Deallocate(ptr, size, alignment, stack, FROM_MALLOC); } -void* asan_malloc(uptr size, BufferedStackTrace* stack) { +void* asan_malloc(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token) { return SetErrnoOnNull(instance.Allocate(size, /*alignment=*/8, stack, - FROM_MALLOC, /*can_fill=*/true)); + FROM_MALLOC, /*can_fill=*/true, + alloc_token)); } -void* asan_calloc(uptr nmemb, uptr size, BufferedStackTrace* stack) { - return SetErrnoOnNull(instance.Calloc(nmemb, size, stack)); +void* asan_calloc(uptr nmemb, uptr size, BufferedStackTrace* stack, + const uptr* alloc_token) { + return SetErrnoOnNull( + instance.Calloc(nmemb, size, stack, /*align=*/8, alloc_token)); } #if SANITIZER_AIX @@ -1099,20 +1130,22 @@ void* asan_vec_calloc(uptr nmemb, uptr size, BufferedStackTrace* stack) { #endif void* asan_reallocarray(void* p, uptr nmemb, uptr size, - BufferedStackTrace* stack) { + BufferedStackTrace* stack, const uptr* alloc_token) { if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) { errno = errno_ENOMEM; if (AllocatorMayReturnNull()) return nullptr; ReportReallocArrayOverflow(nmemb, size, stack); } - return asan_realloc(p, nmemb * size, stack); + return asan_realloc(p, nmemb * size, stack, alloc_token); } -void* asan_realloc(void* p, uptr size, BufferedStackTrace* stack) { +void* asan_realloc(void* p, uptr size, BufferedStackTrace* stack, + const uptr* alloc_token) { if (!p) return SetErrnoOnNull(instance.Allocate(size, /*alignment=*/8, stack, - FROM_MALLOC, /*can_fill=*/true)); + FROM_MALLOC, /*can_fill=*/true, + alloc_token)); if (size == 0) { if (flags()->allocator_frees_and_returns_null_on_realloc_zero) { instance.Deallocate(p, 0, 0, stack, FROM_MALLOC); @@ -1121,15 +1154,18 @@ void* asan_realloc(void* p, uptr size, BufferedStackTrace* stack) { // Allocate a size of 1 if we shouldn't free() on Realloc to 0 size = 1; } - return SetErrnoOnNull(instance.Reallocate(p, size, stack)); + return SetErrnoOnNull(instance.Reallocate(p, size, stack, alloc_token)); } -void* asan_valloc(uptr size, BufferedStackTrace* stack) { +void* asan_valloc(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token) { return SetErrnoOnNull(instance.Allocate(size, GetPageSizeCached(), stack, - FROM_MALLOC, /*can_fill=*/true)); + FROM_MALLOC, /*can_fill=*/true, + alloc_token)); } -void* asan_pvalloc(uptr size, BufferedStackTrace* stack) { +void* asan_pvalloc(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token) { uptr PageSize = GetPageSizeCached(); if (UNLIKELY(CheckForPvallocOverflow(size, PageSize))) { errno = errno_ENOMEM; @@ -1139,11 +1175,12 @@ void* asan_pvalloc(uptr size, BufferedStackTrace* stack) { } // pvalloc(0) should allocate one page. size = size ? RoundUpTo(size, PageSize) : PageSize; - return SetErrnoOnNull( - instance.Allocate(size, PageSize, stack, FROM_MALLOC, /*can_fill=*/true)); + return SetErrnoOnNull(instance.Allocate(size, PageSize, stack, FROM_MALLOC, + /*can_fill=*/true, alloc_token)); } -void* asan_memalign(uptr alignment, uptr size, BufferedStackTrace* stack) { +void* asan_memalign(uptr alignment, uptr size, BufferedStackTrace* stack, + const uptr* alloc_token) { if (UNLIKELY(!IsPowerOfTwo(alignment))) { errno = errno_EINVAL; if (AllocatorMayReturnNull()) @@ -1151,10 +1188,11 @@ void* asan_memalign(uptr alignment, uptr size, BufferedStackTrace* stack) { ReportInvalidAllocationAlignment(alignment, stack); } return SetErrnoOnNull(instance.Allocate(size, alignment, stack, FROM_MALLOC, - /*can_fill=*/true)); + /*can_fill=*/true, alloc_token)); } -void* asan_aligned_alloc(uptr alignment, uptr size, BufferedStackTrace* stack) { +void* asan_aligned_alloc(uptr alignment, uptr size, BufferedStackTrace* stack, + const uptr* alloc_token) { if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(alignment, size))) { errno = errno_EINVAL; if (AllocatorMayReturnNull()) @@ -1162,18 +1200,18 @@ void* asan_aligned_alloc(uptr alignment, uptr size, BufferedStackTrace* stack) { ReportInvalidAlignedAllocAlignment(size, alignment, stack); } return SetErrnoOnNull(instance.Allocate(size, alignment, stack, FROM_MALLOC, - /*can_fill=*/true)); + /*can_fill=*/true, alloc_token)); } int asan_posix_memalign(void** memptr, uptr alignment, uptr size, - BufferedStackTrace* stack) { + BufferedStackTrace* stack, const uptr* alloc_token) { if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) { if (AllocatorMayReturnNull()) return errno_EINVAL; ReportInvalidPosixMemalignAlignment(alignment, stack); } void* ptr = instance.Allocate(size, alignment, stack, FROM_MALLOC, - /*can_fill=*/true); + /*can_fill=*/true, alloc_token); if (UNLIKELY(!ptr)) // OOM error is already taken care of by Allocate. return errno_ENOMEM; @@ -1206,19 +1244,20 @@ uptr asan_malloc_usable_size(const void* ptr, uptr pc, uptr bp) { namespace { -void* asan_new(uptr size, BufferedStackTrace* stack, bool array) { +void* asan_new(uptr size, BufferedStackTrace* stack, bool array, + const uptr* alloc_token) { return SetErrnoOnNull(instance.Allocate(size, /*alignment=*/0, stack, array ? FROM_NEW_BR : FROM_NEW, - /*can_fill=*/true)); + /*can_fill=*/true, alloc_token)); } void* asan_new_aligned(uptr size, uptr alignment, BufferedStackTrace* stack, - bool array) { + bool array, const uptr* alloc_token) { if (UNLIKELY(!CheckAlignedNewAlignment(alignment))) ReportInvalidAllocationAlignment(alignment, stack); return SetErrnoOnNull(instance.Allocate(size, alignment, stack, array ? FROM_NEW_BR : FROM_NEW, - /*can_fill=*/true)); + /*can_fill=*/true, alloc_token)); } void asan_delete(void* ptr, BufferedStackTrace* stack, bool array) { @@ -1243,21 +1282,24 @@ void asan_delete_sized_aligned(void* ptr, uptr size, uptr alignment, } // namespace -void* asan_new(uptr size, BufferedStackTrace* stack) { - return asan_new(size, stack, /*array=*/false); +void* asan_new(uptr size, BufferedStackTrace* stack, const uptr* alloc_token) { + return asan_new(size, stack, /*array=*/false, alloc_token); } -void* asan_new_aligned(uptr size, uptr alignment, BufferedStackTrace* stack) { - return asan_new_aligned(size, alignment, stack, /*array=*/false); +void* asan_new_aligned(uptr size, uptr alignment, BufferedStackTrace* stack, + const uptr* alloc_token) { + return asan_new_aligned(size, alignment, stack, /*array=*/false, alloc_token); } -void* asan_new_array(uptr size, BufferedStackTrace* stack) { - return asan_new(size, stack, /*array=*/true); +void* asan_new_array(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token) { + return asan_new(size, stack, /*array=*/true, alloc_token); } void* asan_new_array_aligned(uptr size, uptr alignment, - BufferedStackTrace* stack) { - return asan_new_aligned(size, alignment, stack, /*array=*/true); + BufferedStackTrace* stack, + const uptr* alloc_token) { + return asan_new_aligned(size, alignment, stack, /*array=*/true, alloc_token); } void asan_delete(void* ptr, BufferedStackTrace* stack) { diff --git a/compiler-rt/lib/asan/asan_allocator.h b/compiler-rt/lib/asan/asan_allocator.h index f3b215b7b05ee8..975c20afc020e1 100644 --- a/compiler-rt/lib/asan/asan_allocator.h +++ b/compiler-rt/lib/asan/asan_allocator.h @@ -66,6 +66,7 @@ class AsanChunkView { u32 GetAllocStackId() const; u32 GetFreeStackId() const; AllocType GetAllocType() const; + bool GetAllocToken(uptr* token) const; bool AddrIsInside(uptr addr, uptr access_size, sptr* offset) const { if (addr >= Beg() && (addr + access_size) <= End()) { *offset = addr - Beg(); @@ -283,34 +284,48 @@ struct AsanThreadLocalMallocStorage { AsanThreadLocalMallocStorage() {} }; -void* asan_memalign(uptr alignment, uptr size, BufferedStackTrace* stack); +// A null alloc_token means no token was supplied; zero is a valid token value. +void* asan_memalign(uptr alignment, uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); void asan_free(void* ptr, BufferedStackTrace* stack); void asan_free_sized(void* ptr, uptr size, BufferedStackTrace* stack); void asan_free_aligned_sized(void* ptr, uptr alignment, uptr size, BufferedStackTrace* stack); -void* asan_malloc(uptr size, BufferedStackTrace* stack); -void* asan_calloc(uptr nmemb, uptr size, BufferedStackTrace* stack); +void* asan_malloc(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); +void* asan_calloc(uptr nmemb, uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); #if SANITIZER_AIX void* asan_vec_malloc(uptr size, BufferedStackTrace* stack); void* asan_vec_calloc(uptr nmemb, uptr size, BufferedStackTrace* stack); #endif -void* asan_realloc(void* p, uptr size, BufferedStackTrace* stack); +void* asan_realloc(void* p, uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); void* asan_reallocarray(void* p, uptr nmemb, uptr size, - BufferedStackTrace* stack); -void* asan_valloc(uptr size, BufferedStackTrace* stack); -void* asan_pvalloc(uptr size, BufferedStackTrace* stack); - -void* asan_aligned_alloc(uptr alignment, uptr size, BufferedStackTrace* stack); + BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); +void* asan_valloc(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); +void* asan_pvalloc(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); + +void* asan_aligned_alloc(uptr alignment, uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); int asan_posix_memalign(void** memptr, uptr alignment, uptr size, - BufferedStackTrace* stack); + BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); uptr asan_malloc_usable_size(const void* ptr, uptr pc, uptr bp); -void* asan_new(uptr size, BufferedStackTrace* stack); -void* asan_new_aligned(uptr size, uptr alignment, BufferedStackTrace* stack); -void* asan_new_array(uptr size, BufferedStackTrace* stack); +void* asan_new(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); +void* asan_new_aligned(uptr size, uptr alignment, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); +void* asan_new_array(uptr size, BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); void* asan_new_array_aligned(uptr size, uptr alignment, - BufferedStackTrace* stack); + BufferedStackTrace* stack, + const uptr* alloc_token = nullptr); void asan_delete(void* ptr, BufferedStackTrace* stack); void asan_delete_aligned(void* ptr, uptr alignment, BufferedStackTrace* stack); void asan_delete_sized(void* ptr, uptr size, BufferedStackTrace* stack); diff --git a/compiler-rt/lib/asan/asan_descriptions.cpp b/compiler-rt/lib/asan/asan_descriptions.cpp index 551b819a4c436d..415121601925ec 100644 --- a/compiler-rt/lib/asan/asan_descriptions.cpp +++ b/compiler-rt/lib/asan/asan_descriptions.cpp @@ -172,6 +172,7 @@ bool GetHeapAddressInformation(uptr addr, uptr access_size, CHECK_NE(chunk.AllocTid(), kInvalidTid); descr->alloc_tid = chunk.AllocTid(); descr->alloc_stack_id = chunk.GetAllocStackId(); + descr->has_alloc_token = chunk.GetAllocToken(&descr->alloc_token); descr->free_tid = chunk.FreeTid(); if (descr->free_tid != kInvalidTid) descr->free_stack_id = chunk.GetFreeStackId(); @@ -419,6 +420,8 @@ void StackAddressDescription::Print() const { void HeapAddressDescription::Print() const { PrintHeapChunkAccess(addr, chunk_access); + if (has_alloc_token) + Printf("Allocation token: 0x%zx\n", alloc_token); asanThreadRegistry().CheckLocked(); AsanThreadContext *alloc_thread = GetThreadContextByTidLocked(alloc_tid); diff --git a/compiler-rt/lib/asan/asan_descriptions.h b/compiler-rt/lib/asan/asan_descriptions.h index a614f47d461bbd..a4ece0f6957a94 100644 --- a/compiler-rt/lib/asan/asan_descriptions.h +++ b/compiler-rt/lib/asan/asan_descriptions.h @@ -121,6 +121,8 @@ struct HeapAddressDescription { uptr free_tid; u32 alloc_stack_id; u32 free_stack_id; + uptr alloc_token; + bool has_alloc_token; ChunkAccess chunk_access; void Print() const; diff --git a/compiler-rt/test/asan/TestCases/Linux/alloc-token-runtime.cpp b/compiler-rt/test/asan/TestCases/Linux/alloc-token-runtime.cpp new file mode 100644 index 00000000000000..f544cc18d38a27 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/Linux/alloc-token-runtime.cpp @@ -0,0 +1,52 @@ +// RUN: %clangxx_asan -O0 %s -o %t +// RUN: %env_asan_opts=detect_leaks=0:allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s +// RUN: %env_asan_opts=detect_leaks=0:allocator_may_return_null=1:malloc_context_size=0 %run %t 2>&1 | FileCheck %s +// REQUIRES: linux, (x86_64-target-arch || aarch64-target-arch) +// UNSUPPORTED: android + +#include <cassert> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <sanitizer/asan_interface.h> + +extern "C" { +void *__alloc_token_calloc(size_t, size_t, size_t); +void *__alloc_token_realloc(void *, size_t, size_t); +void *__alloc_token_aligned_alloc(size_t, size_t, size_t); +} + +int main() { + void *p = __alloc_token_calloc(1, 1, 0); + assert(*static_cast<char *>(p) == 0); + __asan_describe_address(p); + void *q = __alloc_token_realloc(p, 64, SIZE_MAX); + __asan_describe_address(p); + // CHECK: Allocation token: 0x0 + // CHECK: Allocation token: 0x0 + // CHECK: freed by thread + + memset(q, 1, 64); + assert(!__alloc_token_realloc(q, SIZE_MAX, 1)); + __asan_describe_address(q); + free(q); + __asan_describe_address(q); + // CHECK: Allocation token: 0xffffffffffffffff + // CHECK: Allocation token: 0xffffffffffffffff + // CHECK: freed by thread + + p = __alloc_token_aligned_alloc(4096, 4 << 20, 0x123); + assert(reinterpret_cast<uintptr_t>(p) % 4096 == 0); + memset(p, 1, 4 << 20); + free(p); + __asan_describe_address(p); + // CHECK: Allocation token: 0x123 + + fprintf(stderr, "ordinary allocation\n"); + p = malloc(64); + __asan_describe_address(p); + free(p); + // CHECK: ordinary allocation + // CHECK-NOT: Allocation token: +} diff --git a/compiler-rt/test/asan/TestCases/Linux/alloc-token.cpp b/compiler-rt/test/asan/TestCases/Linux/alloc-token.cpp new file mode 100644 index 00000000000000..28ea7ef1505b28 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/Linux/alloc-token.cpp @@ -0,0 +1,71 @@ +// RUN: %clangxx_asan -std=c++17 -O0 -fsanitize=alloc-token -falloc-token-max=1 %s -o %t +// RUN: not %run %t 0 2>&1 | FileCheck %s --check-prefixes=CHECK,UAF +// RUN: not %run %t 1 2>&1 | FileCheck %s --check-prefixes=CHECK,UAF +// RUN: not %run %t 2 2>&1 | FileCheck %s --check-prefixes=CHECK,OVERFLOW +// RUN: not %run %t 3 2>&1 | FileCheck %s --check-prefix=PLAIN +// RUN: %if x86_64-target-arch %{ %env_asan_opts=detect_leaks=0:quarantine_size_mb=0:thread_local_quarantine_size_kb=0 %run %t 4 2>&1 | FileCheck %s --check-prefix=REUSE %} +// RUN: %clangxx_asan -std=c++17 -O2 -fsanitize=alloc-token -falloc-token-max=1 %s -o %t +// RUN: not %run %t 0 2>&1 | FileCheck %s --check-prefixes=CHECK,UAF +// RUN: not %run %t 1 2>&1 | FileCheck %s --check-prefixes=CHECK,UAF +// REQUIRES: linux, (x86_64-target-arch || aarch64-target-arch) +// UNSUPPORTED: android + +#include <cassert> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <new> +#include <sanitizer/asan_interface.h> + +struct alignas(64) Aligned { + int value; +}; + +static void report_callback(const char *report) { + assert(strstr(report, "Allocation token: 0x0")); + fprintf(stderr, "Token in report buffer\n"); +} + +__attribute__((noinline, no_sanitize("alloc-token"))) void * +ordinary_allocation() { + return malloc(40); +} + +int main(int argc, char **argv) { + int kind = atoi(argv[1]); + if (kind == 4) { + // Both requests use the same x86-64 size class. + void *p = malloc(32); + __asan_describe_address(p); + free(p); + void *q = ordinary_allocation(); + assert(p == q); + memset(q, 1, 40); + __asan_describe_address(q); + free(q); + return 0; + } + if (kind != 3) + __asan_set_error_report_callback(report_callback); + if (kind == 1) { + volatile Aligned *p = new (std::nothrow) Aligned[2]; + delete[] p; + p->value = 1; + } else { + volatile int *p = + static_cast<int *>(kind == 3 ? ordinary_allocation() : malloc(64)); + if (kind != 2) + free(const_cast<int *>(p)); + p[kind == 2 ? 16 : 0] = 1; + } +} + +// UAF: ERROR: AddressSanitizer: heap-use-after-free +// OVERFLOW: ERROR: AddressSanitizer: heap-buffer-overflow +// CHECK: Allocation token: 0x0 +// CHECK: Token in report buffer +// PLAIN: ERROR: AddressSanitizer: heap-use-after-free +// PLAIN-NOT: Allocation token: +// REUSE: Allocation token: 0x0 +// REUSE: 40-byte region +// REUSE-NOT: Allocation token: _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
