[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