hwasan源码探究

llvm版本:cd4c10afea7eaaf87c1830e340863f0bf8745b0b

直接在llvm官网找到github链接clone就可以了。

常用函数

user内存和影子内存的转换,非常好理解:

//compiler-rt/lib/hwasan/hwasan_mapping.h​
inline uptr GetShadowOffset() {​
 return SANITIZER_FUCHSIA ? 0 : __hwasan_shadow_memory_dynamic_address;​
}​
inline uptr MemToShadow(uptr untagged_addr) {​
 return (untagged_addr >> kShadowScale) + GetShadowOffset();​
}​
inline uptr ShadowToMem(uptr shadow_addr) {​
 return (shadow_addr - GetShadowOffset()) << kShadowScale;​
}​
inline uptr MemToShadowSize(uptr size) {​
 return size >> kShadowScale;​
}​

bool MemIsApp(uptr p);​

inline bool MemIsShadow(uptr p) {​
 return (kLowShadowStart <= p && p <= kLowShadowEnd) ||
 (kHighShadowStart <= p && p <= kHighShadowEnd);​
}​

堆分配记录

每个线程都有线程本地变量,记录每个分配。

static void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) {​
 CHECK(tagged_ptr);​
 void *untagged_ptr = UntagPtr(tagged_ptr);​


 void *aligned_ptr = reinterpret_cast<void *>(​
 RoundDownTo(reinterpret_cast<uptr>(untagged_ptr), kShadowAlignment));​
 tag_t pointer_tag = GetTagFromPointer(reinterpret_cast<uptr>(tagged_ptr));​
 Metadata *meta =
 reinterpret_cast<Metadata *>(allocator.GetMetaData(aligned_ptr));​
 if (!meta) {​
 ReportInvalidFree(stack, reinterpret_cast<uptr>(tagged_ptr));​
 return;​
 }​

 uptr orig_size = meta->GetRequestedSize();​
 u32 free_context_id = StackDepotPut(*stack);​
 u32 alloc_context_id = meta->GetAllocStackId();​
 u32 alloc_thread_id = meta->GetAllocThreadId();​

 bool in_taggable_region =
 InTaggableRegion(reinterpret_cast<uptr>(tagged_ptr));​

 // Check tail magic.​
 uptr tagged_size = TaggedSize(orig_size);​
 if (flags()->free_checks_tail_magic && orig_size &&
 tagged_size != orig_size) {​
 uptr tail_size = tagged_size - orig_size - 1;​
 CHECK_LT(tail_size, kShadowAlignment);​
 void *tail_beg = reinterpret_cast<void *>(​
 reinterpret_cast<uptr>(aligned_ptr) + orig_size);​
 tag_t short_granule_memtag = *(reinterpret_cast<tag_t *>(​
 reinterpret_cast<uptr>(tail_beg) + tail_size));​
 if (tail_size &&
 (internal_memcmp(tail_beg, tail_magic, tail_size) ||
 (in_taggable_region && pointer_tag != short_granule_memtag)))​
 ReportTailOverwritten(stack, reinterpret_cast<uptr>(tagged_ptr),​
 orig_size, tail_magic);​
 }​

 // TODO(kstoimenov): consider meta->SetUnallocated(free_context_id).​
 meta->SetUnallocated();​
 // This memory will not be reused by anyone else, so we are free to keep it​
 // poisoned.​
 Thread *t = GetCurrentThread();​
 if (flags()->max_free_fill_size > 0) {​
 uptr fill_size =
 Min(TaggedSize(orig_size), (uptr)flags()->max_free_fill_size);​
 internal_memset(aligned_ptr, flags()->free_fill_byte, fill_size);​
 }​
 if (in_taggable_region && flags()->tag_in_free && malloc_bisect(stack, 0) &&
 atomic_load_relaxed(&hwasan_allocator_tagging_enabled) &&
 allocator.FromPrimary(untagged_ptr) /* Secondary 0-tag and unmap.*/) {​
 // Always store full 8-bit tags on free to maximize UAF detection.​
 tag_t tag;​
 if (t) {​
 // Make sure we are not using a short granule tag as a poison tag. This​
 // would make us attempt to read the memory on a UaF.​
 // The tag can be zero if tagging is disabled on this thread.​
 do {​
 tag = t->GenerateRandomTag(/*num_bits=*/8);​
 } while (​
 UNLIKELY((tag < kShadowAlignment || tag == pointer_tag) && tag != 0));​
 } else {​
 static_assert(kFallbackFreeTag >= kShadowAlignment,​
 "fallback tag must not be a short granule tag.");​
 tag = kFallbackFreeTag;​
 }​
 TagMemoryAligned(reinterpret_cast<uptr>(aligned_ptr), TaggedSize(orig_size),​
 tag);​
 }​
 if (t) {​
 allocator.Deallocate(t->allocator_cache(), aligned_ptr);​
 // 这是唯一往Thread::heap_allocations()里push数据的地方。也就是说Thread::heap_allocations()里记录的都是​
 // 成对的分配​
 if (auto *ha = t->heap_allocations())​
 ha->push({reinterpret_cast<uptr>(tagged_ptr), alloc_thread_id,​
 alloc_context_id, free_context_id,​
 static_cast<u32>(orig_size)});​
 } else {​
 SpinMutexLock l(&fallback_mutex);​
 AllocatorCache *cache = &fallback_allocator_cache;​
 allocator.Deallocate(cache, aligned_ptr);​
 }​
}
 

重点:

每次成对的分配/释放,会以数据结构HeapAllocationRecord的形式保存在Thread::heap_allocations_成员中。

问题Report

问题上报是hwasan中很关键的逻辑,这直接决定我们如何理解hwasan报错,以及如何分析问题。

hwasan报错分为以下几类:

  1. InvalidFreeReport

  2. TailOverwrittenReport

  3. TagMismatchReport(最常见)

我们先看最常见的TagMismatchReport的整个调用链:

HandleTagMismatch hwasan.cpp​
 ReportTagMismatch // 构造TagMismatchReport对象,之后在其析构函数中执行上报逻辑​
 TagMismatchReport::TagMismatchReport​
 BaseReport::BaseReport // 调用父类的构造函数​
 // 以下是BaseReport构造过程中的关键函数,这些函数会保存各类重要的数据,包括堆、栈的分配等​
 UntagAddr // 保存一份不含tag的ptr​
 GetTagFromPointer // 获取ptr tag​
 FindMismatchOffset​
 CopyHeapChunk // 拷贝ptr指向的附近user内存的值​
 CopyAllocations // 拷贝ptr指向内存的分配记录(如果有)​
 FindBufferOverflowCandidate // 寻找overflow原本访问的内存​
 CopyShadow // 拷贝ptr指向的附近影子内存的值​
 TagMismatchReport::~TagMismatchReport //执行上报逻辑​
 PrintAddressDescription // 输出报错类型​
 PrintTags​
 ReportErrorSummary

接下来分步骤看整个过程。

TagMismatchReport构造过程

很重要,很多变量都是在此时初始化:

class BaseReport {​
 public:
 /**​
 * stack: 报错栈​
 * tagged_addr: 报错地址​
 */
 BaseReport(StackTrace *stack, bool fatal, uptr tagged_addr, uptr access_size)​
 : scoped_report(fatal),​
 stack(stack),​
 tagged_addr(tagged_addr),​
 access_size(access_size),​
 untagged_addr(UntagAddr(tagged_addr)),​
 ptr_tag(GetTagFromPointer(tagged_addr)),​
 mismatch_offset(FindMismatchOffset()),​
 heap(CopyHeapChunk()),​
 allocations(CopyAllocations()),​
 candidate(FindBufferOverflowCandidate()),​
 shadow(CopyShadow()) {}

拷贝堆、栈分配记录

TagMismatchReport构造过程

很重要,很多变量都是在此时初始化:

class BaseReport {​
 public:
 /**​
 * stack: 报错栈​
 * tagged_addr: 报错地址​
 */
 BaseReport(StackTrace *stack, bool fatal, uptr tagged_addr, uptr access_size)​
 : scoped_report(fatal),​
 stack(stack),​
 tagged_addr(tagged_addr),​
 access_size(access_size),​
 untagged_addr(UntagAddr(tagged_addr)),​
 ptr_tag(GetTagFromPointer(tagged_addr)),​
 mismatch_offset(FindMismatchOffset()),​
 heap(CopyHeapChunk()),​
 allocations(CopyAllocations()),​
 candidate(FindBufferOverflowCandidate()),​
 shadow(CopyShadow()) {}​

拷贝堆、栈分配记录

BaseReport::Allocations BaseReport::CopyAllocations() {​
 if (MemIsShadow(untagged_addr))​
 return {};​
 uptr stack_allocations_count = 0;​
 uptr heap_allocations_count = 0;​
 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) {​
 if (stack_allocations_count < ARRAY_SIZE(stack_allocations_storage) &&
 t->AddrIsInStack(untagged_addr)) {​
 stack_allocations_storage[stack_allocations_count++].CopyFrom(t);​
 }​

 if (heap_allocations_count < ARRAY_SIZE(heap_allocations_storage)) {​
 // Scan all threads' ring buffers to find if it's a heap-use-after-free.​
 HeapAllocationRecord har;​
 uptr ring_index, num_matching_addrs, num_matching_addrs_4b;​
 if (FindHeapAllocation(t->heap_allocations(), tagged_addr, &har,​
 &ring_index, &num_matching_addrs,​
 &num_matching_addrs_4b)) {​
 //在某个线程找到了对应的分配。注意这个分配是一定包含了释放的,可看“堆分配记录”一节​
 auto &ha = heap_allocations_storage[heap_allocations_count++];​
 ha.har = har;​
 ha.ring_index = ring_index;​
 ha.num_matching_addrs = num_matching_addrs;​
 ha.num_matching_addrs_4b = num_matching_addrs_4b;​
 ha.free_thread_id = t->unique_id();​
 }​
 }​
 });​

 return {{stack_allocations_storage, stack_allocations_count},​
 {heap_allocations_storage, heap_allocations_count}};​
}​

寻找可能的溢出访问的buffer

FindBufferOverflowCandidate实现:

BaseReport::OverflowCandidate BaseReport::FindBufferOverflowCandidate() const {​
 OverflowCandidate result = {};​
 if (MemIsShadow(untagged_addr))​
 return result;​
 // Check if this looks like a heap buffer overflow by scanning​
 // the shadow left and right and looking for the first adjacent​
 // object with a different memory tag. If that tag matches ptr_tag,​
 // check the allocator if it has a live chunk there.​
 tag_t *tag_ptr = reinterpret_cast<tag_t *>(MemToShadow(untagged_addr));​
 tag_t *candidate_tag_ptr = nullptr, *left = tag_ptr, *right = tag_ptr;​
 uptr candidate_distance = 0;​

 从报错地址对应的影子内存开始前后1000字节的范围内找tag能够匹配的内存。实际user内存的查找范围就是16*1000=16000字节,接近4页。​
 for (; candidate_distance < 1000; candidate_distance++) {​
 if (MemIsShadow(reinterpret_cast<uptr>(left)) && TagsEqual(ptr_tag, left)) {​
 candidate_tag_ptr = left;​
 break;​
 }​
 --left;​
 if (MemIsShadow(reinterpret_cast<uptr>(right)) &&
 TagsEqual(ptr_tag, right)) {​
 candidate_tag_ptr = right;​
 break;​
 }​
 ++right;​
 }​

 报错地址和能匹配上tag的内存不超过16字节才算is_close。​
 constexpr auto kCloseCandidateDistance = 1;​
 result.is_close = candidate_distance <= kCloseCandidateDistance;​

 result.after = candidate_tag_ptr == left;​
 result.untagged_addr = ShadowToMem(reinterpret_cast<uptr>(candidate_tag_ptr));​
 HwasanChunkView chunk = FindHeapChunkByAddress(result.untagged_addr);​
 if (chunk.IsAllocated()) {​
 result.heap.is_allocated = true;​
 result.heap.begin = chunk.Beg();​
 result.heap.end = chunk.End();​
 result.heap.thread_id = chunk.GetAllocThreadId();​
 result.heap.stack_id = chunk.GetAllocStackId();​
 }​
 return result;​
}

这里反映出,现在的hwasan在检测overflow时确实不够严谨。

现在的方法是,从ptr指向内存开始向两边找,如果能在1000字节的影子内存范围内,找到最近的能匹配ptr的内存,就认为是发生了overflow。

但现实中很可能出现一种情况:

  1. ptr指向的内存地址A被释放。

  2. A被分配给了其它指针。

  3. ptr指向内存的16000字节内恰好随机到一个相同tag的内存块。

  4. 此时再用ptr去访问原内存(且不超过原内存的范围),发现原内存被分配,但tag不一致。于是前后找,找到了那个恰好相同的tag,于是判定为overflow。

但很明显,按照代码逻辑,这应该算是UAF(只不过被free的内存再次被分配),但却被识别为heap overflow。这正是问题SF signalFrameUpdate hwasan UAF问题分析中遇到的情况。这类问题可以称之为“标签重用”问题。

那么这是我们就要问一个问题了:为什么hwasan会这么设计?为什么不能严谨地判断?

尝试查找相关资料,但没能找到。这里我尝试推测一下。

我认为是实现成本的问题。试想,如果要严谨的判断UAF,我们需要哪些信息?我们是不是得知道每次分配的地址、size和tag,然后还要给这次分配记录一个id。这样当用ptr访问内存时,我们才能查到一次分配的信息,进而判断UAF。

但是在一个复杂的程序里,我们不可能把每一次分配都记录下来,因为这个数据量实在是太大了。当然我们可以给每个内存块最多只记录一次分配,但即便如此数据量仍然很大,而且维护这样的数据运行时开销也很大,毕竟每次内存访问都要执行这些判断。

所以我认为这是hwasan设计,也就是标签匹配方案本身的一个折中。它能够在尽可能检测出问题的情况下,保证运行时开销能接受。否则哪些偶现问题更难以解决了。

报错流程

// compiler-rt/lib/hwasan/hwasan_report.cpp​
TagMismatchReport::~TagMismatchReport() {​
 Decorator d;​
 // TODO: when possible, try to print heap-use-after-free, etc.​
 const char *bug_type = "tag-mismatch";​
 uptr pc = GetTopPc(stack);​
 Printf("%s", d.Error());​
 Report("ERROR: %s: %s on address %p at pc %p\n", SanitizerToolName, bug_type,​
 untagged_addr, pc);​

 Thread *t = GetCurrentThread();​

 tag_t mem_tag = GetTagCopy(MemToShadow(untagged_addr + mismatch_offset));​

 Printf("%s", d.Access());​
 // 输出ptr/mem的tag信息​
 if (mem_tag && mem_tag < kShadowAlignment) {​
 // mem_tag小于16,会输出short tag和随机tag​
 tag_t short_tag =​
 GetShortTagCopy(MemToShadow(untagged_addr + mismatch_offset));​
 Printf(​
 "%s of size %zu at %p tags: %02x/%02x(%02x) (ptr/mem) in thread T%zd\n",​
 is_store ? "WRITE" : "READ", access_size, untagged_addr, ptr_tag,​
 mem_tag, short_tag, t->unique_id());​
 } else {​
 Printf("%s of size %zu at %p tags: %02x/%02x (ptr/mem) in thread T%zd\n",​
 is_store ? "WRITE" : "READ", access_size, untagged_addr, ptr_tag,​
 mem_tag, t->unique_id());​
 }​
 if (mismatch_offset)​
 Printf("Invalid access starting at offset %zu\n", mismatch_offset);​
 Printf("%s", d.Default());​

 stack->Print();​

 //这里输出错误类型,包括UAF,Heap buffer overflow等​
 PrintAddressDescription();​
 t->Announce();​

 PrintTags(untagged_addr + mismatch_offset);​

 if (registers_frame)​
 ReportRegisters(registers_frame, pc);​

 MaybePrintAndroidHelpUrl();​
 ReportErrorSummary(bug_type, stack);​
}​
} // namespace​
// compiler-rt/lib/hwasan/hwasan_report.cpp​
void BaseReport::PrintAddressDescription() const {​
 Decorator d;​
 int num_descriptions_printed = 0;​

 // 输出访问内存块的基本信息​
 // Print some very basic information about the address, if it's a heap.​
 if (heap.begin) {​
 Printf(​
 "%s[%p,%p) is a %s %s heap chunk; "​
 "size: %zd offset: %zd\n%s",​
 d.Location(), heap.begin, heap.begin + heap.size,​
 heap.from_small_heap ? "small" : "large",​
 heap.is_allocated ? "allocated" : "unallocated", heap.size,​
 untagged_addr - heap.begin, d.Default());​
 }​

 auto announce_by_id = [](u32 thread_id) {​
 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) {​
 if (thread_id == t->unique_id())​
 t->Announce();​
 });​
 };​

 // 输出stack错误。allocations的赋值点就很重要。​
 // Check stack first. If the address is on the stack of a live thread, we​
 // know it cannot be a heap / global overflow.​
 for (const auto &sa : allocations.stack) {​
 Printf("%s", d.Error());​
 Printf("\nCause: stack tag-mismatch\n");​
 Printf("%s", d.Location());​
 Printf("Address %p is located in stack of thread T%zd\n", untagged_addr,​
 sa.thread_id());​
 Printf("%s", d.Default());​
 announce_by_id(sa.thread_id());​
 PrintStackAllocations(sa.get(), ptr_tag, untagged_addr);​
 num_descriptions_printed++;​
 }​

 // 满足全部以下条件就是heap-buffer-overflow or global-overflow:​
 1. allocations.stack为空,说明内存不是栈内存​
 2. 地址不为0​
 3. candidate.is_close。​
 if (allocations.stack.empty() && candidate.untagged_addr &&​
 candidate.is_close) {​
 PrintHeapOrGlobalCandidate();​
 num_descriptions_printed++;​
 }​

 // UAF。上面的if没return,说明overflow和UAF可能同时出现?感觉不合理,实际也没见过。​
 for (const auto &ha : allocations.heap) {​
 const HeapAllocationRecord har = ha.har;​

 Printf("%s", d.Error());​
 Printf("\nCause: use-after-free\n");​
 Printf("%s", d.Location());​
 Printf("%p is located %zd bytes inside a %zd-byte region [%p,%p)\n",​
 untagged_addr, untagged_addr - UntagAddr(har.tagged_addr),​
 har.requested_size, UntagAddr(har.tagged_addr),​
 UntagAddr(har.tagged_addr) + har.requested_size);​
 Printf("%s", d.Allocation());​
 Printf("freed by thread T%u here:\n", ha.free_thread_id);​
 Printf("%s", d.Default());​
 GetStackTraceFromId(har.free_context_id).Print();​

 Printf("%s", d.Allocation());​
 Printf("previously allocated by thread T%u here:\n", har.alloc_thread_id);​
 Printf("%s", d.Default());​
 GetStackTraceFromId(har.alloc_context_id).Print();​

 // Print a developer note: the index of this heap object​
 // in the thread's deallocation ring buffer.​
 Printf("hwasan_dev_note_heap_rb_distance: %zd %zd\n", ha.ring_index + 1,​
 flags()->heap_history_size);​
 Printf("hwasan_dev_note_num_matching_addrs: %zd\n", ha.num_matching_addrs);​
 Printf("hwasan_dev_note_num_matching_addrs_4b: %zd\n",​
 ha.num_matching_addrs_4b);​

 announce_by_id(ha.free_thread_id);​
 // TODO: announce_by_id(har.alloc_thread_id);​
 num_descriptions_printed++;​
 }​

 // 这里注意,如果前面两种都没输出,那么还是按照overflow处理​
 if (candidate.untagged_addr && num_descriptions_printed == 0) {​
 PrintHeapOrGlobalCandidate();​
 num_descriptions_printed++;​
 }​

 // Print the remaining threads, as an extra information, 1 line per thread.​
 if (flags()->print_live_threads_info) {​
 Printf("\n");​
 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) { t->Announce(); });​
 }​

 if (!num_descriptions_printed)​
 // We exhausted our possibilities. Bail out.​
 Printf("HWAddressSanitizer can not describe address in more detail.\n");​
 if (num_descriptions_printed > 1) {​
 Printf(​
 "There are %d potential causes, printed above in order "​
 "of likeliness.\n",​
 num_descriptions_printed);​
 }​
}​

PrintHeapOrGlobalCandidate:实现:

// compiler-rt/lib/hwasan/hwasan_report.cpp​
void BaseReport::PrintAddressDescription() const {​
 Decorator d;​
 int num_descriptions_printed = 0;​
​
 // 输出访问内存块的基本信息​
 // Print some very basic information about the address, if it's a heap.​
 if (heap.begin) {​
 Printf(​
 "%s[%p,%p) is a %s %s heap chunk; "​
 "size: %zd offset: %zd\n%s",​
 d.Location(), heap.begin, heap.begin + heap.size,​
 heap.from_small_heap ? "small" : "large",​
 heap.is_allocated ? "allocated" : "unallocated", heap.size,​
 untagged_addr - heap.begin, d.Default());​
 }​
​
 auto announce_by_id = [](u32 thread_id) {​
 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) {​
 if (thread_id == t->unique_id())​
 t->Announce();​
 });​
 };​
​
 // 输出stack错误。allocations的赋值点就很重要。​
 // Check stack first. If the address is on the stack of a live thread, we​
 // know it cannot be a heap / global overflow.​
 for (const auto &sa : allocations.stack) {​
 Printf("%s", d.Error());​
 Printf("\nCause: stack tag-mismatch\n");​
 Printf("%s", d.Location());​
 Printf("Address %p is located in stack of thread T%zd\n", untagged_addr,​
 sa.thread_id());​
 Printf("%s", d.Default());​
 announce_by_id(sa.thread_id());​
 PrintStackAllocations(sa.get(), ptr_tag, untagged_addr);​
 num_descriptions_printed++;​
 }​
​
 // 满足全部以下条件就是heap-buffer-overflow or global-overflow:​
 1. allocations.stack为空,说明内存不是栈内存​
 2. 地址不为0​
 3. candidate.is_close。​
 if (allocations.stack.empty() && candidate.untagged_addr &&​
 candidate.is_close) {​
 PrintHeapOrGlobalCandidate();​
 num_descriptions_printed++;​
 }​
​
 // UAF。上面的if没return,说明overflow和UAF可能同时出现?感觉不合理,实际也没见过。​
 for (const auto &ha : allocations.heap) {​
 const HeapAllocationRecord har = ha.har;​
​
 Printf("%s", d.Error());​
 Printf("\nCause: use-after-free\n");​
 Printf("%s", d.Location());​
 Printf("%p is located %zd bytes inside a %zd-byte region [%p,%p)\n",​
 untagged_addr, untagged_addr - UntagAddr(har.tagged_addr),​
 har.requested_size, UntagAddr(har.tagged_addr),​
 UntagAddr(har.tagged_addr) + har.requested_size);​
 Printf("%s", d.Allocation());​
 Printf("freed by thread T%u here:\n", ha.free_thread_id);​
 Printf("%s", d.Default());​
 GetStackTraceFromId(har.free_context_id).Print();​
​
 Printf("%s", d.Allocation());​
 Printf("previously allocated by thread T%u here:\n", har.alloc_thread_id);​
 Printf("%s", d.Default());​
 GetStackTraceFromId(har.alloc_context_id).Print();​
​
 // Print a developer note: the index of this heap object​
 // in the thread's deallocation ring buffer.​
 Printf("hwasan_dev_note_heap_rb_distance: %zd %zd\n", ha.ring_index + 1,​
 flags()->heap_history_size);​
 Printf("hwasan_dev_note_num_matching_addrs: %zd\n", ha.num_matching_addrs);​
 Printf("hwasan_dev_note_num_matching_addrs_4b: %zd\n",​
 ha.num_matching_addrs_4b);​
​
 announce_by_id(ha.free_thread_id);​
 // TODO: announce_by_id(har.alloc_thread_id);​
 num_descriptions_printed++;​
 }​
​
 // 这里注意,如果前面两种都没输出,那么还是按照overflow处理​
 if (candidate.untagged_addr && num_descriptions_printed == 0) {​
 PrintHeapOrGlobalCandidate();​
 num_descriptions_printed++;​
 }​
​
 // Print the remaining threads, as an extra information, 1 line per thread.​
 if (flags()->print_live_threads_info) {​
 Printf("\n");​
 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) { t->Announce(); });​
 }​
​
 if (!num_descriptions_printed)​
 // We exhausted our possibilities. Bail out.​
 Printf("HWAddressSanitizer can not describe address in more detail.\n");​
 if (num_descriptions_printed > 1) {​
 Printf(​
 "There are %d potential causes, printed above in order "​
 "of likeliness.\n",​
 num_descriptions_printed);​
 }​
}​
​
​
PrintHeapOrGlobalCandidate实现:​
​
​
void BaseReport::PrintHeapOrGlobalCandidate() const {​
 Decorator d;​
 if (candidate.heap.is_allocated) {​
 // 内存是堆上分配的,所以认为是heap buffer overflow​
 uptr offset;​
 const char *whence;​
 if (candidate.heap.begin <= untagged_addr &&​
 untagged_addr < candidate.heap.end) {​
 offset = untagged_addr - candidate.heap.begin;​
 whence = "inside";​
 } else if (candidate.after) {​
 offset = untagged_addr - candidate.heap.end;​
 whence = "after";​
 } else {​
 offset = candidate.heap.begin - untagged_addr;​
 whence = "before";​
 }​
 Printf("%s", d.Error());​
 Printf("\nCause: heap-buffer-overflow\n");​
 Printf("%s", d.Default());​
 Printf("%s", d.Location());​
 Printf("%p is located %zd bytes %s a %zd-byte region [%p,%p)\n",​
 untagged_addr, offset, whence,​
 candidate.heap.end - candidate.heap.begin, candidate.heap.begin,​
 candidate.heap.end);​
 Printf("%s", d.Allocation());​
 Printf("allocated by thread T%u here:\n", candidate.heap.thread_id);​
 Printf("%s", d.Default());​
 GetStackTraceFromId(candidate.heap.stack_id).Print();​
 return;​
 }​
 // Check whether the address points into a loaded library. If so, this is​
 // most likely a global variable.​
 const char *module_name;​
 uptr module_address;​
 Symbolizer *sym = Symbolizer::GetOrInit();​
 if (sym->GetModuleNameAndOffsetForPC(candidate.untagged_addr, &module_name,​
 &module_address)) {​
 Printf("%s", d.Error());​
 Printf("\nCause: global-overflow\n");​
 Printf("%s", d.Default());​
 DataInfo info;​
 Printf("%s", d.Location());​
 if (sym->SymbolizeData(candidate.untagged_addr, &info) && info.start) {​
 Printf(​
 "%p is located %zd bytes %s a %zd-byte global variable "​
 "%s [%p,%p) in %s\n",​
 untagged_addr,​
 candidate.after ? untagged_addr - (info.start + info.size)​
 : info.start - untagged_addr,​
 candidate.after ? "after" : "before", info.size, info.name,​
 info.start, info.start + info.size, module_name);​
 } else {​
 uptr size = GetGlobalSizeFromDescriptor(candidate.untagged_addr);​
 if (size == 0)​
 // We couldn't find the size of the global from the descriptors.​
 Printf(​
 "%p is located %s a global variable in "​
 "\n #0 0x%x (%s+0x%x)\n",​
 untagged_addr, candidate.after ? "after" : "before",​
 candidate.untagged_addr, module_name, module_address);​
 else​
 Printf(​
 "%p is located %s a %zd-byte global variable in "​
 "\n #0 0x%x (%s+0x%x)\n",​
 untagged_addr, candidate.after ? "after" : "before", size,​
 candidate.untagged_addr, module_name, module_address);​
 }​
 Printf("%s", d.Default());​
 }​
}​

小结

UAF和overflow的判断逻辑

根据代码做进一步的提炼,总体逻辑如下: