366 lines · cpp
1//===-- asan_fake_stack.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-exception6//7//===----------------------------------------------------------------------===//8//9// This file is a part of AddressSanitizer, an address sanity checker.10//11// FakeStack is used to detect use-after-return bugs.12//===----------------------------------------------------------------------===//13 14#include "asan_allocator.h"15#include "asan_poisoning.h"16#include "asan_thread.h"17 18namespace __asan {19 20static const u64 kMagic1 = kAsanStackAfterReturnMagic;21static const u64 kMagic2 = (kMagic1 << 8) | kMagic1;22static const u64 kMagic4 = (kMagic2 << 16) | kMagic2;23static const u64 kMagic8 = (kMagic4 << 32) | kMagic4;24 25static const u64 kAllocaRedzoneSize = 32UL;26static const u64 kAllocaRedzoneMask = 31UL;27 28// For small size classes inline PoisonShadow for better performance.29ALWAYS_INLINE void SetShadow(uptr ptr, uptr size, uptr class_id, u64 magic) {30 CHECK(AddrIsAlignedByGranularity(ptr + size));31 u64* shadow = reinterpret_cast<u64*>(MemToShadow(ptr));32 if (ASAN_SHADOW_SCALE == 3 && class_id <= 6) {33 // This code expects ASAN_SHADOW_SCALE=3.34 for (uptr i = 0; i < (((uptr)1) << class_id); i++) {35 shadow[i] = magic;36 // Make sure this does not become memset.37 SanitizerBreakOptimization(nullptr);38 }39 } else {40 // The size class is too big, it's cheaper to poison only size bytes.41 PoisonShadow(ptr, size, static_cast<u8>(magic));42 }43 44 if (magic == 0) {45 uptr redzone_size = FakeStack::BytesInSizeClass(class_id) - size;46 PoisonShadow(ptr + size, redzone_size, kAsanStackRightRedzoneMagic);47 }48}49 50FakeStack* FakeStack::Create(uptr stack_size_log) {51 static uptr kMinStackSizeLog = 16;52 static uptr kMaxStackSizeLog = FIRST_32_SECOND_64(24, 28);53 if (stack_size_log < kMinStackSizeLog)54 stack_size_log = kMinStackSizeLog;55 if (stack_size_log > kMaxStackSizeLog)56 stack_size_log = kMaxStackSizeLog;57 CHECK_LE(kMaxStackFrameSizeLog, stack_size_log);58 uptr size = RequiredSize(stack_size_log);59 uptr padded_size = size + kMaxStackFrameSize;60 void* true_res = reinterpret_cast<void*>(61 flags()->uar_noreserve ? MmapNoReserveOrDie(padded_size, "FakeStack")62 : MmapOrDie(padded_size, "FakeStack"));63 // GetFrame() requires the property that64 // (res + kFlagsOffset + SizeRequiredForFlags(stack_size_log)) is aligned to65 // kMaxStackFrameSize.66 // We didn't use MmapAlignedOrDieOnFatalError, because it requires that the67 // *size* is a power of 2, which is an overly strong condition.68 static_assert(alignof(FakeStack) <= kMaxStackFrameSize);69 FakeStack* res = reinterpret_cast<FakeStack*>(70 RoundUpTo(71 (uptr)true_res + kFlagsOffset + SizeRequiredForFlags(stack_size_log),72 kMaxStackFrameSize) -73 kFlagsOffset - SizeRequiredForFlags(stack_size_log));74 res->true_start = true_res;75 res->stack_size_log_ = stack_size_log;76 u8* p = reinterpret_cast<u8*>(res);77 VReport(1,78 "T%d: FakeStack created: %p -- %p stack_size_log: %zd; "79 "mmapped %zdK, noreserve=%d, true_start: %p, start of first frame: "80 "0x%zx\n",81 GetCurrentTidOrInvalid(), (void*)p,82 (void*)(p + FakeStack::RequiredSize(stack_size_log)), stack_size_log,83 size >> 10, flags()->uar_noreserve, res->true_start,84 res->GetFrame(stack_size_log, /*class_id*/ 0, /*pos*/ 0));85 return res;86}87 88void FakeStack::Destroy(int tid) {89 PoisonAll(0);90 if (Verbosity() >= 2) {91 InternalScopedString str;92 for (uptr class_id = 0; class_id < kNumberOfSizeClasses; class_id++)93 str.AppendF("%zd: %zd/%zd; ", class_id, hint_position_[class_id],94 NumberOfFrames(stack_size_log(), class_id));95 Report("T%d: FakeStack destroyed: %s\n", tid, str.data());96 }97 uptr size = RequiredSize(stack_size_log_);98 uptr padded_size = size + kMaxStackFrameSize;99 FlushUnneededASanShadowMemory(reinterpret_cast<uptr>(true_start),100 padded_size);101 UnmapOrDie(true_start, padded_size);102}103 104void FakeStack::PoisonAll(u8 magic) {105 PoisonShadow(reinterpret_cast<uptr>(this), RequiredSize(stack_size_log()),106 magic);107}108 109#if !defined(_MSC_VER) || defined(__clang__)110ALWAYS_INLINE USED111#endif112 FakeFrame* FakeStack::Allocate(uptr stack_size_log, uptr class_id,113 uptr real_stack) {114 CHECK_LT(class_id, kNumberOfSizeClasses);115 if (needs_gc_)116 GC(real_stack);117 uptr& hint_position = hint_position_[class_id];118 const int num_iter = NumberOfFrames(stack_size_log, class_id);119 u8* flags = GetFlags(stack_size_log, class_id);120 for (int i = 0; i < num_iter; i++) {121 uptr pos = ModuloNumberOfFrames(stack_size_log, class_id, hint_position++);122 // This part is tricky. On one hand, checking and setting flags[pos]123 // should be atomic to ensure async-signal safety. But on the other hand,124 // if the signal arrives between checking and setting flags[pos], the125 // signal handler's fake stack will start from a different hint_position126 // and so will not touch this particular byte. So, it is safe to do this127 // with regular non-atomic load and store (at least I was not able to make128 // this code crash).129 if (flags[pos])130 continue;131 flags[pos] = 1;132 FakeFrame* res =133 reinterpret_cast<FakeFrame*>(GetFrame(stack_size_log, class_id, pos));134 res->real_stack = real_stack;135 *SavedFlagPtr(reinterpret_cast<uptr>(res), class_id) = &flags[pos];136 return res;137 }138 return nullptr; // We are out of fake stack.139}140 141uptr FakeStack::AddrIsInFakeStack(uptr ptr, uptr* frame_beg, uptr* frame_end) {142 uptr stack_size_log = this->stack_size_log();143 uptr beg = reinterpret_cast<uptr>(GetFrame(stack_size_log, 0, 0));144 uptr end = reinterpret_cast<uptr>(this) + RequiredSize(stack_size_log);145 if (ptr < beg || ptr >= end)146 return 0;147 uptr class_id = (ptr - beg) >> stack_size_log;148 uptr base = beg + (class_id << stack_size_log);149 CHECK_LE(base, ptr);150 CHECK_LT(ptr, base + (((uptr)1) << stack_size_log));151 uptr pos = (ptr - base) >> (kMinStackFrameSizeLog + class_id);152 uptr res = base + pos * BytesInSizeClass(class_id);153 *frame_end = res + BytesInSizeClass(class_id);154 *frame_beg = res + sizeof(FakeFrame);155 return res;156}157 158void FakeStack::HandleNoReturn() { needs_gc_ = true; }159 160// Hack: The statement below is not true if we take into account sigaltstack or161// makecontext. It should be possible to make GC to discard wrong stack frame if162// we use these tools. For now, let's support the simplest case and allow GC to163// discard only frames from the default stack, assuming there is no buffer on164// the stack which is used for makecontext or sigaltstack.165//166// When throw, longjmp or some such happens we don't call OnFree() and167// as the result may leak one or more fake frames, but the good news is that168// we are notified about all such events by HandleNoReturn().169// If we recently had such no-return event we need to collect garbage frames.170// We do it based on their 'real_stack' values -- everything that is lower171// than the current real_stack is garbage.172NOINLINE void FakeStack::GC(uptr real_stack) {173 AsanThread* curr_thread = GetCurrentThread();174 if (!curr_thread)175 return; // Try again when we have a thread.176 auto top = curr_thread->stack_top();177 auto bottom = curr_thread->stack_bottom();178 if (real_stack < bottom || real_stack > top)179 return; // Not the default stack.180 181 for (uptr class_id = 0; class_id < kNumberOfSizeClasses; class_id++) {182 u8* flags = GetFlags(stack_size_log(), class_id);183 for (uptr i = 0, n = NumberOfFrames(stack_size_log(), class_id); i < n;184 i++) {185 if (flags[i] == 0)186 continue; // not allocated.187 FakeFrame* ff =188 reinterpret_cast<FakeFrame*>(GetFrame(stack_size_log(), class_id, i));189 // GC only on the default stack.190 if (bottom < ff->real_stack && ff->real_stack < real_stack) {191 flags[i] = 0;192 // Poison the frame, so the any access will be reported as UAR.193 SetShadow(reinterpret_cast<uptr>(ff), BytesInSizeClass(class_id),194 class_id, kMagic8);195 }196 }197 }198 needs_gc_ = false;199}200 201void FakeStack::ForEachFakeFrame(RangeIteratorCallback callback, void* arg) {202 for (uptr class_id = 0; class_id < kNumberOfSizeClasses; class_id++) {203 u8* flags = GetFlags(stack_size_log(), class_id);204 for (uptr i = 0, n = NumberOfFrames(stack_size_log(), class_id); i < n;205 i++) {206 if (flags[i] == 0)207 continue; // not allocated.208 FakeFrame* ff =209 reinterpret_cast<FakeFrame*>(GetFrame(stack_size_log(), class_id, i));210 uptr begin = reinterpret_cast<uptr>(ff);211 callback(begin, begin + FakeStack::BytesInSizeClass(class_id), arg);212 }213 }214}215 216#if (SANITIZER_LINUX && !SANITIZER_ANDROID) || SANITIZER_FUCHSIA217static THREADLOCAL FakeStack* fake_stack_tls;218 219static FakeStack* GetTLSFakeStack() { return fake_stack_tls; }220static void SetTLSFakeStack(FakeStack* fs) { fake_stack_tls = fs; }221void ResetTLSFakeStack() { fake_stack_tls = nullptr; }222#else223static FakeStack* GetTLSFakeStack() { return nullptr; }224static void SetTLSFakeStack(FakeStack*) {}225void ResetTLSFakeStack() {}226#endif // (SANITIZER_LINUX && !SANITIZER_ANDROID) || SANITIZER_FUCHSIA227 228static FakeStack* GetFakeStack() {229 AsanThread* t = GetCurrentThread();230 if (!t)231 return nullptr;232 return t->get_or_create_fake_stack();233}234 235static FakeStack* GetFakeStackFast() {236 FakeStack* fs = GetTLSFakeStack();237 if (LIKELY(fs))238 return fs;239 if (!__asan_option_detect_stack_use_after_return)240 return nullptr;241 fs = GetFakeStack();242 if (LIKELY(fs))243 SetTLSFakeStack(fs);244 return fs;245}246 247static FakeStack* GetFakeStackFastAlways() {248 FakeStack* fs = GetTLSFakeStack();249 if (LIKELY(fs))250 return fs;251 fs = GetFakeStack();252 if (LIKELY(fs))253 SetTLSFakeStack(fs);254 return fs;255}256 257static ALWAYS_INLINE uptr OnMalloc(uptr class_id, uptr size) {258 FakeStack* fs = GetFakeStackFast();259 if (!fs)260 return 0;261 FakeFrame* ff =262 fs->Allocate(fs->stack_size_log(), class_id, GET_CURRENT_FRAME());263 if (!ff)264 return 0; // Out of fake stack.265 uptr ptr = reinterpret_cast<uptr>(ff);266 SetShadow(ptr, size, class_id, 0);267 return ptr;268}269 270static ALWAYS_INLINE uptr OnMallocAlways(uptr class_id, uptr size) {271 FakeStack* fs = GetFakeStackFastAlways();272 if (!fs)273 return 0;274 FakeFrame* ff =275 fs->Allocate(fs->stack_size_log(), class_id, GET_CURRENT_FRAME());276 if (!ff)277 return 0; // Out of fake stack.278 uptr ptr = reinterpret_cast<uptr>(ff);279 SetShadow(ptr, size, class_id, 0);280 return ptr;281}282 283static ALWAYS_INLINE void OnFree(uptr ptr, uptr class_id, uptr size) {284 FakeStack::Deallocate(ptr, class_id);285 SetShadow(ptr, size, class_id, kMagic8);286}287 288} // namespace __asan289 290// ---------------------- Interface ---------------- {{{1291using namespace __asan;292#define DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(class_id) \293 extern "C" SANITIZER_INTERFACE_ATTRIBUTE uptr \294 __asan_stack_malloc_##class_id(uptr size) { \295 return OnMalloc(class_id, size); \296 } \297 extern "C" SANITIZER_INTERFACE_ATTRIBUTE uptr \298 __asan_stack_malloc_always_##class_id(uptr size) { \299 return OnMallocAlways(class_id, size); \300 } \301 extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __asan_stack_free_##class_id( \302 uptr ptr, uptr size) { \303 OnFree(ptr, class_id, size); \304 }305 306DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(0)307DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(1)308DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(2)309DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(3)310DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(4)311DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(5)312DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(6)313DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(7)314DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(8)315DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(9)316DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID(10)317 318extern "C" {319// TODO: remove this method and fix tests that use it by setting320// -asan-use-after-return=never, after modal UAR flag lands321// (https://github.com/google/sanitizers/issues/1394)322SANITIZER_INTERFACE_ATTRIBUTE323void* __asan_get_current_fake_stack() { return GetFakeStackFast(); }324 325SANITIZER_INTERFACE_ATTRIBUTE326void* __asan_addr_is_in_fake_stack(void* fake_stack, void* addr, void** beg,327 void** end) {328 FakeStack* fs = reinterpret_cast<FakeStack*>(fake_stack);329 if (!fs)330 return nullptr;331 uptr frame_beg, frame_end;332 FakeFrame* frame = reinterpret_cast<FakeFrame*>(fs->AddrIsInFakeStack(333 reinterpret_cast<uptr>(addr), &frame_beg, &frame_end));334 if (!frame)335 return nullptr;336 if (frame->magic != kCurrentStackFrameMagic)337 return nullptr;338 if (beg)339 *beg = reinterpret_cast<void*>(frame_beg);340 if (end)341 *end = reinterpret_cast<void*>(frame_end);342 return reinterpret_cast<void*>(frame->real_stack);343}344 345SANITIZER_INTERFACE_ATTRIBUTE346void __asan_alloca_poison(uptr addr, uptr size) {347 uptr LeftRedzoneAddr = addr - kAllocaRedzoneSize;348 uptr PartialRzAddr = addr + size;349 uptr RightRzAddr = (PartialRzAddr + kAllocaRedzoneMask) & ~kAllocaRedzoneMask;350 uptr PartialRzAligned = PartialRzAddr & ~(ASAN_SHADOW_GRANULARITY - 1);351 FastPoisonShadow(LeftRedzoneAddr, kAllocaRedzoneSize, kAsanAllocaLeftMagic);352 FastPoisonShadowPartialRightRedzone(353 PartialRzAligned, PartialRzAddr % ASAN_SHADOW_GRANULARITY,354 RightRzAddr - PartialRzAligned, kAsanAllocaRightMagic);355 FastPoisonShadow(RightRzAddr, kAllocaRedzoneSize, kAsanAllocaRightMagic);356}357 358SANITIZER_INTERFACE_ATTRIBUTE359void __asan_allocas_unpoison(uptr top, uptr bottom) {360 if ((!top) || (top > bottom))361 return;362 REAL(memset)(reinterpret_cast<void*>(MemToShadow(top)), 0,363 (bottom - top) / ASAN_SHADOW_GRANULARITY);364}365} // extern "C"366