Skip to content

Conversation

Cao-Wuhui
Copy link
Contributor

Summary:

  • Add wcscpy/wcsncpy interceptors; enable wcscat/wcsncat on Windows
  • Add tests (standard RUN lines)

Prior work:

Impl:

  • MaybeRealWcsnlen guarded by SANITIZER_INTERCEPT_WCSLEN
  • Register wcscpy/wcsncpy; enable WCSCAT on Windows; update Windows thunk

Testing:

  • 4 new tests pass on AArch64 Linux
  • Full check-asan passed (0 fail)

Context:

Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot
Copy link
Member

llvmbot commented Sep 12, 2025

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

Author: Yixuan Cao (Cao-Wuhui)

Changes

Summary:

  • Add wcscpy/wcsncpy interceptors; enable wcscat/wcsncat on Windows
  • Add tests (standard RUN lines)

Prior work:

  • Based on and extends PR #90909 (thanks!)

Impl:

  • MaybeRealWcsnlen guarded by SANITIZER_INTERCEPT_WCSLEN
  • Register wcscpy/wcsncpy; enable WCSCAT on Windows; update Windows thunk

Testing:

  • 4 new tests pass on AArch64 Linux
  • Full check-asan passed (0 fail)

Context:


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

8 Files Affected:

  • (modified) compiler-rt/lib/asan/asan_interceptors.cpp (+44)
  • (modified) compiler-rt/lib/asan/asan_interceptors.h (+1)
  • (modified) compiler-rt/lib/asan/asan_win_static_runtime_thunk.cpp (+4)
  • (modified) compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h (+1-1)
  • (added) compiler-rt/test/asan/TestCases/wcscat.cpp (+13)
  • (added) compiler-rt/test/asan/TestCases/wcscpy.cpp (+17)
  • (added) compiler-rt/test/asan/TestCases/wcsncat.cpp (+13)
  • (added) compiler-rt/test/asan/TestCases/wcsncpy.cpp (+13)
diff --git a/compiler-rt/lib/asan/asan_interceptors.cpp b/compiler-rt/lib/asan/asan_interceptors.cpp
index 7c9a08b9083a2..dc56da99a69f0 100644
--- a/compiler-rt/lib/asan/asan_interceptors.cpp
+++ b/compiler-rt/lib/asan/asan_interceptors.cpp
@@ -65,6 +65,15 @@ static inline uptr MaybeRealStrnlen(const char *s, uptr maxlen) {
   return internal_strnlen(s, maxlen);
 }
 
+static inline uptr MaybeRealWcsnlen(const wchar_t *s, uptr maxlen) {
+#if SANITIZER_INTERCEPT_STRNLEN
+  if (REAL(wcsnlen)) {
+    return REAL(wcsnlen)(s, maxlen);
+  }
+#endif
+  return internal_wcsnlen(s, maxlen);
+}
+
 void SetThreadName(const char *name) {
   AsanThread *t = GetCurrentThread();
   if (t)
@@ -570,6 +579,26 @@ INTERCEPTOR(char *, strcpy, char *to, const char *from) {
   return REAL(strcpy)(to, from);
 }
 
+INTERCEPTOR(wchar_t *, wcscpy, wchar_t *to, const wchar_t *from) {
+  void *ctx;
+  ASAN_INTERCEPTOR_ENTER(ctx, wcscpy);
+  if constexpr (SANITIZER_APPLE) {
+    if (UNLIKELY(!AsanInited()))
+      return REAL(wcscpy)(to, from);
+  } else {
+    if (!TryAsanInitFromRtl())
+      return REAL(wcscpy)(to, from);
+  }
+
+  if (flags()->replace_str) {
+    uptr from_size = (internal_wcslen(from) + 1) * sizeof(wchar_t);
+    CHECK_RANGES_OVERLAP("wcscpy", to, from_size, from, from_size);
+    ASAN_READ_RANGE(ctx, from, from_size);
+    ASAN_WRITE_RANGE(ctx, to, from_size);
+  }
+  return REAL(wcscpy)(to, from);
+}
+
 // Windows doesn't always define the strdup identifier,
 // and when it does it's a macro defined to either _strdup
 // or _strdup_dbg, _strdup_dbg ends up calling _strdup, so
@@ -633,6 +662,19 @@ INTERCEPTOR(char*, strncpy, char *to, const char *from, usize size) {
   return REAL(strncpy)(to, from, size);
 }
 
+INTERCEPTOR(wchar_t *, wcsncpy, wchar_t *to, const wchar_t *from, usize size) {
+  void *ctx;
+  ASAN_INTERCEPTOR_ENTER(ctx, wcsncpy);
+  AsanInitFromRtl();
+  if (flags()->replace_str) {
+    uptr from_size = Min<uptr>(size, MaybeRealWcsnlen(from, size) + 1) * sizeof(wchar_t);
+    CHECK_RANGES_OVERLAP("wcsncpy", to, from_size, from, from_size);
+    ASAN_READ_RANGE(ctx, from, from_size);
+    ASAN_WRITE_RANGE(ctx, to, size * sizeof(wchar_t));
+  }
+  return REAL(wcsncpy)(to, from, size);
+}
+
 template <typename Fn>
 static ALWAYS_INLINE auto StrtolImpl(void *ctx, Fn real, const char *nptr,
                                      char **endptr, int base)
@@ -808,6 +850,8 @@ void InitializeAsanInterceptors() {
   ASAN_INTERCEPT_FUNC(strcpy);
   ASAN_INTERCEPT_FUNC(strncat);
   ASAN_INTERCEPT_FUNC(strncpy);
+  ASAN_INTERCEPT_FUNC(wcscpy);
+  ASAN_INTERCEPT_FUNC(wcsncpy);
   ASAN_INTERCEPT_FUNC(strdup);
 #  if ASAN_INTERCEPT___STRDUP
   ASAN_INTERCEPT_FUNC(__strdup);
diff --git a/compiler-rt/lib/asan/asan_interceptors.h b/compiler-rt/lib/asan/asan_interceptors.h
index 3e2386eaf8092..33d4210b5815c 100644
--- a/compiler-rt/lib/asan/asan_interceptors.h
+++ b/compiler-rt/lib/asan/asan_interceptors.h
@@ -129,6 +129,7 @@ DECLARE_REAL(char*, strchr, const char *str, int c)
 DECLARE_REAL(SIZE_T, strlen, const char *s)
 DECLARE_REAL(char*, strncpy, char *to, const char *from, SIZE_T size)
 DECLARE_REAL(SIZE_T, strnlen, const char *s, SIZE_T maxlen)
+DECLARE_REAL(SIZE_T, wcsnlen, const wchar_t *s, SIZE_T maxlen)
 DECLARE_REAL(char*, strstr, const char *s1, const char *s2)
 
 #  if !SANITIZER_APPLE
diff --git a/compiler-rt/lib/asan/asan_win_static_runtime_thunk.cpp b/compiler-rt/lib/asan/asan_win_static_runtime_thunk.cpp
index 4a69b66574039..8e88f77a2536d 100644
--- a/compiler-rt/lib/asan/asan_win_static_runtime_thunk.cpp
+++ b/compiler-rt/lib/asan/asan_win_static_runtime_thunk.cpp
@@ -65,6 +65,10 @@ INTERCEPT_LIBRARY_FUNCTION_ASAN(strstr);
 INTERCEPT_LIBRARY_FUNCTION_ASAN(strtok);
 INTERCEPT_LIBRARY_FUNCTION_ASAN(wcslen);
 INTERCEPT_LIBRARY_FUNCTION_ASAN(wcsnlen);
+INTERCEPT_LIBRARY_FUNCTION_ASAN(wcscat);
+INTERCEPT_LIBRARY_FUNCTION_ASAN(wcsncat);
+INTERCEPT_LIBRARY_FUNCTION_ASAN(wcscpy);
+INTERCEPT_LIBRARY_FUNCTION_ASAN(wcsncpy);
 
 // Note: Don't intercept strtol(l). They are supposed to set errno for out-of-
 // range values, but since the ASan runtime is linked against the dynamic CRT,
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
index 29987decdff45..88ecd7e16306a 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
@@ -551,7 +551,7 @@ SANITIZER_WEAK_IMPORT void *aligned_alloc(__sanitizer::usize __alignment,
 #define SANITIZER_INTERCEPT_MALLOC_USABLE_SIZE (!SI_MAC && !SI_NETBSD)
 #define SANITIZER_INTERCEPT_MCHECK_MPROBE SI_LINUX_NOT_ANDROID
 #define SANITIZER_INTERCEPT_WCSLEN 1
-#define SANITIZER_INTERCEPT_WCSCAT SI_POSIX
+#define SANITIZER_INTERCEPT_WCSCAT (SI_POSIX || SI_WINDOWS)
 #define SANITIZER_INTERCEPT_WCSDUP SI_POSIX
 #define SANITIZER_INTERCEPT_SIGNAL_AND_SIGACTION (!SI_WINDOWS && SI_NOT_FUCHSIA)
 #define SANITIZER_INTERCEPT_BSD_SIGNAL SI_ANDROID
diff --git a/compiler-rt/test/asan/TestCases/wcscat.cpp b/compiler-rt/test/asan/TestCases/wcscat.cpp
new file mode 100644
index 0000000000000..6154020788e27
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/wcscat.cpp
@@ -0,0 +1,13 @@
+// REQUIRES: !windows
+// RUN: %clangxx_asan -O0 -fno-builtin %s -o %t && %run %t
+
+#include <wchar.h>
+
+int main() {
+  wchar_t dst[16] = L"ab";
+  const wchar_t *src = L"c";
+  wcscat(dst, src);
+  return 0;
+}
+
+
diff --git a/compiler-rt/test/asan/TestCases/wcscpy.cpp b/compiler-rt/test/asan/TestCases/wcscpy.cpp
new file mode 100644
index 0000000000000..3a58f28555269
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/wcscpy.cpp
@@ -0,0 +1,17 @@
+// REQUIRES: !windows
+// RUN: %clangxx_asan -O0 -fno-builtin %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <wchar.h>
+
+__attribute__((noinline)) void bad_wcs(void) {
+  wchar_t buf[] = L"hello";
+  // CHECK: wcscpy-param-overlap: memory ranges
+  wcscpy(buf, buf + 1);
+}
+
+int main() {
+  bad_wcs();
+  return 0;
+}
+
+
diff --git a/compiler-rt/test/asan/TestCases/wcsncat.cpp b/compiler-rt/test/asan/TestCases/wcsncat.cpp
new file mode 100644
index 0000000000000..d260f9ce2f0f2
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/wcsncat.cpp
@@ -0,0 +1,13 @@
+// REQUIRES: !windows
+// RUN: %clangxx_asan -O0 -fno-builtin %s -o %t && %run %t
+
+#include <wchar.h>
+
+int main() {
+  wchar_t dst[16] = L"ab";
+  const wchar_t *src = L"cd";
+  wcsncat(dst, src, 1);
+  return 0;
+}
+
+
diff --git a/compiler-rt/test/asan/TestCases/wcsncpy.cpp b/compiler-rt/test/asan/TestCases/wcsncpy.cpp
new file mode 100644
index 0000000000000..70023bd1f3379
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/wcsncpy.cpp
@@ -0,0 +1,13 @@
+// REQUIRES: !windows
+// RUN: %clangxx_asan -O0 -fno-builtin %s -o %t && %run %t
+
+#include <wchar.h>
+
+int main() {
+  wchar_t src[] = L"abc";
+  wchar_t dst[4] = {0};
+  wcsncpy(dst, src, 3);
+  return 0;
+}
+
+

…t/wcsncat on Windows

Summary:
- Add ASan interceptors for wcscpy/wcsncpy and register them.
- Enable wcscat/wcsncat on Windows via platform interceptor macro.
- Add MaybeRealWcsnlen guarded by SANITIZER_INTERCEPT_WCSLEN.
- Update Windows static thunk to forward wchar functions.
- Add tests for wcscpy/wcsncpy/wcscat/wcsncat with standard RUN lines.

Prior work and attribution:
- Based on and extends PR llvm#90909 (author: branh, Microsoft). Thanks for the prior work and review feedback.
  llvm#90909

Testing:
- AArch64 Linux: 4 new tests pass; full check-asan passed (0 failures) after enabling profile runtime.

Context:
- Related research background: Tech-ASan (Two-stage check for AddressSanitizer): https://arxiv.org/abs/2506.05022
@Cao-Wuhui
Copy link
Contributor Author

I decided to close this PR because I will rework the patch and open a new one later.

@Cao-Wuhui Cao-Wuhui closed this Sep 12, 2025
@Cao-Wuhui Cao-Wuhui deleted the tech-asan-wchar branch September 12, 2025 09:13
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