From 267f96a25db872d26d90f5317c7444e855cfc9d4 Mon Sep 17 00:00:00 2001 From: thetruestblue Date: Wed, 16 Jul 2025 14:21:57 -0700 Subject: [PATCH 1/3] [ASan][Darwin][GCD] Add interceptor for dispatch_apply ASan had a gap in covarage for wqthreads submitted by dispatch_apply This adds interceptor for dispatch_apply and adds a test that a failure in a dispatch apply block contains thread and stack info. rdar://139660648 --- compiler-rt/lib/asan/asan_mac.cpp | 19 ++++++++++++- compiler-rt/lib/asan/tests/asan_mac_test.cpp | 6 +++++ compiler-rt/lib/asan/tests/asan_mac_test.h | 1 + .../lib/asan/tests/asan_mac_test_helpers.mm | 10 +++++++ .../Darwin/dispatch_apply_threadno.c | 27 +++++++++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c diff --git a/compiler-rt/lib/asan/asan_mac.cpp b/compiler-rt/lib/asan/asan_mac.cpp index be513a03ed5cd..30c81ec64f024 100644 --- a/compiler-rt/lib/asan/asan_mac.cpp +++ b/compiler-rt/lib/asan/asan_mac.cpp @@ -103,6 +103,7 @@ void FlushUnneededASanShadowMemory(uptr p, uptr size) { // dispatch_after() // dispatch_group_async_f() // dispatch_group_async() +// dispatch_apply() // TODO(glider): libdispatch API contains other functions that we don't support // yet. // @@ -255,6 +256,8 @@ void dispatch_source_set_cancel_handler(dispatch_source_t ds, void dispatch_source_set_event_handler(dispatch_source_t ds, void(^work)(void)); dispatch_mach_t dispatch_mach_create(const char *label, dispatch_queue_t queue, dispatch_mach_handler_t handler); +void dispatch_apply(size_t iterations, dispatch_queue_t queue, + void (^block)(size_t iteration)); } #define GET_ASAN_BLOCK(work) \ @@ -332,6 +335,20 @@ INTERCEPTOR(void *, dispatch_mach_create_f, const char *label, }); } -#endif +INTERCEPTOR(void, dispatch_apply, size_t iterations, dispatch_queue_t queue, + void (^block)(size_t iteration)) { + ENABLE_FRAME_POINTER; + int parent_tid = GetCurrentTidOrInvalid(); + + void (^asan_block)(size_t) = ^(size_t iteration) { + GET_STACK_TRACE_THREAD; + asan_register_worker_thread(parent_tid, &stack); + block(iteration); + }; + + REAL(dispatch_apply)(iterations, queue, asan_block); +} + +# endif #endif // SANITIZER_APPLE diff --git a/compiler-rt/lib/asan/tests/asan_mac_test.cpp b/compiler-rt/lib/asan/tests/asan_mac_test.cpp index bd36089991deb..4b21f12f81eac 100644 --- a/compiler-rt/lib/asan/tests/asan_mac_test.cpp +++ b/compiler-rt/lib/asan/tests/asan_mac_test.cpp @@ -116,6 +116,12 @@ TEST(AddressSanitizerMac, GCDDispatchAfter) { EXPECT_DEATH(TestGCDDispatchAfter(), "Shadow byte legend"); } +TEST(AddressSanitizerMac, GCDDispatchApply) { + // Make sure the whole ASan report is printed, i.e. that we don't die + // on a CHECK. + EXPECT_DEATH(TestGCDDispatchApply(), "Shadow byte legend"); +} + TEST(AddressSanitizerMac, GCDSourceEvent) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. diff --git a/compiler-rt/lib/asan/tests/asan_mac_test.h b/compiler-rt/lib/asan/tests/asan_mac_test.h index 441547a5a3dcb..ec71546a3989b 100644 --- a/compiler-rt/lib/asan/tests/asan_mac_test.h +++ b/compiler-rt/lib/asan/tests/asan_mac_test.h @@ -9,6 +9,7 @@ extern "C" { void TestGCDReuseWqthreadsAsync(); void TestGCDReuseWqthreadsSync(); void TestGCDDispatchAfter(); + void TestGCDDispatchApply(); void TestGCDInTSDDestructor(); void TestGCDSourceEvent(); void TestGCDSourceCancel(); diff --git a/compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm b/compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm index 3f8fa26d95b8d..ddb50f894639d 100644 --- a/compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm +++ b/compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm @@ -148,6 +148,16 @@ void TestGCDDispatchAfter() { wait_forever(); } +void TestGCDDispatchApply() { + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + __block char *buffer = (char *)malloc(4); + dispatch_apply(8, queue, ^(size_t i) { + access_memory(&buffer[i]); + }); + + free(buffer); // not reached +} + void worker_do_deallocate(void *ptr) { free(ptr); } diff --git a/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c new file mode 100644 index 0000000000000..8dfd0942bf656 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c @@ -0,0 +1,27 @@ +// Bugs caught within missing GCD dispatch blocks result in thread being reported as T-1 +// with an empty stack. +// This tests that dispatch_apply blocks can capture valid thread number and stack. + +// RUN: %clang_asan %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s + +#include +#include + +__attribute__((noinline)) void access_memory_frame(char *x) { *x = 0; } + +__attribute__((noinline)) void test_dispatch_apply() { + char *x = (char *)malloc(4); + dispatch_apply(8, dispatch_get_global_queue(0, 0), ^(size_t i) { + access_memory_frame(&x[i]); + }); +} + +int main(int argc, const char *argv[]) { + test_dispatch_apply(); + return 0; +} + +// CHECK: ERROR: AddressSanitizer: heap-buffer-overflow +// CHECK: #0 0x{{.*}} in {{.*}}access_memory_frame +// CHECK-NOT: T-1 \ No newline at end of file From 181b63fd97b6d5e770101f17be10ada12cd6c234 Mon Sep 17 00:00:00 2001 From: thetruestblue Date: Thu, 17 Jul 2025 09:22:15 -0700 Subject: [PATCH 2/3] Add dispatch_apply_f interceptor --- compiler-rt/lib/asan/asan_mac.cpp | 20 ++++++++++++++++- .../Darwin/dispatch_apply_threadno.c | 22 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/compiler-rt/lib/asan/asan_mac.cpp b/compiler-rt/lib/asan/asan_mac.cpp index 30c81ec64f024..2ac23eec33db6 100644 --- a/compiler-rt/lib/asan/asan_mac.cpp +++ b/compiler-rt/lib/asan/asan_mac.cpp @@ -104,6 +104,7 @@ void FlushUnneededASanShadowMemory(uptr p, uptr size) { // dispatch_group_async_f() // dispatch_group_async() // dispatch_apply() +// dispatch_apply_f() // TODO(glider): libdispatch API contains other functions that we don't support // yet. // @@ -244,7 +245,24 @@ INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group, asan_dispatch_call_block_and_release); } -#if !defined(MISSING_BLOCKS_SUPPORT) +extern "C" void asan_dispatch_apply_f_block(void *context, size_t iteration) { + GET_STACK_TRACE_THREAD; + asan_block_context_t *asan_ctxt = (asan_block_context_t *)context; + asan_register_worker_thread(asan_ctxt->parent_tid, &stack); + ((void (*)(void *, size_t))asan_ctxt->func)(asan_ctxt->block, iteration); +} + +INTERCEPTOR(void, dispatch_apply_f, size_t iterations, dispatch_queue_t queue, + void *ctxt, void (*work)(void *, size_t)) { + GET_STACK_TRACE_THREAD; + asan_block_context_t *asan_ctxt = + alloc_asan_context(ctxt, (dispatch_function_t)work, &stack); + + REAL(dispatch_apply_f) + (iterations, queue, (void *)asan_ctxt, asan_dispatch_apply_f_block); +} + +# if !defined(MISSING_BLOCKS_SUPPORT) extern "C" { void dispatch_async(dispatch_queue_t dq, void(^work)(void)); void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq, diff --git a/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c index 8dfd0942bf656..49c0bd40cc38e 100644 --- a/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c +++ b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c @@ -2,6 +2,9 @@ // with an empty stack. // This tests that dispatch_apply blocks can capture valid thread number and stack. +// RUN: %clang_asan -DDISPATCH_APPLY_F %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s + // RUN: %clang_asan %s -o %t // RUN: not %run %t 2>&1 | FileCheck %s @@ -17,8 +20,27 @@ __attribute__((noinline)) void test_dispatch_apply() { }); } +typedef struct { + char *data; +} Context; + +void da_func(void *ctx, size_t i) { + Context *c = (Context *)ctx; + access_memory_frame(&c->data[i]); +} + +__attribute__((noinline)) void test_dispatch_apply_f() { + Context *ctx = (Context *)malloc(sizeof(Context)); + ctx->data = (char *)malloc(4); + dispatch_apply_f(8, dispatch_get_global_queue(0, 0), ctx, da_func); +} + int main(int argc, const char *argv[]) { +#if DISPATCH_APPLY_F + test_dispatch_apply_f(); +#else test_dispatch_apply(); +#endif return 0; } From acf7469ff0494c4266e3342610a3fdf020151520 Mon Sep 17 00:00:00 2001 From: thetruestblue Date: Mon, 21 Jul 2025 13:46:44 -0700 Subject: [PATCH 3/3] Apply feedback -- don't recompile test file, change callback function name. --- compiler-rt/lib/asan/asan_mac.cpp | 11 ++++---- .../Darwin/dispatch_apply_threadno.c | 25 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/compiler-rt/lib/asan/asan_mac.cpp b/compiler-rt/lib/asan/asan_mac.cpp index 2ac23eec33db6..54ed7db032af9 100644 --- a/compiler-rt/lib/asan/asan_mac.cpp +++ b/compiler-rt/lib/asan/asan_mac.cpp @@ -245,7 +245,7 @@ INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group, asan_dispatch_call_block_and_release); } -extern "C" void asan_dispatch_apply_f_block(void *context, size_t iteration) { +extern "C" void asan_dispatch_apply_f_work(void *context, size_t iteration) { GET_STACK_TRACE_THREAD; asan_block_context_t *asan_ctxt = (asan_block_context_t *)context; asan_register_worker_thread(asan_ctxt->parent_tid, &stack); @@ -257,9 +257,8 @@ INTERCEPTOR(void, dispatch_apply_f, size_t iterations, dispatch_queue_t queue, GET_STACK_TRACE_THREAD; asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, (dispatch_function_t)work, &stack); - - REAL(dispatch_apply_f) - (iterations, queue, (void *)asan_ctxt, asan_dispatch_apply_f_block); + REAL(dispatch_apply_f)(iterations, queue, (void *)asan_ctxt, + asan_dispatch_apply_f_work); } # if !defined(MISSING_BLOCKS_SUPPORT) @@ -269,13 +268,13 @@ void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq, void(^work)(void)); void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, void(^work)(void)); +void dispatch_apply(size_t iterations, dispatch_queue_t queue, + void (^block)(size_t iteration)); void dispatch_source_set_cancel_handler(dispatch_source_t ds, void(^work)(void)); void dispatch_source_set_event_handler(dispatch_source_t ds, void(^work)(void)); dispatch_mach_t dispatch_mach_create(const char *label, dispatch_queue_t queue, dispatch_mach_handler_t handler); -void dispatch_apply(size_t iterations, dispatch_queue_t queue, - void (^block)(size_t iteration)); } #define GET_ASAN_BLOCK(work) \ diff --git a/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c index 49c0bd40cc38e..5e06615e8e9e9 100644 --- a/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c +++ b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c @@ -2,13 +2,12 @@ // with an empty stack. // This tests that dispatch_apply blocks can capture valid thread number and stack. -// RUN: %clang_asan -DDISPATCH_APPLY_F %s -o %t -// RUN: not %run %t 2>&1 | FileCheck %s - // RUN: %clang_asan %s -o %t -// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: not %run %t func 2>&1 | FileCheck %s --check-prefixes=CHECK-FUNC,CHECK +// RUN: not %run %t block 2>&1 | FileCheck %s --check-prefixes=CHECK-BLOCK,CHECK #include +#include #include __attribute__((noinline)) void access_memory_frame(char *x) { *x = 0; } @@ -36,14 +35,20 @@ __attribute__((noinline)) void test_dispatch_apply_f() { } int main(int argc, const char *argv[]) { -#if DISPATCH_APPLY_F - test_dispatch_apply_f(); -#else - test_dispatch_apply(); -#endif + if (strcmp(argv[1], "func") == 0) { + fprintf(stderr, "Test dispatch_apply with function\n"); + // CHECK-FUNC: dispatch_apply with function + test_dispatch_apply_f(); + } else if (strcmp(argv[1], "block") == 0) { + fprintf(stderr, "Test dispatch_apply with block\n"); + // CHECK-BLOCK: dispatch_apply with block + test_dispatch_apply(); + } else { + abort(); + } return 0; } // CHECK: ERROR: AddressSanitizer: heap-buffer-overflow // CHECK: #0 0x{{.*}} in {{.*}}access_memory_frame -// CHECK-NOT: T-1 \ No newline at end of file +// CHECK-NOT: T-1