Skip to content

[ASan][Darwin][GCD] Add interceptor for dispatch_apply #149238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

thetruestblue
Copy link
Contributor

@thetruestblue thetruestblue commented Jul 17, 2025

ASan had a gap in coverage for wqthreads blocks submitted by dispatch_apply

This adds interceptor for dispatch_apply and dispatch_apply_f and adds a test that a failure in a dispatch apply block contains thread and stack info.

rdar://139660648

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
@llvmbot
Copy link
Member

llvmbot commented Jul 17, 2025

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: None (thetruestblue)

Changes

ASan had a gap in coverage for wqthreads blocks 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


Full diff: https://github.com/llvm/llvm-project/pull/149238.diff

5 Files Affected:

  • (modified) compiler-rt/lib/asan/asan_mac.cpp (+18-1)
  • (modified) compiler-rt/lib/asan/tests/asan_mac_test.cpp (+6)
  • (modified) compiler-rt/lib/asan/tests/asan_mac_test.h (+1)
  • (modified) compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm (+10)
  • (added) compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c (+27)
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 <dispatch/dispatch.h>
+#include <stdlib.h>
+
+__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

@thetruestblue
Copy link
Contributor Author

@DanBlackwell @padriff requesting review as well.

@thetruestblue thetruestblue force-pushed the blueg/dispatch-apply-interceptor branch from 92a25c0 to 5d72c09 Compare July 17, 2025 16:24
Copy link

github-actions bot commented Jul 17, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions c,cpp,h -- compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c compiler-rt/lib/asan/asan_mac.cpp compiler-rt/lib/asan/tests/asan_mac_test.cpp compiler-rt/lib/asan/tests/asan_mac_test.h
View the diff from clang-format here.
diff --git a/compiler-rt/lib/asan/asan_mac.cpp b/compiler-rt/lib/asan/asan_mac.cpp
index 2ac23eec3..d0e9fef78 100644
--- a/compiler-rt/lib/asan/asan_mac.cpp
+++ b/compiler-rt/lib/asan/asan_mac.cpp
@@ -258,8 +258,8 @@ INTERCEPTOR(void, dispatch_apply_f, size_t iterations, dispatch_queue_t queue,
   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_block);
 }
 
 #  if !defined(MISSING_BLOCKS_SUPPORT)
@@ -281,10 +281,10 @@ void dispatch_apply(size_t iterations, dispatch_queue_t queue,
 #define GET_ASAN_BLOCK(work) \
   void (^asan_block)(void);  \
   int parent_tid = GetCurrentTidOrInvalid(); \
-  asan_block = ^(void) { \
-    GET_STACK_TRACE_THREAD; \
-    asan_register_worker_thread(parent_tid, &stack); \
-    work(); \
+  asan_block = ^(void) {                   \
+        GET_STACK_TRACE_THREAD;                          \
+        asan_register_worker_thread(parent_tid, &stack); \
+        work(); \
   }
 
 INTERCEPTOR(void, dispatch_async,

@thetruestblue thetruestblue force-pushed the blueg/dispatch-apply-interceptor branch from 5d72c09 to 181b63f Compare July 17, 2025 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants