From 14b03ba1f174a301dfeeddf79144f8fddf81b0a3 Mon Sep 17 00:00:00 2001 From: Oleksii Shevchuk 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +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 +#include +#include +#include +#include + +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; +}