[compiler-rt] [tsan] fcntl interceptor with F_DUPFD and F_DUPFD_CLOEXEC support (PR #221064)

Vitaly Buka via llvm-commits llvm-commits at lists.llvm.org
Sun Oct 4 22:49:28 PDT 2026


https://github.com/vitalybuka updated https://github.com/llvm/llvm-project/pull/221064

>From c308f9fc8a3f06effbd246e1bb57a78cb61f9238 Mon Sep 17 00:00:00 2001
From: Mike Aizatsky <maizatskyi at cloudflare.com>
Date: Thu, 3 Sep 2026 14:56:07 -0700
Subject: [PATCH] [tsan] fcntl with F_DUPFD_CLOEXEC support

fixes https://github.com/rust-lang/rust/issues/130037
and bunch of others
---
 .../lib/tsan/rtl/tsan_interceptors_posix.cpp  | 61 +++++++++++++++----
 .../Linux/fd_fcntl_dupfd_cloexec_norace.cpp   | 46 ++++++++++++++
 2 files changed, 95 insertions(+), 12 deletions(-)
 create mode 100644 compiler-rt/test/tsan/Linux/fd_fcntl_dupfd_cloexec_norace.cpp

diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
index e00853bd1b426..48a51390c8046 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
@@ -1826,29 +1826,64 @@ TSAN_INTERCEPTOR(int, creat64, const char *name, int mode) {
 #define TSAN_MAYBE_INTERCEPT_CREAT64
 #endif
 
-TSAN_INTERCEPTOR(int, dup, int oldfd) {
-  SCOPED_TSAN_INTERCEPTOR(dup, oldfd);
-  int newfd = REAL(dup)(oldfd);
+static int HandleDupResult(ThreadState* thr, uptr pc, int oldfd, int newfd,
+                           bool write) {
   if (oldfd >= 0 && newfd >= 0 && newfd != oldfd)
-    FdDup(thr, pc, oldfd, newfd, true);
+    FdDup(thr, pc, oldfd, newfd, write);
   return newfd;
 }
 
+#if SANITIZER_LINUX
+// These values are part of the Linux UAPI and are the same on all Linux
+// architectures.
+static const int kF_DUPFD = 0;
+static const int kF_DUPFD_CLOEXEC = 1030;
+
+// fcntl accepts either an int or a pointer as its optional argument.  Reading
+// it as uptr follows libc's implementation strategy and preserves either value
+// on the ABIs supported by TSan.
+#  define TSAN_FCNTL_INTERCEPTOR(func)                       \
+    TSAN_INTERCEPTOR(int, func, int oldfd, int cmd, ...) {   \
+      va_list ap;                                            \
+      va_start(ap, cmd);                                     \
+      uptr arg = va_arg(ap, uptr);                           \
+      va_end(ap);                                            \
+      SCOPED_TSAN_INTERCEPTOR(func, oldfd, cmd, arg);        \
+      int newfd = REAL(func)(oldfd, cmd, arg);               \
+      if (cmd == kF_DUPFD || cmd == kF_DUPFD_CLOEXEC)        \
+        return HandleDupResult(thr, pc, oldfd, newfd, true); \
+      return newfd;                                          \
+    }
+
+TSAN_FCNTL_INTERCEPTOR(fcntl)
+#  if SANITIZER_INTERCEPT_FCNTL64
+TSAN_FCNTL_INTERCEPTOR(fcntl64)
+#    define TSAN_MAYBE_INTERCEPT_FCNTL64 TSAN_INTERCEPT(fcntl64)
+#  else
+#    define TSAN_MAYBE_INTERCEPT_FCNTL64
+#  endif
+#  undef TSAN_FCNTL_INTERCEPTOR
+#  define TSAN_MAYBE_INTERCEPT_FCNTL TSAN_INTERCEPT(fcntl)
+#else
+#  define TSAN_MAYBE_INTERCEPT_FCNTL
+#  define TSAN_MAYBE_INTERCEPT_FCNTL64
+#endif
+
+TSAN_INTERCEPTOR(int, dup, int oldfd) {
+  SCOPED_TSAN_INTERCEPTOR(dup, oldfd);
+  return HandleDupResult(thr, pc, oldfd, REAL(dup)(oldfd), true);
+}
+
 TSAN_INTERCEPTOR(int, dup2, int oldfd, int newfd) {
   SCOPED_TSAN_INTERCEPTOR(dup2, oldfd, newfd);
-  int newfd2 = REAL(dup2)(oldfd, newfd);
-  if (oldfd >= 0 && newfd2 >= 0 && newfd2 != oldfd)
-    FdDup(thr, pc, oldfd, newfd2, false);
-  return newfd2;
+  return HandleDupResult(thr, pc, oldfd, REAL(dup2)(oldfd, newfd), false);
 }
 
 #if !SANITIZER_APPLE
 TSAN_INTERCEPTOR(int, dup3, int oldfd, int newfd, int flags) {
   SCOPED_TSAN_INTERCEPTOR(dup3, oldfd, newfd, flags);
-  int newfd2 = REAL(dup3)(oldfd, newfd, flags);
-  if (oldfd >= 0 && newfd2 >= 0 && newfd2 != oldfd)
-    FdDup(thr, pc, oldfd, newfd2, false);
-  return newfd2;
+  return HandleDupResult(thr, pc, oldfd, REAL(dup3)(oldfd, newfd, flags),
+                         false);
 }
 #endif
 
@@ -3146,6 +3181,8 @@ void InitializeInterceptors() {
   TSAN_MAYBE_INTERCEPT_OPEN64;
   TSAN_INTERCEPT(creat);
   TSAN_MAYBE_INTERCEPT_CREAT64;
+  TSAN_MAYBE_INTERCEPT_FCNTL;
+  TSAN_MAYBE_INTERCEPT_FCNTL64;
   TSAN_INTERCEPT(dup);
   TSAN_INTERCEPT(dup2);
   TSAN_INTERCEPT(dup3);
diff --git a/compiler-rt/test/tsan/Linux/fd_fcntl_dupfd_cloexec_norace.cpp b/compiler-rt/test/tsan/Linux/fd_fcntl_dupfd_cloexec_norace.cpp
new file mode 100644
index 0000000000000..2bd9928934e01
--- /dev/null
+++ b/compiler-rt/test/tsan/Linux/fd_fcntl_dupfd_cloexec_norace.cpp
@@ -0,0 +1,46 @@
+// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int value;
+int duplicate_write_fd;
+
+void *writer(void *) {
+  value = 1;
+  if (write(duplicate_write_fd, ".", 1) != 1)
+    abort();
+  return nullptr;
+}
+
+int main() {
+  int pipe_fds[2];
+  if (pipe2(pipe_fds, O_CLOEXEC) != 0)
+    abort();
+
+  duplicate_write_fd = fcntl(pipe_fds[1], F_DUPFD_CLOEXEC, 0);
+  if (duplicate_write_fd < 0)
+    abort();
+
+  pthread_t thread;
+  pthread_create(&thread, nullptr, writer, nullptr);
+
+  char byte;
+  if (read(pipe_fds[0], &byte, 1) != 1)
+    abort();
+  if (value != 1)
+    abort();
+
+  pthread_join(thread, nullptr);
+  close(duplicate_write_fd);
+  close(pipe_fds[0]);
+  close(pipe_fds[1]);
+  fprintf(stderr, "DONE\n");
+  return 0;
+}
+
+// CHECK-NOT: WARNING: ThreadSanitizer: data race
+// CHECK: DONE



More information about the llvm-commits mailing list