Index: compiler-rt/include/sanitizer/common_interface_defs.h =================================================================== --- compiler-rt/include/sanitizer/common_interface_defs.h +++ compiler-rt/include/sanitizer/common_interface_defs.h @@ -341,6 +341,30 @@ const void **bottom_old, size_t *size_old); +/// Calls the user-provided callback for each Fake stack region for the +/// specified thread ID. +/// +/// \param os_tid Thread ID. Only the Fake stack of this thread ID is considered. +/// \param callback User-provided callback. For each stack region, +/// callback is called with begin and +/// end marking the stack span and arg +/// equal to the arg parameter value passed. +/// \param arg This value is passed as last parameter to callback. +void __sanitizer_for_each_extra_stack_range( + uint64_t os_tid, void (*callback)(size_t begin, size_t end, void *arg), + void *arg); + +/// Calls the user-provided callback for each Fake stack region for +/// threads that have a Fake stack. +/// +/// \param callback User-provided callback. For each stack region, +/// callback is called with begin and +/// end marking the stack span and arg +/// equal to the user-provided arg value. +/// \param arg This value is passed as last parameter to callback. +void __sanitizer_for_each_extra_stack_range_all_threads( + void (*callback)(size_t begin, size_t end, void *arg), void *arg); + // Get full module name and calculate pc offset within it. // Returns 1 if pc belongs to some module, 0 if module was not found. int __sanitizer_get_module_and_offset_for_pc(void *pc, char *module_path, Index: compiler-rt/lib/asan/asan_interface.inc =================================================================== --- compiler-rt/lib/asan/asan_interface.inc +++ compiler-rt/lib/asan/asan_interface.inc @@ -154,6 +154,8 @@ INTERFACE_FUNCTION(__asan_unregister_image_globals) INTERFACE_FUNCTION(__asan_version_mismatch_check_v8) INTERFACE_FUNCTION(__sanitizer_finish_switch_fiber) +INTERFACE_FUNCTION(__sanitizer_for_each_extra_stack_range) +INTERFACE_FUNCTION(__sanitizer_for_each_extra_stack_range_all_threads) INTERFACE_FUNCTION(__sanitizer_print_stack_trace) INTERFACE_FUNCTION(__sanitizer_ptr_cmp) INTERFACE_FUNCTION(__sanitizer_ptr_sub) Index: compiler-rt/lib/asan/asan_thread.cpp =================================================================== --- compiler-rt/lib/asan/asan_thread.cpp +++ compiler-rt/lib/asan/asan_thread.cpp @@ -533,4 +533,40 @@ (uptr*)bottom_old, (uptr*)size_old); } + +SANITIZER_INTERFACE_ATTRIBUTE +void __sanitizer_for_each_extra_stack_range( + u64 os_id, void (*callback)(uptr begin, uptr end, void *arg), void *arg) { + AsanThread *t = GetAsanThreadByOsIDLocked(os_id); + if (t && t->has_fake_stack()) + t->fake_stack()->ForEachFakeFrame(callback, arg); +} + +struct RichRangeIteratorCallback { + RangeIteratorCallback callback; + void *arg; +}; + +static void +CallRichRangeCallback(ThreadContextBase *tctx_base, void *arg) { + auto *cb = reinterpret_cast(arg); + auto *tctx = static_cast(tctx_base); + AsanThread *t = tctx->thread; + if (t && t->has_fake_stack()) + t->fake_stack()->ForEachFakeFrame(cb->callback, cb->arg); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __sanitizer_for_each_extra_stack_range_all_threads( + void (*callback)(uptr begin, uptr end, void *arg), void *arg) { + if (!__asan_option_detect_stack_use_after_return) + return; + + RichRangeIteratorCallback cb = {callback, arg}; + { + ThreadRegistryLock l(&asanThreadRegistry()); + asanThreadRegistry().RunCallbackForEachThreadLocked(CallRichRangeCallback, + &cb); + } } +} // extern "C" Index: compiler-rt/test/asan/TestCases/iterate-fakestacks.cpp =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/iterate-fakestacks.cpp @@ -0,0 +1,55 @@ +// RUN: %clangxx_asan -O0 %s -o %t +// RUN: %env_asan_opts=detect_stack_use_after_return=1 %run %t 2>&1 | FileCheck %s + +#include + +#include + +// The FakeStack region also contains the redzones on both sides of the local variable(s). +// This means that reading from the region will read from the redzones too, which would +// trigger ASan stack-buffer-overread errors. Disabling instrumentation of this function +// bypasses the poison check / solves the errors. The purpose of this testcase is not to test +// whether ASan triggers on bad reads, but instead to test whether fakestack iteration +// callback is called with the correct address range that includes local variables. +__attribute__((no_sanitize("address"))) +void callback(size_t begin, size_t end, void *arg) { + printf("%s:", arg); + for (auto p = begin; p < end; p++) { + char *c = (char*)p; + if (*c >= 32) // Filter out ASCII control codes + printf("%c", *c); + } + printf("\n"); +} + +__attribute__((noinline)) void frame3() { + char localvars[6] = "Three"; + const char *arg = "from frame three"; + printf("FRAME3\n"); + __sanitizer_for_each_extra_stack_range_all_threads(&callback, (void*)arg); +} + +__attribute__((noinline)) void frame2() { + char localvars[50] = "A$@N"; + frame3(); +} + +int main(int argc, char **argv) { + char localvars[8] = "L1V|\\/|"; + frame2(); + + const char *arg = "from main"; + printf("MAIN\n"); + __sanitizer_for_each_extra_stack_range_all_threads(&callback, (void*)arg); + printf("DONE\n"); + return 0; +} + +// CHECK: FRAME3 +// There is no guarantee on the iteration order of the frames +// CHECK-DAG: from frame three:{{.*}}Three +// CHECK-DAG: from frame three:{{.*}}A$@N +// CHECK-DAG: from frame three:{{.*}}L1V|\/| +// CHECK: MAIN +// CHECK-NEXT: from main:{{.*}}L1V|\/| +// CHECK-NEXT: DONE