[compiler-rt] [sanitizer] Intercept scandirat/scandirat64 (PR #163924)

via llvm-commits llvm-commits at lists.llvm.org
Fri Oct 17 01:16:01 PDT 2025


https://github.com/alxchk created https://github.com/llvm/llvm-project/pull/163924

Fixes https://github.com/llvm/llvm-project/issues/163923

>From 14b03ba1f174a301dfeeddf79144f8fddf81b0a3 Mon Sep 17 00:00:00 2001
From: Oleksii Shevchuk <alxchk at gmail.com>
Date: Thu, 16 Oct 2025 23:53:17 +0300
Subject: [PATCH] [sanitizer] Intercept scandirat/scandirat64

---
 .../lib/hwasan/hwasan_platform_interceptors.h |   6 +
 .../sanitizer_common_interceptors.inc         | 110 ++++++++++++++++++
 .../sanitizer_platform_interceptors.h         |   2 +
 compiler-rt/test/msan/scandirat.cpp           |  56 +++++++++
 compiler-rt/test/msan/scandirat_null.cpp      |  34 ++++++
 .../sanitizer_common/TestCases/scandirat.c    |  28 +++++
 6 files changed, 236 insertions(+)
 create mode 100644 compiler-rt/test/msan/scandirat.cpp
 create mode 100644 compiler-rt/test/msan/scandirat_null.cpp
 create mode 100644 compiler-rt/test/sanitizer_common/TestCases/scandirat.c

diff --git a/compiler-rt/lib/hwasan/hwasan_platform_interceptors.h b/compiler-rt/lib/hwasan/hwasan_platform_interceptors.h
index 8a653d83dec65..71bbad2d38f3b 100644
--- a/compiler-rt/lib/hwasan/hwasan_platform_interceptors.h
+++ b/compiler-rt/lib/hwasan/hwasan_platform_interceptors.h
@@ -389,9 +389,15 @@
 #undef SANITIZER_INTERCEPT_SCANDIR
 #define SANITIZER_INTERCEPT_SCANDIR 0
 
+#undef SANITIZER_INTERCEPT_SCANDIRAT
+#define SANITIZER_INTERCEPT_SCANDIRAT 0
+
 #undef SANITIZER_INTERCEPT_SCANDIR64
 #define SANITIZER_INTERCEPT_SCANDIR64 0
 
+#undef SANITIZER_INTERCEPT_SCANDIRAT64
+#define SANITIZER_INTERCEPT_SCANDIRAT64 0
+
 #undef SANITIZER_INTERCEPT_GETGROUPS
 #define SANITIZER_INTERCEPT_GETGROUPS 0
 
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
index b10ce7fa44afc..0d1455e124f1b 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
@@ -106,6 +106,7 @@
 #define readdir __readdir30
 #define readdir_r __readdir_r30
 #define scandir __scandir30
+#define scandirat __scandirat30
 #define setitimer __setitimer50
 #define setlocale __setlocale50
 #define shmctl __shmctl50
@@ -4227,6 +4228,59 @@ INTERCEPTOR(int, scandir, char *dirp, __sanitizer_dirent ***namelist,
 #define INIT_SCANDIR
 #endif
 
+#if SANITIZER_INTERCEPT_SCANDIRAT
+typedef int (*scandirat_filter_f)(const struct __sanitizer_dirent *);
+typedef int (*scandirat_compar_f)(const struct __sanitizer_dirent **,
+                                const struct __sanitizer_dirent **);
+
+static THREADLOCAL scandirat_filter_f scandirat_filter;
+static THREADLOCAL scandirat_compar_f scandirat_compar;
+
+static int wrapped_scandirat_filter(const struct __sanitizer_dirent *dir) {
+  COMMON_INTERCEPTOR_UNPOISON_PARAM(1);
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(dir, __sanitizer_dirsiz(dir));
+  return scandirat_filter(dir);
+}
+
+static int wrapped_scandirat_compar(const struct __sanitizer_dirent **a,
+                                  const struct __sanitizer_dirent **b) {
+  COMMON_INTERCEPTOR_UNPOISON_PARAM(2);
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(a, sizeof(*a));
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(*a, __sanitizer_dirsiz(*a));
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(b, sizeof(*b));
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(*b, __sanitizer_dirsiz(*b));
+  return scandirat_compar(a, b);
+}
+
+INTERCEPTOR(int, scandirat, int dirfd, char *dirp, __sanitizer_dirent ***namelist,
+            scandirat_filter_f filter, scandirat_compar_f compar) {
+  void *ctx;
+  COMMON_INTERCEPTOR_ENTER(ctx, scandirat, dirfd, dirp, namelist, filter, compar);
+  if (dirp) COMMON_INTERCEPTOR_READ_RANGE(ctx, dirp, internal_strlen(dirp) + 1);
+  scandirat_filter = filter;
+  scandirat_compar = compar;
+  // FIXME: under ASan the call below may write to freed memory and corrupt
+  // its metadata. See
+  // https://github.com/google/sanitizers/issues/321.
+  int res = REAL(scandirat)(dirfd, dirp, namelist,
+                          filter ? wrapped_scandirat_filter : nullptr,
+                          compar ? wrapped_scandirat_compar : nullptr);
+  scandirat_filter = nullptr;
+  scandirat_compar = nullptr;
+  if (namelist && res > 0) {
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, namelist, sizeof(*namelist));
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *namelist, sizeof(**namelist) * res);
+    for (int i = 0; i < res; ++i)
+      COMMON_INTERCEPTOR_WRITE_RANGE(ctx, (*namelist)[i],
+                                     __sanitizer_dirsiz((*namelist)[i]));
+  }
+  return res;
+}
+#define INIT_SCANDIRAT COMMON_INTERCEPT_FUNCTION(scandirat);
+#else
+#define INIT_SCANDIRAT
+#endif
+
 #if SANITIZER_INTERCEPT_SCANDIR64
 typedef int (*scandir64_filter_f)(const struct __sanitizer_dirent64 *);
 typedef int (*scandir64_compar_f)(const struct __sanitizer_dirent64 **,
@@ -4281,6 +4335,60 @@ INTERCEPTOR(int, scandir64, char *dirp, __sanitizer_dirent64 ***namelist,
 #define INIT_SCANDIR64
 #endif
 
+#if SANITIZER_INTERCEPT_SCANDIRAT64
+typedef int (*scandirat64_filter_f)(const struct __sanitizer_dirent64 *);
+typedef int (*scandirat64_compar_f)(const struct __sanitizer_dirent64 **,
+                                    const struct __sanitizer_dirent64 **);
+
+static THREADLOCAL scandirat64_filter_f scandirat64_filter;
+static THREADLOCAL scandirat64_compar_f scandirat64_compar;
+
+static int wrapped_scandirat64_filter(const struct __sanitizer_dirent64 *dir) {
+  COMMON_INTERCEPTOR_UNPOISON_PARAM(1);
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(dir, __sanitizer_dirsiz(dir));
+  return scandirat64_filter(dir);
+}
+
+static int wrapped_scandirat64_compar(const struct __sanitizer_dirent64 **a,
+                                      const struct __sanitizer_dirent64 **b) {
+  COMMON_INTERCEPTOR_UNPOISON_PARAM(2);
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(a, sizeof(*a));
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(*a, __sanitizer_dirsiz(*a));
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(b, sizeof(*b));
+  COMMON_INTERCEPTOR_INITIALIZE_RANGE(*b, __sanitizer_dirsiz(*b));
+  return scandirat64_compar(a, b);
+}
+
+INTERCEPTOR(int, scandirat64, int dirfd, char *dirp, __sanitizer_dirent64 ***namelist,
+            scandirat64_filter_f filter, scandirat64_compar_f compar) {
+  void *ctx;
+  COMMON_INTERCEPTOR_ENTER(ctx, scandirat64, dirfd, dirp, namelist, filter, compar);
+  if (dirp) COMMON_INTERCEPTOR_READ_RANGE(ctx, dirp, internal_strlen(dirp) + 1);
+  scandirat64_filter = filter;
+  scandirat64_compar = compar;
+  // FIXME: under ASan the call below may write to freed memory and corrupt
+  // its metadata. See
+  // https://github.com/google/sanitizers/issues/321.
+  int res =
+      REAL(scandirat64)(dirfd, dirp, namelist,
+                        filter ? wrapped_scandirat64_filter : nullptr,
+                        compar ? wrapped_scandirat64_compar : nullptr);
+  scandirat64_filter = nullptr;
+  scandirat64_compar = nullptr;
+  if (namelist && res > 0) {
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, namelist, sizeof(*namelist));
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *namelist, sizeof(**namelist) * res);
+    for (int i = 0; i < res; ++i)
+      COMMON_INTERCEPTOR_WRITE_RANGE(ctx, (*namelist)[i],
+                                     __sanitizer_dirsiz((*namelist)[i]));
+  }
+  return res;
+}
+#define INIT_SCANDIRAT64 COMMON_INTERCEPT_FUNCTION(scandirat64);
+#else
+#define INIT_SCANDIRAT64
+#endif
+
 #if SANITIZER_INTERCEPT_GETGROUPS
 INTERCEPTOR(int, getgroups, int size, u32 *lst) {
   void *ctx;
@@ -10526,7 +10634,9 @@ static void InitializeCommonInterceptors() {
   INIT_STRERROR_R;
   INIT_XPG_STRERROR_R;
   INIT_SCANDIR;
+  INIT_SCANDIRAT;
   INIT_SCANDIR64;
+  INIT_SCANDIRAT64;
   INIT_GETGROUPS;
   INIT_POLL;
   INIT_PPOLL;
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
index 88ecd7e16306a..dfe518609c673 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
@@ -356,7 +356,9 @@ SANITIZER_WEAK_IMPORT void *aligned_alloc(__sanitizer::usize __alignment,
 #define SANITIZER_INTERCEPT_XPG_STRERROR_R SI_LINUX_NOT_ANDROID
 #define SANITIZER_INTERCEPT_SCANDIR \
   (SI_FREEBSD || SI_NETBSD || SI_LINUX_NOT_ANDROID || SI_SOLARIS)
+#define SANITIZER_INTERCEPT_SCANDIRAT SI_GLIBC
 #define SANITIZER_INTERCEPT_SCANDIR64 SI_GLIBC || SI_SOLARIS32
+#define SANITIZER_INTERCEPT_SCANDIRAT64 SI_GLIBC
 #define SANITIZER_INTERCEPT_GETGROUPS SI_POSIX
 #define SANITIZER_INTERCEPT_POLL SI_POSIX
 #define SANITIZER_INTERCEPT_PPOLL SI_LINUX_NOT_ANDROID || SI_SOLARIS
diff --git a/compiler-rt/test/msan/scandirat.cpp b/compiler-rt/test/msan/scandirat.cpp
new file mode 100644
index 0000000000000..d538de1057b04
--- /dev/null
+++ b/compiler-rt/test/msan/scandirat.cpp
@@ -0,0 +1,56 @@
+// RUN: %clangxx_msan -O0 %s -o %t && %run %t %p
+// RUN: %clangxx_msan -O0 -D_FILE_OFFSET_BITS=64 %s -o %t && %run %t %p
+// RUN: %clangxx_msan -O3 %s -o %t && %run %t %p
+
+#include <assert.h>
+#include <glob.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <sanitizer/msan_interface.h>
+
+
+static int my_filter(const struct dirent *a) {
+  assert(__msan_test_shadow(&a, sizeof(a)) == (size_t)-1);
+  printf("%s\n", a->d_name);
+  __msan_print_shadow(a, a->d_reclen);
+  assert(__msan_test_shadow(a, a->d_reclen) == (size_t)-1);
+  printf("%s\n", a->d_name);
+  return strlen(a->d_name) == 3 && a->d_name[2] == 'b';
+}
+
+static int my_compar(const struct dirent **a, const struct dirent **b) {
+  assert(__msan_test_shadow(a, sizeof(*a)) == (size_t)-1);
+  assert(__msan_test_shadow(*a, (*a)->d_reclen) == (size_t)-1);
+  assert(__msan_test_shadow(b, sizeof(*b)) == (size_t)-1);
+  assert(__msan_test_shadow(*b, (*b)->d_reclen) == (size_t)-1);
+  if ((*a)->d_name[1] == (*b)->d_name[1])
+    return 0;
+  return ((*a)->d_name[1] < (*b)->d_name[1]) ? 1 : -1;
+}
+
+int main(int argc, char *argv[]) {
+  assert(argc == 2);
+  char buf[1024];
+  snprintf(buf, sizeof(buf), "%s/%s", argv[1], "scandir_test_root/");
+
+  struct dirent **d;
+  int res = scandirat(AT_FDCWD, buf, &d, my_filter, my_compar);
+  assert(res == 2);
+  assert(__msan_test_shadow(&d, sizeof(*d)) == (size_t)-1);
+  for (int i = 0; i < res; ++i) {
+    assert(__msan_test_shadow(&d[i], sizeof(d[i])) == (size_t)-1);
+    assert(__msan_test_shadow(d[i], d[i]->d_reclen) == (size_t)-1);
+  }
+
+  assert(strcmp(d[0]->d_name, "bbb") == 0);
+  assert(strcmp(d[1]->d_name, "aab") == 0);
+  return 0;
+}
diff --git a/compiler-rt/test/msan/scandirat_null.cpp b/compiler-rt/test/msan/scandirat_null.cpp
new file mode 100644
index 0000000000000..ced2041417347
--- /dev/null
+++ b/compiler-rt/test/msan/scandirat_null.cpp
@@ -0,0 +1,34 @@
+// RUN: %clangxx_msan -O0 %s -o %t && %run %t %p
+// RUN: %clangxx_msan -O0 -D_FILE_OFFSET_BITS=64 %s -o %t && %run %t %p
+// RUN: %clangxx_msan -O3 %s -o %t && %run %t %p
+
+#include <assert.h>
+#include <glob.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <sanitizer/msan_interface.h>
+
+
+int main(int argc, char *argv[]) {
+  assert(argc == 2);
+  char buf[1024];
+  snprintf(buf, sizeof(buf), "%s/%s", argv[1], "scandir_test_root/");
+
+  struct dirent **d;
+  int res = scandirat(AT_FDCWD, buf, &d, NULL, NULL);
+  assert(res >= 3);
+  assert(__msan_test_shadow(&d, sizeof(*d)) == (size_t)-1);
+  for (int i = 0; i < res; ++i) {
+    assert(__msan_test_shadow(&d[i], sizeof(d[i])) == (size_t)-1);
+    assert(__msan_test_shadow(d[i], d[i]->d_reclen) == (size_t)-1);
+  }
+  return 0;
+}
diff --git a/compiler-rt/test/sanitizer_common/TestCases/scandirat.c b/compiler-rt/test/sanitizer_common/TestCases/scandirat.c
new file mode 100644
index 0000000000000..6d889cfe212be
--- /dev/null
+++ b/compiler-rt/test/sanitizer_common/TestCases/scandirat.c
@@ -0,0 +1,28 @@
+// REQUIRES: (linux && !android) || freebsd
+
+// RUN: rm -rf %t-dir
+// RUN: mkdir -p %t-dir
+// RUN: touch %t-dir/a %t-dir/b %t-dir/c
+
+// RUN: %clang %s -DTEMP_DIR='"'"%t-dir"'"' -o %t && %run %t 2>&1
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(int argc, char **argv) {
+  struct dirent **dirpp = NULL;
+  int count = scandir(AT_FDCWD, TEMP_DIR, &dirpp, NULL, NULL);
+  fprintf(stderr, "count is %d\n", count);
+  if (count >= 0) {
+    for (int i = 0; i < count; ++i) {
+      fprintf(stderr, "found %s\n", dirpp[i]->d_name);
+      free(dirpp[i]);
+    }
+    free(dirpp);
+  }
+  return 0;
+}



More information about the llvm-commits mailing list