| 1 | //===-- hwasan_allocator.cpp ------------------------ ---------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file is a part of HWAddressSanitizer. |
| 10 | // |
| 11 | // HWAddressSanitizer allocator. |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "sanitizer_common/sanitizer_atomic.h" |
| 15 | #include "sanitizer_common/sanitizer_errno.h" |
| 16 | #include "sanitizer_common/sanitizer_stackdepot.h" |
| 17 | #include "hwasan.h" |
| 18 | #include "hwasan_allocator.h" |
| 19 | #include "hwasan_checks.h" |
| 20 | #include "hwasan_mapping.h" |
| 21 | #include "hwasan_malloc_bisect.h" |
| 22 | #include "hwasan_thread.h" |
| 23 | #include "hwasan_report.h" |
| 24 | #include "lsan/lsan_common.h" |
| 25 | |
| 26 | namespace __hwasan { |
| 27 | |
| 28 | static Allocator allocator; |
| 29 | static AllocatorCache fallback_allocator_cache; |
| 30 | static SpinMutex fallback_mutex; |
| 31 | static atomic_uint8_t hwasan_allocator_tagging_enabled; |
| 32 | |
| 33 | static constexpr tag_t kFallbackAllocTag = 0xBB & kTagMask; |
| 34 | static constexpr tag_t kFallbackFreeTag = 0xBC; |
| 35 | |
| 36 | enum { |
| 37 | // Either just allocated by underlying allocator, but AsanChunk is not yet |
| 38 | // ready, or almost returned to undelying allocator and AsanChunk is already |
| 39 | // meaningless. |
| 40 | CHUNK_INVALID = 0, |
| 41 | // The chunk is allocated and not yet freed. |
| 42 | CHUNK_ALLOCATED = 1, |
| 43 | }; |
| 44 | |
| 45 | |
| 46 | // Initialized in HwasanAllocatorInit, an never changed. |
| 47 | alignas(16) static u8 tail_magic[kShadowAlignment - 1]; |
| 48 | static uptr max_malloc_size; |
| 49 | |
| 50 | bool HwasanChunkView::IsAllocated() const { |
| 51 | return metadata_ && metadata_->IsAllocated(); |
| 52 | } |
| 53 | |
| 54 | uptr HwasanChunkView::Beg() const { |
| 55 | return block_; |
| 56 | } |
| 57 | uptr HwasanChunkView::End() const { |
| 58 | return Beg() + UsedSize(); |
| 59 | } |
| 60 | uptr HwasanChunkView::UsedSize() const { |
| 61 | return metadata_->GetRequestedSize(); |
| 62 | } |
| 63 | u32 HwasanChunkView::GetAllocStackId() const { |
| 64 | return metadata_->GetAllocStackId(); |
| 65 | } |
| 66 | |
| 67 | u32 HwasanChunkView::GetAllocThreadId() const { |
| 68 | return metadata_->GetAllocThreadId(); |
| 69 | } |
| 70 | |
| 71 | uptr HwasanChunkView::ActualSize() const { |
| 72 | return allocator.GetActuallyAllocatedSize(p: reinterpret_cast<void *>(block_)); |
| 73 | } |
| 74 | |
| 75 | bool HwasanChunkView::FromSmallHeap() const { |
| 76 | return allocator.FromPrimary(p: reinterpret_cast<void *>(block_)); |
| 77 | } |
| 78 | |
| 79 | bool HwasanChunkView::AddrIsInside(uptr addr) const { |
| 80 | return (addr >= Beg()) && (addr < Beg() + UsedSize()); |
| 81 | } |
| 82 | |
| 83 | inline void Metadata::SetAllocated(u32 stack, u64 size) { |
| 84 | Thread *t = GetCurrentThread(); |
| 85 | u64 context = t ? t->unique_id() : kMainTid; |
| 86 | context <<= 32; |
| 87 | context += stack; |
| 88 | requested_size_low = size & ((1ul << 32) - 1); |
| 89 | requested_size_high = size >> 32; |
| 90 | atomic_store(a: &alloc_context_id, v: context, mo: memory_order_relaxed); |
| 91 | atomic_store(a: &chunk_state, v: CHUNK_ALLOCATED, mo: memory_order_release); |
| 92 | } |
| 93 | |
| 94 | inline void Metadata::SetUnallocated() { |
| 95 | atomic_store(a: &chunk_state, v: CHUNK_INVALID, mo: memory_order_release); |
| 96 | requested_size_low = 0; |
| 97 | requested_size_high = 0; |
| 98 | atomic_store(a: &alloc_context_id, v: 0, mo: memory_order_relaxed); |
| 99 | } |
| 100 | |
| 101 | inline bool Metadata::IsAllocated() const { |
| 102 | return atomic_load(a: &chunk_state, mo: memory_order_relaxed) == CHUNK_ALLOCATED; |
| 103 | } |
| 104 | |
| 105 | inline u64 Metadata::GetRequestedSize() const { |
| 106 | return (static_cast<u64>(requested_size_high) << 32) + requested_size_low; |
| 107 | } |
| 108 | |
| 109 | inline u32 Metadata::GetAllocStackId() const { |
| 110 | return atomic_load(a: &alloc_context_id, mo: memory_order_relaxed); |
| 111 | } |
| 112 | |
| 113 | inline u32 Metadata::GetAllocThreadId() const { |
| 114 | u64 context = atomic_load(a: &alloc_context_id, mo: memory_order_relaxed); |
| 115 | u32 tid = context >> 32; |
| 116 | return tid; |
| 117 | } |
| 118 | |
| 119 | void GetAllocatorStats(AllocatorStatCounters s) { |
| 120 | allocator.GetStats(s); |
| 121 | } |
| 122 | |
| 123 | inline void Metadata::SetLsanTag(__lsan::ChunkTag tag) { |
| 124 | lsan_tag = tag; |
| 125 | } |
| 126 | |
| 127 | inline __lsan::ChunkTag Metadata::GetLsanTag() const { |
| 128 | return static_cast<__lsan::ChunkTag>(lsan_tag); |
| 129 | } |
| 130 | |
| 131 | uptr GetAliasRegionStart() { |
| 132 | #if defined(HWASAN_ALIASING_MODE) |
| 133 | constexpr uptr kAliasRegionOffset = 1ULL << (kTaggableRegionCheckShift - 1); |
| 134 | uptr AliasRegionStart = |
| 135 | __hwasan_shadow_memory_dynamic_address + kAliasRegionOffset; |
| 136 | |
| 137 | CHECK_EQ(AliasRegionStart >> kTaggableRegionCheckShift, |
| 138 | __hwasan_shadow_memory_dynamic_address >> kTaggableRegionCheckShift); |
| 139 | CHECK_EQ( |
| 140 | (AliasRegionStart + kAliasRegionOffset - 1) >> kTaggableRegionCheckShift, |
| 141 | __hwasan_shadow_memory_dynamic_address >> kTaggableRegionCheckShift); |
| 142 | return AliasRegionStart; |
| 143 | #else |
| 144 | return 0; |
| 145 | #endif |
| 146 | } |
| 147 | |
| 148 | void HwasanAllocatorInit() { |
| 149 | atomic_store_relaxed(a: &hwasan_allocator_tagging_enabled, |
| 150 | v: !flags()->disable_allocator_tagging); |
| 151 | SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null); |
| 152 | allocator.InitLinkerInitialized( |
| 153 | release_to_os_interval_ms: common_flags()->allocator_release_to_os_interval_ms, |
| 154 | heap_start: GetAliasRegionStart()); |
| 155 | for (uptr i = 0; i < sizeof(tail_magic); i++) |
| 156 | tail_magic[i] = GetCurrentThread()->GenerateRandomTag(); |
| 157 | if (common_flags()->max_allocation_size_mb) { |
| 158 | max_malloc_size = common_flags()->max_allocation_size_mb << 20; |
| 159 | max_malloc_size = Min(a: max_malloc_size, b: kMaxAllowedMallocSize); |
| 160 | } else { |
| 161 | max_malloc_size = kMaxAllowedMallocSize; |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | void HwasanAllocatorLock() { allocator.ForceLock(); } |
| 166 | |
| 167 | void HwasanAllocatorUnlock() { allocator.ForceUnlock(); } |
| 168 | |
| 169 | void AllocatorThreadStart(AllocatorCache *cache) { allocator.InitCache(cache); } |
| 170 | |
| 171 | void AllocatorThreadFinish(AllocatorCache *cache) { |
| 172 | allocator.SwallowCache(cache); |
| 173 | allocator.DestroyCache(cache); |
| 174 | } |
| 175 | |
| 176 | static uptr TaggedSize(uptr size) { |
| 177 | if (!size) size = 1; |
| 178 | uptr new_size = RoundUpTo(size, boundary: kShadowAlignment); |
| 179 | CHECK_GE(new_size, size); |
| 180 | return new_size; |
| 181 | } |
| 182 | |
| 183 | static void *HwasanAllocate(StackTrace *stack, uptr orig_size, uptr alignment, |
| 184 | bool zeroise) { |
| 185 | // Keep this consistent with LSAN and ASAN behavior. |
| 186 | if (UNLIKELY(orig_size == 0)) |
| 187 | orig_size = 1; |
| 188 | if (UNLIKELY(orig_size > max_malloc_size)) { |
| 189 | if (AllocatorMayReturnNull()) { |
| 190 | Report(format: "WARNING: HWAddressSanitizer failed to allocate 0x%zx bytes\n" , |
| 191 | orig_size); |
| 192 | return nullptr; |
| 193 | } |
| 194 | ReportAllocationSizeTooBig(user_size: orig_size, max_size: max_malloc_size, stack); |
| 195 | } |
| 196 | if (UNLIKELY(IsRssLimitExceeded())) { |
| 197 | if (AllocatorMayReturnNull()) |
| 198 | return nullptr; |
| 199 | ReportRssLimitExceeded(stack); |
| 200 | } |
| 201 | |
| 202 | alignment = Max(a: alignment, b: kShadowAlignment); |
| 203 | uptr size = TaggedSize(size: orig_size); |
| 204 | Thread *t = GetCurrentThread(); |
| 205 | void *allocated; |
| 206 | if (t) { |
| 207 | allocated = allocator.Allocate(cache: t->allocator_cache(), size, alignment); |
| 208 | } else { |
| 209 | SpinMutexLock l(&fallback_mutex); |
| 210 | AllocatorCache *cache = &fallback_allocator_cache; |
| 211 | allocated = allocator.Allocate(cache, size, alignment); |
| 212 | } |
| 213 | if (UNLIKELY(!allocated)) { |
| 214 | SetAllocatorOutOfMemory(); |
| 215 | if (AllocatorMayReturnNull()) |
| 216 | return nullptr; |
| 217 | ReportOutOfMemory(requested_size: size, stack); |
| 218 | } |
| 219 | if (zeroise) { |
| 220 | // The secondary allocator mmaps memory, which should be zero-inited so we |
| 221 | // don't need to explicitly clear it. |
| 222 | if (allocator.FromPrimary(p: allocated)) |
| 223 | internal_memset(s: allocated, c: 0, n: size); |
| 224 | } else if (flags()->max_malloc_fill_size > 0) { |
| 225 | uptr fill_size = Min(a: size, b: (uptr)flags()->max_malloc_fill_size); |
| 226 | internal_memset(s: allocated, c: flags()->malloc_fill_byte, n: fill_size); |
| 227 | } |
| 228 | if (size != orig_size) { |
| 229 | u8 *tail = reinterpret_cast<u8 *>(allocated) + orig_size; |
| 230 | uptr tail_length = size - orig_size; |
| 231 | internal_memcpy(dest: tail, src: tail_magic, n: tail_length - 1); |
| 232 | // Short granule is excluded from magic tail, so we explicitly untag. |
| 233 | tail[tail_length - 1] = 0; |
| 234 | } |
| 235 | |
| 236 | void *user_ptr = allocated; |
| 237 | if (InTaggableRegion(addr: reinterpret_cast<uptr>(user_ptr)) && |
| 238 | atomic_load_relaxed(a: &hwasan_allocator_tagging_enabled) && |
| 239 | flags()->tag_in_malloc && malloc_bisect(stack, orig_size)) { |
| 240 | tag_t tag = t ? t->GenerateRandomTag() : kFallbackAllocTag; |
| 241 | uptr tag_size = orig_size ? orig_size : 1; |
| 242 | uptr full_granule_size = RoundDownTo(x: tag_size, boundary: kShadowAlignment); |
| 243 | user_ptr = (void *)TagMemoryAligned(p: (uptr)user_ptr, size: full_granule_size, tag); |
| 244 | if (full_granule_size != tag_size) { |
| 245 | u8 *short_granule = reinterpret_cast<u8 *>(allocated) + full_granule_size; |
| 246 | TagMemoryAligned(p: (uptr)short_granule, size: kShadowAlignment, |
| 247 | tag: tag_size % kShadowAlignment); |
| 248 | short_granule[kShadowAlignment - 1] = tag; |
| 249 | } |
| 250 | } else { |
| 251 | // Tagging can not be completely skipped. If it's disabled, we need to tag |
| 252 | // with zeros. |
| 253 | user_ptr = (void *)TagMemoryAligned(p: (uptr)user_ptr, size, tag: 0); |
| 254 | } |
| 255 | |
| 256 | Metadata *meta = |
| 257 | reinterpret_cast<Metadata *>(allocator.GetMetaData(p: allocated)); |
| 258 | #if CAN_SANITIZE_LEAKS |
| 259 | meta->SetLsanTag(__lsan::DisabledInThisThread() ? __lsan::kIgnored |
| 260 | : __lsan::kDirectlyLeaked); |
| 261 | #endif |
| 262 | meta->SetAllocated(stack: StackDepotPut(stack: *stack), size: orig_size); |
| 263 | RunMallocHooks(ptr: user_ptr, size: orig_size); |
| 264 | return user_ptr; |
| 265 | } |
| 266 | |
| 267 | static bool PointerAndMemoryTagsMatch(void *tagged_ptr) { |
| 268 | CHECK(tagged_ptr); |
| 269 | uptr tagged_uptr = reinterpret_cast<uptr>(tagged_ptr); |
| 270 | if (!InTaggableRegion(addr: tagged_uptr)) |
| 271 | return true; |
| 272 | tag_t mem_tag = *reinterpret_cast<tag_t *>( |
| 273 | MemToShadow(untagged_addr: reinterpret_cast<uptr>(UntagPtr(tagged_ptr)))); |
| 274 | return PossiblyShortTagMatches(mem_tag, ptr: tagged_uptr, sz: 1); |
| 275 | } |
| 276 | |
| 277 | static bool CheckInvalidFree(StackTrace *stack, void *untagged_ptr, |
| 278 | void *tagged_ptr) { |
| 279 | // This function can return true if halt_on_error is false. |
| 280 | if (!MemIsApp(p: reinterpret_cast<uptr>(untagged_ptr)) || |
| 281 | !PointerAndMemoryTagsMatch(tagged_ptr)) { |
| 282 | ReportInvalidFree(stack, addr: reinterpret_cast<uptr>(tagged_ptr)); |
| 283 | return true; |
| 284 | } |
| 285 | return false; |
| 286 | } |
| 287 | |
| 288 | static void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) { |
| 289 | CHECK(tagged_ptr); |
| 290 | void *untagged_ptr = UntagPtr(tagged_ptr); |
| 291 | |
| 292 | if (RunFreeHooks(ptr: tagged_ptr)) |
| 293 | return; |
| 294 | |
| 295 | if (CheckInvalidFree(stack, untagged_ptr, tagged_ptr)) |
| 296 | return; |
| 297 | |
| 298 | void *aligned_ptr = reinterpret_cast<void *>( |
| 299 | RoundDownTo(x: reinterpret_cast<uptr>(untagged_ptr), boundary: kShadowAlignment)); |
| 300 | tag_t pointer_tag = GetTagFromPointer(p: reinterpret_cast<uptr>(tagged_ptr)); |
| 301 | Metadata *meta = |
| 302 | reinterpret_cast<Metadata *>(allocator.GetMetaData(p: aligned_ptr)); |
| 303 | if (!meta) { |
| 304 | ReportInvalidFree(stack, addr: reinterpret_cast<uptr>(tagged_ptr)); |
| 305 | return; |
| 306 | } |
| 307 | |
| 308 | uptr orig_size = meta->GetRequestedSize(); |
| 309 | u32 free_context_id = StackDepotPut(stack: *stack); |
| 310 | u32 alloc_context_id = meta->GetAllocStackId(); |
| 311 | u32 alloc_thread_id = meta->GetAllocThreadId(); |
| 312 | |
| 313 | bool in_taggable_region = |
| 314 | InTaggableRegion(addr: reinterpret_cast<uptr>(tagged_ptr)); |
| 315 | |
| 316 | // Check tail magic. |
| 317 | uptr tagged_size = TaggedSize(size: orig_size); |
| 318 | if (flags()->free_checks_tail_magic && orig_size && |
| 319 | tagged_size != orig_size) { |
| 320 | uptr tail_size = tagged_size - orig_size - 1; |
| 321 | CHECK_LT(tail_size, kShadowAlignment); |
| 322 | void *tail_beg = reinterpret_cast<void *>( |
| 323 | reinterpret_cast<uptr>(aligned_ptr) + orig_size); |
| 324 | tag_t short_granule_memtag = *(reinterpret_cast<tag_t *>( |
| 325 | reinterpret_cast<uptr>(tail_beg) + tail_size)); |
| 326 | if (tail_size && |
| 327 | (internal_memcmp(s1: tail_beg, s2: tail_magic, n: tail_size) || |
| 328 | (in_taggable_region && pointer_tag != short_granule_memtag))) |
| 329 | ReportTailOverwritten(stack, addr: reinterpret_cast<uptr>(tagged_ptr), |
| 330 | orig_size, expected: tail_magic); |
| 331 | } |
| 332 | |
| 333 | // TODO(kstoimenov): consider meta->SetUnallocated(free_context_id). |
| 334 | meta->SetUnallocated(); |
| 335 | // This memory will not be reused by anyone else, so we are free to keep it |
| 336 | // poisoned. |
| 337 | Thread *t = GetCurrentThread(); |
| 338 | if (flags()->max_free_fill_size > 0) { |
| 339 | uptr fill_size = |
| 340 | Min(a: TaggedSize(size: orig_size), b: (uptr)flags()->max_free_fill_size); |
| 341 | internal_memset(s: aligned_ptr, c: flags()->free_fill_byte, n: fill_size); |
| 342 | } |
| 343 | if (in_taggable_region && flags()->tag_in_free && malloc_bisect(stack, orig_size: 0) && |
| 344 | atomic_load_relaxed(a: &hwasan_allocator_tagging_enabled) && |
| 345 | allocator.FromPrimary(p: untagged_ptr) /* Secondary 0-tag and unmap.*/) { |
| 346 | // Always store full 8-bit tags on free to maximize UAF detection. |
| 347 | tag_t tag; |
| 348 | if (t) { |
| 349 | // Make sure we are not using a short granule tag as a poison tag. This |
| 350 | // would make us attempt to read the memory on a UaF. |
| 351 | // The tag can be zero if tagging is disabled on this thread. |
| 352 | do { |
| 353 | tag = t->GenerateRandomTag(/*num_bits=*/8); |
| 354 | } while ( |
| 355 | UNLIKELY((tag < kShadowAlignment || tag == pointer_tag) && tag != 0)); |
| 356 | } else { |
| 357 | static_assert(kFallbackFreeTag >= kShadowAlignment, |
| 358 | "fallback tag must not be a short granule tag." ); |
| 359 | tag = kFallbackFreeTag; |
| 360 | } |
| 361 | TagMemoryAligned(p: reinterpret_cast<uptr>(aligned_ptr), size: TaggedSize(size: orig_size), |
| 362 | tag); |
| 363 | } |
| 364 | if (t) { |
| 365 | allocator.Deallocate(cache: t->allocator_cache(), p: aligned_ptr); |
| 366 | if (auto *ha = t->heap_allocations()) |
| 367 | ha->push(t: {.tagged_addr: reinterpret_cast<uptr>(tagged_ptr), .alloc_thread_id: alloc_thread_id, |
| 368 | .alloc_context_id: alloc_context_id, .free_context_id: free_context_id, |
| 369 | .requested_size: static_cast<u32>(orig_size)}); |
| 370 | } else { |
| 371 | SpinMutexLock l(&fallback_mutex); |
| 372 | AllocatorCache *cache = &fallback_allocator_cache; |
| 373 | allocator.Deallocate(cache, p: aligned_ptr); |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | static void *HwasanReallocate(StackTrace *stack, void *tagged_ptr_old, |
| 378 | uptr new_size, uptr alignment) { |
| 379 | void *untagged_ptr_old = UntagPtr(tagged_ptr: tagged_ptr_old); |
| 380 | if (CheckInvalidFree(stack, untagged_ptr: untagged_ptr_old, tagged_ptr: tagged_ptr_old)) |
| 381 | return nullptr; |
| 382 | void *tagged_ptr_new = |
| 383 | HwasanAllocate(stack, orig_size: new_size, alignment, zeroise: false /*zeroise*/); |
| 384 | if (tagged_ptr_old && tagged_ptr_new) { |
| 385 | Metadata *meta = |
| 386 | reinterpret_cast<Metadata *>(allocator.GetMetaData(p: untagged_ptr_old)); |
| 387 | void *untagged_ptr_new = UntagPtr(tagged_ptr: tagged_ptr_new); |
| 388 | internal_memcpy(dest: untagged_ptr_new, src: untagged_ptr_old, |
| 389 | n: Min(a: new_size, b: static_cast<uptr>(meta->GetRequestedSize()))); |
| 390 | HwasanDeallocate(stack, tagged_ptr: tagged_ptr_old); |
| 391 | } |
| 392 | return tagged_ptr_new; |
| 393 | } |
| 394 | |
| 395 | static void *HwasanCalloc(StackTrace *stack, uptr nmemb, uptr size) { |
| 396 | if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) { |
| 397 | if (AllocatorMayReturnNull()) |
| 398 | return nullptr; |
| 399 | ReportCallocOverflow(count: nmemb, size, stack); |
| 400 | } |
| 401 | return HwasanAllocate(stack, orig_size: nmemb * size, alignment: sizeof(u64), zeroise: true); |
| 402 | } |
| 403 | |
| 404 | HwasanChunkView FindHeapChunkByAddress(uptr address) { |
| 405 | if (!allocator.PointerIsMine(p: reinterpret_cast<void *>(address))) |
| 406 | return HwasanChunkView(); |
| 407 | void *block = allocator.GetBlockBegin(p: reinterpret_cast<void*>(address)); |
| 408 | if (!block) |
| 409 | return HwasanChunkView(); |
| 410 | Metadata *metadata = |
| 411 | reinterpret_cast<Metadata*>(allocator.GetMetaData(p: block)); |
| 412 | return HwasanChunkView(reinterpret_cast<uptr>(block), metadata); |
| 413 | } |
| 414 | |
| 415 | static const void *AllocationBegin(const void *p) { |
| 416 | const void *untagged_ptr = UntagPtr(tagged_ptr: p); |
| 417 | if (!untagged_ptr) |
| 418 | return nullptr; |
| 419 | |
| 420 | const void *beg = allocator.GetBlockBegin(p: untagged_ptr); |
| 421 | if (!beg) |
| 422 | return nullptr; |
| 423 | |
| 424 | Metadata *b = (Metadata *)allocator.GetMetaData(p: beg); |
| 425 | if (b->GetRequestedSize() == 0) |
| 426 | return nullptr; |
| 427 | |
| 428 | tag_t tag = GetTagFromPointer(p: (uptr)p); |
| 429 | return (const void *)AddTagToPointer(p: (uptr)beg, tag); |
| 430 | } |
| 431 | |
| 432 | static uptr AllocationSize(const void *p) { |
| 433 | const void *untagged_ptr = UntagPtr(tagged_ptr: p); |
| 434 | if (!untagged_ptr) return 0; |
| 435 | const void *beg = allocator.GetBlockBegin(p: untagged_ptr); |
| 436 | if (!beg) |
| 437 | return 0; |
| 438 | Metadata *b = (Metadata *)allocator.GetMetaData(p: beg); |
| 439 | return b->GetRequestedSize(); |
| 440 | } |
| 441 | |
| 442 | static uptr AllocationSizeFast(const void *p) { |
| 443 | const void *untagged_ptr = UntagPtr(tagged_ptr: p); |
| 444 | void *aligned_ptr = reinterpret_cast<void *>( |
| 445 | RoundDownTo(x: reinterpret_cast<uptr>(untagged_ptr), boundary: kShadowAlignment)); |
| 446 | Metadata *meta = |
| 447 | reinterpret_cast<Metadata *>(allocator.GetMetaData(p: aligned_ptr)); |
| 448 | return meta->GetRequestedSize(); |
| 449 | } |
| 450 | |
| 451 | void *hwasan_malloc(uptr size, StackTrace *stack) { |
| 452 | return SetErrnoOnNull(HwasanAllocate(stack, orig_size: size, alignment: sizeof(u64), zeroise: false)); |
| 453 | } |
| 454 | |
| 455 | void *hwasan_calloc(uptr nmemb, uptr size, StackTrace *stack) { |
| 456 | return SetErrnoOnNull(HwasanCalloc(stack, nmemb, size)); |
| 457 | } |
| 458 | |
| 459 | void *hwasan_realloc(void *ptr, uptr size, StackTrace *stack) { |
| 460 | if (!ptr) |
| 461 | return SetErrnoOnNull(HwasanAllocate(stack, orig_size: size, alignment: sizeof(u64), zeroise: false)); |
| 462 | if (size == 0) { |
| 463 | HwasanDeallocate(stack, tagged_ptr: ptr); |
| 464 | return nullptr; |
| 465 | } |
| 466 | return SetErrnoOnNull(HwasanReallocate(stack, tagged_ptr_old: ptr, new_size: size, alignment: sizeof(u64))); |
| 467 | } |
| 468 | |
| 469 | void *hwasan_reallocarray(void *ptr, uptr nmemb, uptr size, StackTrace *stack) { |
| 470 | if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) { |
| 471 | errno = errno_ENOMEM; |
| 472 | if (AllocatorMayReturnNull()) |
| 473 | return nullptr; |
| 474 | ReportReallocArrayOverflow(count: nmemb, size, stack); |
| 475 | } |
| 476 | return hwasan_realloc(ptr, size: nmemb * size, stack); |
| 477 | } |
| 478 | |
| 479 | void *hwasan_valloc(uptr size, StackTrace *stack) { |
| 480 | return SetErrnoOnNull( |
| 481 | HwasanAllocate(stack, orig_size: size, alignment: GetPageSizeCached(), zeroise: false)); |
| 482 | } |
| 483 | |
| 484 | void *hwasan_pvalloc(uptr size, StackTrace *stack) { |
| 485 | uptr PageSize = GetPageSizeCached(); |
| 486 | if (UNLIKELY(CheckForPvallocOverflow(size, PageSize))) { |
| 487 | errno = errno_ENOMEM; |
| 488 | if (AllocatorMayReturnNull()) |
| 489 | return nullptr; |
| 490 | ReportPvallocOverflow(size, stack); |
| 491 | } |
| 492 | // pvalloc(0) should allocate one page. |
| 493 | size = size ? RoundUpTo(size, boundary: PageSize) : PageSize; |
| 494 | return SetErrnoOnNull(HwasanAllocate(stack, orig_size: size, alignment: PageSize, zeroise: false)); |
| 495 | } |
| 496 | |
| 497 | void *hwasan_aligned_alloc(uptr alignment, uptr size, StackTrace *stack) { |
| 498 | if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(alignment, size))) { |
| 499 | errno = errno_EINVAL; |
| 500 | if (AllocatorMayReturnNull()) |
| 501 | return nullptr; |
| 502 | ReportInvalidAlignedAllocAlignment(size, alignment, stack); |
| 503 | } |
| 504 | return SetErrnoOnNull(HwasanAllocate(stack, orig_size: size, alignment, zeroise: false)); |
| 505 | } |
| 506 | |
| 507 | void *hwasan_memalign(uptr alignment, uptr size, StackTrace *stack) { |
| 508 | if (UNLIKELY(!IsPowerOfTwo(alignment))) { |
| 509 | errno = errno_EINVAL; |
| 510 | if (AllocatorMayReturnNull()) |
| 511 | return nullptr; |
| 512 | ReportInvalidAllocationAlignment(alignment, stack); |
| 513 | } |
| 514 | return SetErrnoOnNull(HwasanAllocate(stack, orig_size: size, alignment, zeroise: false)); |
| 515 | } |
| 516 | |
| 517 | int hwasan_posix_memalign(void **memptr, uptr alignment, uptr size, |
| 518 | StackTrace *stack) { |
| 519 | if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) { |
| 520 | if (AllocatorMayReturnNull()) |
| 521 | return errno_EINVAL; |
| 522 | ReportInvalidPosixMemalignAlignment(alignment, stack); |
| 523 | } |
| 524 | void *ptr = HwasanAllocate(stack, orig_size: size, alignment, zeroise: false); |
| 525 | if (UNLIKELY(!ptr)) |
| 526 | // OOM error is already taken care of by HwasanAllocate. |
| 527 | return errno_ENOMEM; |
| 528 | CHECK(IsAligned((uptr)ptr, alignment)); |
| 529 | *memptr = ptr; |
| 530 | return 0; |
| 531 | } |
| 532 | |
| 533 | void hwasan_free(void *ptr, StackTrace *stack) { |
| 534 | return HwasanDeallocate(stack, tagged_ptr: ptr); |
| 535 | } |
| 536 | |
| 537 | } // namespace __hwasan |
| 538 | |
| 539 | // --- Implementation of LSan-specific functions --- {{{1 |
| 540 | namespace __lsan { |
| 541 | |
| 542 | void LockAllocator() { |
| 543 | __hwasan::HwasanAllocatorLock(); |
| 544 | } |
| 545 | |
| 546 | void UnlockAllocator() { |
| 547 | __hwasan::HwasanAllocatorUnlock(); |
| 548 | } |
| 549 | |
| 550 | void GetAllocatorGlobalRange(uptr *begin, uptr *end) { |
| 551 | *begin = (uptr)&__hwasan::allocator; |
| 552 | *end = *begin + sizeof(__hwasan::allocator); |
| 553 | } |
| 554 | |
| 555 | uptr PointsIntoChunk(void *p) { |
| 556 | p = UntagPtr(tagged_ptr: p); |
| 557 | uptr addr = reinterpret_cast<uptr>(p); |
| 558 | uptr chunk = |
| 559 | reinterpret_cast<uptr>(__hwasan::allocator.GetBlockBeginFastLocked(p)); |
| 560 | if (!chunk) |
| 561 | return 0; |
| 562 | __hwasan::Metadata *metadata = reinterpret_cast<__hwasan::Metadata *>( |
| 563 | __hwasan::allocator.GetMetaData(p: reinterpret_cast<void *>(chunk))); |
| 564 | if (!metadata || !metadata->IsAllocated()) |
| 565 | return 0; |
| 566 | if (addr < chunk + metadata->GetRequestedSize()) |
| 567 | return chunk; |
| 568 | if (IsSpecialCaseOfOperatorNew0(chunk_beg: chunk, chunk_size: metadata->GetRequestedSize(), addr)) |
| 569 | return chunk; |
| 570 | return 0; |
| 571 | } |
| 572 | |
| 573 | uptr GetUserBegin(uptr chunk) { |
| 574 | CHECK_EQ(UntagAddr(chunk), chunk); |
| 575 | void *block = __hwasan::allocator.GetBlockBeginFastLocked( |
| 576 | p: reinterpret_cast<void *>(chunk)); |
| 577 | if (!block) |
| 578 | return 0; |
| 579 | __hwasan::Metadata *metadata = reinterpret_cast<__hwasan::Metadata *>( |
| 580 | __hwasan::allocator.GetMetaData(p: block)); |
| 581 | if (!metadata || !metadata->IsAllocated()) |
| 582 | return 0; |
| 583 | |
| 584 | return reinterpret_cast<uptr>(block); |
| 585 | } |
| 586 | |
| 587 | uptr GetUserAddr(uptr chunk) { |
| 588 | if (!InTaggableRegion(addr: chunk)) |
| 589 | return chunk; |
| 590 | tag_t mem_tag = *(tag_t *)__hwasan::MemToShadow(untagged_addr: chunk); |
| 591 | return AddTagToPointer(p: chunk, tag: mem_tag); |
| 592 | } |
| 593 | |
| 594 | LsanMetadata::LsanMetadata(uptr chunk) { |
| 595 | CHECK_EQ(UntagAddr(chunk), chunk); |
| 596 | metadata_ = |
| 597 | chunk ? __hwasan::allocator.GetMetaData(p: reinterpret_cast<void *>(chunk)) |
| 598 | : nullptr; |
| 599 | } |
| 600 | |
| 601 | bool LsanMetadata::allocated() const { |
| 602 | if (!metadata_) |
| 603 | return false; |
| 604 | __hwasan::Metadata *m = reinterpret_cast<__hwasan::Metadata *>(metadata_); |
| 605 | return m->IsAllocated(); |
| 606 | } |
| 607 | |
| 608 | ChunkTag LsanMetadata::tag() const { |
| 609 | __hwasan::Metadata *m = reinterpret_cast<__hwasan::Metadata *>(metadata_); |
| 610 | return m->GetLsanTag(); |
| 611 | } |
| 612 | |
| 613 | void LsanMetadata::set_tag(ChunkTag value) { |
| 614 | __hwasan::Metadata *m = reinterpret_cast<__hwasan::Metadata *>(metadata_); |
| 615 | m->SetLsanTag(value); |
| 616 | } |
| 617 | |
| 618 | uptr LsanMetadata::requested_size() const { |
| 619 | __hwasan::Metadata *m = reinterpret_cast<__hwasan::Metadata *>(metadata_); |
| 620 | return m->GetRequestedSize(); |
| 621 | } |
| 622 | |
| 623 | u32 LsanMetadata::stack_trace_id() const { |
| 624 | __hwasan::Metadata *m = reinterpret_cast<__hwasan::Metadata *>(metadata_); |
| 625 | return m->GetAllocStackId(); |
| 626 | } |
| 627 | |
| 628 | void ForEachChunk(ForEachChunkCallback callback, void *arg) { |
| 629 | __hwasan::allocator.ForEachChunk(callback, arg); |
| 630 | } |
| 631 | |
| 632 | IgnoreObjectResult IgnoreObject(const void *p) { |
| 633 | p = UntagPtr(tagged_ptr: p); |
| 634 | uptr addr = reinterpret_cast<uptr>(p); |
| 635 | uptr chunk = reinterpret_cast<uptr>(__hwasan::allocator.GetBlockBegin(p)); |
| 636 | if (!chunk) |
| 637 | return kIgnoreObjectInvalid; |
| 638 | __hwasan::Metadata *metadata = reinterpret_cast<__hwasan::Metadata *>( |
| 639 | __hwasan::allocator.GetMetaData(p: reinterpret_cast<void *>(chunk))); |
| 640 | if (!metadata || !metadata->IsAllocated()) |
| 641 | return kIgnoreObjectInvalid; |
| 642 | if (addr >= chunk + metadata->GetRequestedSize()) |
| 643 | return kIgnoreObjectInvalid; |
| 644 | if (metadata->GetLsanTag() == kIgnored) |
| 645 | return kIgnoreObjectAlreadyIgnored; |
| 646 | |
| 647 | metadata->SetLsanTag(kIgnored); |
| 648 | return kIgnoreObjectSuccess; |
| 649 | } |
| 650 | |
| 651 | } // namespace __lsan |
| 652 | |
| 653 | using namespace __hwasan; |
| 654 | |
| 655 | void __hwasan_enable_allocator_tagging() { |
| 656 | atomic_store_relaxed(a: &hwasan_allocator_tagging_enabled, v: 1); |
| 657 | } |
| 658 | |
| 659 | void __hwasan_disable_allocator_tagging() { |
| 660 | atomic_store_relaxed(a: &hwasan_allocator_tagging_enabled, v: 0); |
| 661 | } |
| 662 | |
| 663 | uptr __sanitizer_get_current_allocated_bytes() { |
| 664 | uptr stats[AllocatorStatCount]; |
| 665 | allocator.GetStats(s: stats); |
| 666 | return stats[AllocatorStatAllocated]; |
| 667 | } |
| 668 | |
| 669 | uptr __sanitizer_get_heap_size() { |
| 670 | uptr stats[AllocatorStatCount]; |
| 671 | allocator.GetStats(s: stats); |
| 672 | return stats[AllocatorStatMapped]; |
| 673 | } |
| 674 | |
| 675 | uptr __sanitizer_get_free_bytes() { return 1; } |
| 676 | |
| 677 | uptr __sanitizer_get_unmapped_bytes() { return 1; } |
| 678 | |
| 679 | uptr __sanitizer_get_estimated_allocated_size(uptr size) { return size; } |
| 680 | |
| 681 | int __sanitizer_get_ownership(const void *p) { return AllocationSize(p) != 0; } |
| 682 | |
| 683 | const void *__sanitizer_get_allocated_begin(const void *p) { |
| 684 | return AllocationBegin(p); |
| 685 | } |
| 686 | |
| 687 | uptr __sanitizer_get_allocated_size(const void *p) { return AllocationSize(p); } |
| 688 | |
| 689 | uptr __sanitizer_get_allocated_size_fast(const void *p) { |
| 690 | DCHECK_EQ(p, __sanitizer_get_allocated_begin(p)); |
| 691 | uptr ret = AllocationSizeFast(p); |
| 692 | DCHECK_EQ(ret, __sanitizer_get_allocated_size(p)); |
| 693 | return ret; |
| 694 | } |
| 695 | |
| 696 | void __sanitizer_purge_allocator() { allocator.ForceReleaseToOS(); } |
| 697 | |