[compiler-rt] [asan][Windows] Add additional wcs* interceptors (PR #66128)

nicole mazzuca via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 12 12:06:51 PDT 2023


https://github.com/strega-nil-ms created https://github.com/llvm/llvm-project/pull/66128:

This adds `wcs[n]cat`, `wcs[n]cmp`, `wcs[n]cpy`, and `wcschr` functions to the interception code on Windows; `wcs[n]cat` was already intercepted, but only on POSIX.

This was originally done in [LLVM-D157038][]. However, I made the wrapper functions call `REAL(blah)`, which had been munged in a way that no longer worked, which broke them. Thankfully, the tests caught this issue, so I was able to revert, and now I'm sending this back with the change fixed.

Additionally, as part of this change, I added some new `case`s to `interception_win.cpp`; as part of that, I did a sort on the `case`s, so that we could update these statements in the future without worrying about the ordering between our internal fork (which we are working on upstreaming), and LLVM's.

[LLVM-D157038]: https://reviews.llvm.org/D157038

>From b4492e40218440a20bb9e86fef51e322643f40ad Mon Sep 17 00:00:00 2001
From: Nicole Mazzuca <nicole at strega-nil.co>
Date: Tue, 12 Sep 2023 11:59:23 -0700
Subject: [PATCH] Add moar wcs* interceptions

wcs[n]cat, wcs[n]cmp, wcs[n]cpy, and wcschr

wcs[n]cat already existed, but only on POSIX
---
 compiler-rt/lib/asan/asan_win_dll_thunk.cpp   |   7 +
 .../lib/interception/interception_win.cpp     |  91 +++++--
 .../sanitizer_common_interceptors.inc         |  99 ++++++++
 .../lib/sanitizer_common/sanitizer_libc.cpp   |  19 ++
 .../lib/sanitizer_common/sanitizer_libc.h     |   3 +-
 .../sanitizer_platform_interceptors.h         |   5 +-
 .../TestCases/Windows/replaced_functions.c    | 237 ++++++++++++++++++
 7 files changed, 439 insertions(+), 22 deletions(-)
 create mode 100644 compiler-rt/test/asan/TestCases/Windows/replaced_functions.c

diff --git a/compiler-rt/lib/asan/asan_win_dll_thunk.cpp b/compiler-rt/lib/asan/asan_win_dll_thunk.cpp
index 0fa636bec0d001a..f122fb3145e19f8 100644
--- a/compiler-rt/lib/asan/asan_win_dll_thunk.cpp
+++ b/compiler-rt/lib/asan/asan_win_dll_thunk.cpp
@@ -93,7 +93,14 @@ INTERCEPT_LIBRARY_FUNCTION(strstr);
 INTERCEPT_LIBRARY_FUNCTION(strtok);
 INTERCEPT_LIBRARY_FUNCTION(strtol);
 INTERCEPT_LIBRARY_FUNCTION(strtoll);
+INTERCEPT_LIBRARY_FUNCTION(wcscat);
+INTERCEPT_LIBRARY_FUNCTION(wcschr);
+INTERCEPT_LIBRARY_FUNCTION(wcscmp);
+INTERCEPT_LIBRARY_FUNCTION(wcscpy);
 INTERCEPT_LIBRARY_FUNCTION(wcslen);
+INTERCEPT_LIBRARY_FUNCTION(wcsncat);
+INTERCEPT_LIBRARY_FUNCTION(wcsncmp);
+INTERCEPT_LIBRARY_FUNCTION(wcsncpy);
 INTERCEPT_LIBRARY_FUNCTION(wcsnlen);
 
 #  if defined(_MSC_VER) && !defined(__clang__)
diff --git a/compiler-rt/lib/interception/interception_win.cpp b/compiler-rt/lib/interception/interception_win.cpp
index 00c317510e42087..3217abe43e860c0 100644
--- a/compiler-rt/lib/interception/interception_win.cpp
+++ b/compiler-rt/lib/interception/interception_win.cpp
@@ -520,15 +520,21 @@ static size_t GetInstructionSize(uptr address, size_t* rel_offset = nullptr) {
   switch (*(u16*)(address)) {
     case 0x018A:  // 8A 01 : mov al, byte ptr [ecx]
     case 0xFF8B:  // 8B FF : mov edi, edi
+    case 0xDC8B:  // 8B DC : mov ebx, esp
     case 0xEC8B:  // 8B EC : mov ebp, esp
     case 0xc889:  // 89 C8 : mov eax, ecx
     case 0xE589:  // 89 E5 : mov ebp, esp
     case 0xC18B:  // 8B C1 : mov eax, ecx
+    case 0xFF33:  // 33 FF : xor edi, edi
     case 0xC033:  // 33 C0 : xor eax, eax
     case 0xC933:  // 33 C9 : xor ecx, ecx
     case 0xD233:  // 33 D2 : xor edx, edx
       return 2;
 
+    case 0xEC83:  // 83 EC XX : sub esp, XX
+    case 0xE483:  // 83 E4 XX : and esp, XX
+      return 3;
+
     // Cannot overwrite control-instruction. Return 0 to indicate failure.
     case 0x25FF:  // FF 25 XX XX XX XX : jmp [XXXXXXXX]
       return 0;
@@ -572,6 +578,10 @@ static size_t GetInstructionSize(uptr address, size_t* rel_offset = nullptr) {
     case 0x018a:  // mov al, byte ptr [rcx]
       return 2;
 
+    case 0xE483:    // 83 E4 XX : and esp, XX
+    case 0xC1F6:    // F6 C1 XX : test cl, XX
+      return 3;
+
     case 0x058B:  // 8B 05 XX XX XX XX : mov eax, dword ptr [XX XX XX XX]
       if (rel_offset)
         *rel_offset = 2;
@@ -579,37 +589,72 @@ static size_t GetInstructionSize(uptr address, size_t* rel_offset = nullptr) {
   }
 
   switch (0x00FFFFFF & *(u32*)address) {
-    case 0xe58948:    // 48 8b c4 : mov rbp, rsp
-    case 0xc18b48:    // 48 8b c1 : mov rax, rcx
-    case 0xc48b48:    // 48 8b c4 : mov rax, rsp
-    case 0xd9f748:    // 48 f7 d9 : neg rcx
-    case 0xd12b48:    // 48 2b d1 : sub rdx, rcx
-    case 0x07c1f6:    // f6 c1 07 : test cl, 0x7
-    case 0xc98548:    // 48 85 C9 : test rcx, rcx
-    case 0xd28548:    // 48 85 d2 : test rdx, rdx
-    case 0xc0854d:    // 4d 85 c0 : test r8, r8
     case 0xc2b60f:    // 0f b6 c2 : movzx eax, dl
-    case 0xc03345:    // 45 33 c0 : xor r8d, r8d
-    case 0xc93345:    // 45 33 c9 : xor r9d, r9d
-    case 0xdb3345:    // 45 33 DB : xor r11d, r11d
-    case 0xd98b4c:    // 4c 8b d9 : mov r11, rcx
-    case 0xd28b4c:    // 4c 8b d2 : mov r10, rdx
-    case 0xc98b4c:    // 4C 8B C9 : mov r9, rcx
-    case 0xc18b4c:    // 4C 8B C1 : mov r8, rcx
     case 0xd2b60f:    // 0f b6 d2 : movzx edx, dl
-    case 0xca2b48:    // 48 2b ca : sub rcx, rdx
-    case 0x10b70f:    // 0f b7 10 : movzx edx, WORD PTR [rax]
+    case 0x01b70f:    // 0f b7 01 : movzx eax, word ptr [rcx]
+    case 0x02b70f:    // 0f b7 02 : movzx eax, word ptr [rdx]
+    case 0x10b70f:    // 0f b7 10 : movzx edx, word ptr [rax]
+    case 0xc2b70f:    // 0f b7 c2 : movzx eax, dx
     case 0xc00b4d:    // 3d 0b c0 : or r8, r8
     case 0xc08b41:    // 41 8b c0 : mov eax, r8d
+    case 0xc18b41:    // 41 8b c1 : mov eax, r9d
+    case 0xc28b41:    // 41 8b c2 : mov eax, r10d
+    case 0xc38b41:    // 41 8b c3 : mov eax, r11d
+    case 0xc48b41:    // 41 8b c4 : mov eax, r12d
+    case 0xc03345:    // 45 33 c0 : xor r8d, r8d
+    case 0xc93345:    // 45 33 c9 : xor r9d, r9d
+    case 0xdb3345:    // 45 33 db : xor r11d, r11d
+    case 0xca2b48:    // 48 2b ca : sub rcx, rdx
+    case 0xd12b48:    // 48 2b d1 : sub rdx, rcx
+    case 0xca3b48:    // 48 3b ca : cmp rcx, rdx
+    case 0xc08548:    // 48 85 c0 : test rax, rax
+    case 0xc98548:    // 48 85 c9 : test rcx, rcx
+    case 0xd28548:    // 48 85 d2 : test rdx, rdx
+    case 0xdb8548:    // 48 85 db : test rbx, rbx
+    case 0xe48548:    // 48 85 e4 : test rsp, rsp
+    case 0xed8548:    // 48 85 ed : test rbp, rbp
+    case 0xe58948:    // 48 89 e5 : mov rbp, rsp
+    case 0xc18b48:    // 48 8b c1 : mov rax, rcx
+    case 0xc48b48:    // 48 8b c4 : mov rax, rsp
     case 0xd18b48:    // 48 8b d1 : mov rdx, rcx
-    case 0xdc8b4c:    // 4c 8b dc : mov r11, rsp
+    case 0xd9f748:    // 48 f7 d9 : neg rcx
+    case 0xc0ff48:    // 48 ff c0 : inc rax
+    case 0xc1ff48:    // 48 ff c1 : inc rcx
+    case 0xc2ff48:    // 48 ff c2 : inc rdx
+    case 0xc3ff48:    // 48 ff c3 : inc rbx
+    case 0xc6ff48:    // 48 ff c6 : inc rsi
+    case 0xc7ff48:    // 48 ff c7 : inc rdi
+    case 0xc0ff49:    // 49 ff c0 : inc r8
+    case 0xc1ff49:    // 49 ff c1 : inc r9
+    case 0xc2ff49:    // 49 ff c2 : inc r10
+    case 0xc3ff49:    // 49 ff c3 : inc r11
+    case 0xc4ff49:    // 49 ff c4 : inc r12
+    case 0xc5ff49:    // 49 ff c5 : inc r13
+    case 0xc6ff49:    // 49 ff c6 : inc r14
+    case 0xc7ff49:    // 49 ff c7 : inc r15
+    case 0xc22b4c:    // 4c 2b c2 : sub r8, rdx
+    case 0xc18b4c:    // 4c 8b c1 : mov r8, rcx
+    case 0xc98b4c:    // 4c 8b c9 : mov r9, rcx
     case 0xd18b4c:    // 4c 8b d1 : mov r10, rcx
-    case 0xE0E483:    // 83 E4 E0 : and esp, 0xFFFFFFE0
+    case 0xd28b4c:    // 4c 8b d2 : mov r10, rdx
+    case 0xd98b4c:    // 4c 8b d9 : mov r11, rcx
+    case 0xdc8b4c:    // 4c 8b dc : mov r11, rsp
+    case 0xc0854d:    // 4d 85 c0 : test r8, r8
+    case 0xc9854d:    // 4d 85 c9 : test r9, r9
+    case 0xd2854d:    // 4d 85 d2 : test r10, r10
+    case 0xdb854d:    // 4d 85 db : test r11, r11
+    case 0xe4854d:    // 4d 85 e4 : test r12, r12
+    case 0xed854d:    // 4d 85 ed : test r13, r13
+    case 0xf6854d:    // 4d 85 f6 : test r14, r14
+    case 0xff854d:    // 4d 85 ff : test r15, r15
       return 3;
 
     case 0xec8348:    // 48 83 ec XX : sub rsp, XX
     case 0xf88349:    // 49 83 f8 XX : cmp r8, XX
     case 0x588948:    // 48 89 58 XX : mov QWORD PTR[rax + XX], rbx
+    case 0x245489:    // 89 54 24 XX : mov DWORD PTR[rsp + XX], edx
+    case 0x398366:    // 66 83 39 XX : cmp DWORD PTR [rcx], XX
+    case 0x428d44:    // 44 8d 42 XX : lea r8d , [rdx + XX]
       return 4;
 
     case 0xec8148:    // 48 81 EC XX XX XX XX : sub rsp, XXXXXXXX
@@ -631,6 +676,8 @@ static size_t GetInstructionSize(uptr address, size_t* rel_offset = nullptr) {
   }
 
   switch (*(u32*)(address)) {
+    case 0x01b70f44:  // 44 0f b7 01 : movzx r8d, WORD PTR [rcx]
+      return 4;
     case 0x24448b48:  // 48 8b 44 24 XX : mov rax, QWORD ptr [rsp + XX]
     case 0x246c8948:  // 48 89 6C 24 XX : mov QWORD ptr [rsp + XX], rbp
     case 0x245c8948:  // 48 89 5c 24 XX : mov QWORD PTR [rsp + XX], rbx
@@ -640,6 +687,10 @@ static size_t GetInstructionSize(uptr address, size_t* rel_offset = nullptr) {
     case 0x24548948:  // 48 89 54 24 XX : mov QWORD PTR [rsp + XX], rdx
     case 0x244c894c:  // 4c 89 4c 24 XX : mov QWORD PTR [rsp + XX], r9
     case 0x2444894c:  // 4c 89 44 24 XX : mov QWORD PTR [rsp + XX], r8
+    case 0x244c8944:  // 44 89 4c 24 XX   mov DWORD PTR [rsp + XX], r9d
+    case 0x24448944:  // 44 89 44 24 XX   mov DWORD PTR [rsp + XX], r8d
+    case 0x24548966:  // 66 89 54 24 XX : mov [rsp + XX], dx
+    case 0x246c8d48:  // 48 8d 6c 24 XX : lea rbp, [rsp + XX]
       return 5;
     case 0x24648348:  // 48 83 64 24 XX : and QWORD PTR [rsp + XX], YY
       return 6;
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
index 0e563fa12022a3e..87192ed2d5aa227 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
@@ -449,6 +449,9 @@ INTERCEPTOR(char*, textdomain, const char *domainname) {
 static inline int CharCmpX(unsigned char c1, unsigned char c2) {
   return (c1 == c2) ? 0 : (c1 < c2) ? -1 : 1;
 }
+static inline int WCharCmpX(wchar_t c1, wchar_t c2) {
+  return (c1 == c2) ? 0 : (c1 < c2) ? -1 : 1;
+}
 
 DECLARE_WEAK_INTERCEPTOR_HOOK(__sanitizer_weak_hook_strcmp, uptr called_pc,
                               const char *s1, const char *s2, int result)
@@ -7090,6 +7093,99 @@ INTERCEPTOR(wchar_t *, wcsdup, wchar_t *s) {
 #define INIT_WCSDUP
 #endif
 
+#if SANITIZER_INTERCEPT_WCSCPY
+INTERCEPTOR(wchar_t *, wcscpy, wchar_t *dst, const wchar_t *src) {
+  void *ctx;
+  COMMON_INTERCEPTOR_ENTER(ctx, wcscpy, dst, src);
+  SIZE_T src_len = internal_wcslen(src);
+  COMMON_INTERCEPTOR_READ_RANGE(ctx, src, sizeof(wchar_t) * (src_len + 1));
+  COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dst, sizeof(wchar_t) * (src_len + 1));
+  wchar_t *result = internal_wcscpy(dst, src);
+  return result;
+}
+INTERCEPTOR(wchar_t *, wcsncpy, wchar_t *dst, const wchar_t *src, SIZE_T size) {
+  void *ctx;
+  COMMON_INTERCEPTOR_ENTER(ctx, wcsncpy, dst, src, size);
+  SIZE_T src_len = internal_wcsnlen(src, size);
+  if (src_len != size)
+    ++src_len; // account for the nul terminator
+  COMMON_INTERCEPTOR_READ_RANGE(ctx, src, sizeof(wchar_t) * src_len);
+  COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dst, sizeof(wchar_t) * src_len);
+  wchar_t *result = internal_wcsncpy(dst, src, size);
+  return result;
+}
+
+#define INIT_WCSCPY \
+  COMMON_INTERCEPT_FUNCTION(wcscpy); \
+  COMMON_INTERCEPT_FUNCTION(wcsncpy);
+#else
+#define INIT_WCSCPY
+#endif
+
+#if SANITIZER_INTERCEPT_WCSCMP
+INTERCEPTOR(int, wcscmp, const wchar_t *s1, const wchar_t *s2) {
+  void *ctx;
+  COMMON_INTERCEPTOR_ENTER(ctx, wcscmp, s1, s2);
+  wchar_t c1, c2;
+  uptr i;
+  for (i = 0;; i++) {
+    c1 = s1[i];
+    c2 = s2[i];
+    if (c1 != c2 || c1 == '\0') break;
+  }
+  if (common_flags()->intercept_strcmp) {
+    COMMON_INTERCEPTOR_READ_RANGE(ctx, s1, sizeof(wchar_t) * (i + 1));
+    COMMON_INTERCEPTOR_READ_RANGE(ctx, s2, sizeof(wchar_t) * (i + 1));
+  }
+  int result = WCharCmpX(c1, c2);
+  return result;
+}
+INTERCEPTOR(int, wcsncmp, wchar_t *s1, const wchar_t *s2, SIZE_T size) {
+  void *ctx;
+  COMMON_INTERCEPTOR_ENTER(ctx, wcsncmp, s1, s2, size);
+  wchar_t c1, c2;
+  uptr i;
+  for (i = 0; i < size; i++) {
+    c1 = s1[i];
+    c2 = s2[i];
+    if (c1 != c2 || c1 == '\0') break;
+  }
+  uptr i1 = i;
+  uptr i2 = i;
+  if (common_flags()->strict_string_checks) {
+    for (; i1 < size && s1[i1]; i1++) {}
+    for (; i2 < size && s2[i2]; i2++) {}
+  }
+  COMMON_INTERCEPTOR_READ_RANGE(ctx, s1, sizeof(wchar_t) * Min(i1 + 1, size));
+  COMMON_INTERCEPTOR_READ_RANGE(ctx, s2, sizeof(wchar_t) * Min(i2 + 1, size));
+  int result = WCharCmpX(c1, c2);
+  return result;
+}
+
+#define INIT_WCSCMP \
+  COMMON_INTERCEPT_FUNCTION(wcscmp); \
+  COMMON_INTERCEPT_FUNCTION(wcsncmp);
+#else
+#define INIT_WCSCMP
+#endif
+
+#if SANITIZER_INTERCEPT_WCSCHR
+INTERCEPTOR(wchar_t*, wcschr, const wchar_t *s, wchar_t c) {
+  void *ctx;
+  COMMON_INTERCEPTOR_ENTER(ctx, wcschr, s, c);
+  wchar_t *result = REAL(wcschr)(s, c);
+  if (common_flags()->intercept_strchr) {
+    SIZE_T read_size = (result ? result - s : internal_wcslen(s)) + 1;
+    COMMON_INTERCEPTOR_READ_RANGE(ctx, s, sizeof(*s) * read_size);
+  }
+  return result;
+}
+
+#define INIT_WCSCHR COMMON_INTERCEPT_FUNCTION(wcschr)
+#else
+#define INIT_WCSCHR
+#endif
+
 #if SANITIZER_INTERCEPT_STRXFRM
 static SIZE_T RealStrLen(const char *str) { return internal_strlen(str); }
 
@@ -10474,6 +10570,9 @@ static void InitializeCommonInterceptors() {
   INIT_WCSLEN;
   INIT_WCSCAT;
   INIT_WCSDUP;
+  INIT_WCSCHR;
+  INIT_WCSCMP;
+  INIT_WCSCPY;
   INIT_WCSXFRM;
   INIT___WCSXFRM_L;
   INIT_ACCT;
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_libc.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_libc.cpp
index 4a6fa5e8dbacb49..5658164500d7934 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_libc.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_libc.cpp
@@ -279,6 +279,25 @@ uptr internal_wcsnlen(const wchar_t *s, uptr maxlen) {
   return i;
 }
 
+wchar_t* internal_wcscpy(wchar_t* dst, const wchar_t* src) {
+  wchar_t* dst_it = dst;
+  do {
+    *dst_it++ = *src++;
+  } while (*src);
+  return dst;
+}
+
+wchar_t* internal_wcsncpy(wchar_t* dst, const wchar_t* src, uptr maxlen) {
+  uptr i = 0;
+  for (; src[i] && i < maxlen; ++i) {
+    dst[i] = src[i];
+  }
+  for (; i < maxlen; ++i) {
+    dst[i] = 0;
+  }
+  return dst;
+}
+
 bool mem_is_zero(const char *beg, uptr size) {
   CHECK_LE(size, 1ULL << FIRST_32_SECOND_64(30, 40));  // Sanity check.
   const char *end = beg + size;
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_libc.h b/compiler-rt/lib/sanitizer_common/sanitizer_libc.h
index e881db2079086d4..5ac547c195383e9 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_libc.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_libc.h
@@ -71,7 +71,8 @@ int internal_snprintf(char *buffer, uptr length, const char *format, ...)
     FORMAT(3, 4);
 uptr internal_wcslen(const wchar_t *s);
 uptr internal_wcsnlen(const wchar_t *s, uptr maxlen);
-
+wchar_t* internal_wcscpy(wchar_t* dst, const wchar_t* src);
+wchar_t* internal_wcsncpy(wchar_t* dst, const wchar_t* src, uptr maxlen);
 // Return true if all bytes in [mem, mem+size) are zero.
 // Optimized for the case when the result is true.
 bool mem_is_zero(const char *mem, uptr size);
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
index 8c7c00de6d1292f..ef5d14d4e16f967 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
@@ -496,8 +496,11 @@
 #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_WCSCPY SI_WINDOWS
+#define SANITIZER_INTERCEPT_WCSCMP SI_WINDOWS
+#define SANITIZER_INTERCEPT_WCSCHR SI_WINDOWS
 #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/Windows/replaced_functions.c b/compiler-rt/test/asan/TestCases/Windows/replaced_functions.c
new file mode 100644
index 000000000000000..609da83690b3f92
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Windows/replaced_functions.c
@@ -0,0 +1,237 @@
+// RUN: %clang_cl_asan /Od %s /Fe%t
+
+#define _CRT_SECURE_NO_WARNINGS
+#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <malloc.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+// the size parameter is the size of the src buffer in bytes
+// (dst has size 2 * size, for strcat reasons)
+// the last two bytes are 0 (for str/wcs functions)
+// they have distinct first bytes
+// dst will be free'd if --fail is passed
+typedef int test_function_t(void *, const void *, size_t);
+
+#if __clang__
+#  define DECLARE_WRAPPED(name) typeof(name) __asan_wrap_##name;
+#else
+// typeof is only supported in MSVC as of 17.7, with `-std:clatest`,
+// and it doesn't seem to be possible to pass an option _only_ to cl
+#  define DECLARE_WRAPPED(name) int __asan_wrap_##name();
+#endif
+
+#define TEST_FUNCTION_DECL(name)                                               \
+  __declspec(dllexport) int test_##name(void *dst, const void *src, size_t size)
+#define TEST_WRAPPED_FUNCTION_DECL(name)                                       \
+  DECLARE_WRAPPED(name)                                                        \
+  __declspec(dllexport) int test_wrap_##name(void *dst, const void *src,       \
+                                             size_t size)
+
+#define TEST_FUNCTION(name, ...)                                               \
+  TEST_FUNCTION_DECL(name) { return 0 != name(__VA_ARGS__); }                  \
+  TEST_WRAPPED_FUNCTION_DECL(name) {                                           \
+    return 0 != __asan_wrap_##name(__VA_ARGS__);                               \
+  }
+
+#define TEST_NOT_FUNCTION(name, ...)                                           \
+  TEST_FUNCTION_DECL(name) { return 0 == name(__VA_ARGS__); }                  \
+  TEST_WRAPPED_FUNCTION_DECL(name) {                                           \
+    return 0 == __asan_wrap_##name(__VA_ARGS__);                               \
+  }
+
+// RUN: %run %t --success memset 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped memset 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail memset 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped memset 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(memset, dst, *(const char *)src, size)
+// RUN: %run %t --success memmove 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped memmove 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail memmove 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped memmove 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(memmove, dst, src, size)
+// RUN: %run %t --success memcpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped memcpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail memcpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped memcpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(memcpy, dst, src, size)
+// RUN: %run %t --success memcmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped memcmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail memcmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped memcmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(memcmp, dst, src, size)
+// RUN: %run %t --success memchr 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped memchr 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail memchr 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped memchr 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_NOT_FUNCTION(memchr, dst, *(const char *)src, size)
+
+// RUN: %run %t --success strlen 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strlen 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strlen 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strlen 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(strlen, dst)
+// RUN: %run %t --success strnlen 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strnlen 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strnlen 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strnlen 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(strnlen, dst, size)
+// RUN: %run %t --success strcpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strcpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strcpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strcpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(strcpy, dst, src)
+// RUN: %run %t --success strncpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strncpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strncpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strncpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(strncpy, dst, src, size)
+// RUN: %run %t --success strcat 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strcat 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strcat 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strcat 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(strcat, dst, src)
+// RUN: %run %t --success strncat 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strncat 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strncat 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strncat 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(strncat, dst, src, size)
+// RUN: %run %t --success strcmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strcmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strcmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strcmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(strcmp, dst, src)
+// RUN: %run %t --success strncmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strncmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strncmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strncmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(strncmp, dst, src, size)
+// RUN: %run %t --success strchr 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped strchr 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail strchr 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped strchr 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_NOT_FUNCTION(strchr, dst, *(const char *)src)
+
+// RUN: %run %t --success wcslen 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped wcslen 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail wcslen 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped wcslen 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(wcslen, dst)
+// RUN: %run %t --success wcsnlen 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped wcsnlen 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail wcsnlen 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped wcsnlen 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(wcsnlen, dst, size)
+// RUN: %run %t --success wcscpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped wcscpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail wcscpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped wcscpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(wcscpy, dst, src)
+// RUN: %run %t --success wcsncpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped wcsncpy 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail wcsncpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped wcsncpy 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(wcsncpy, dst, src, size / 2)
+// RUN: %run %t --success wcscat 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped wcscat 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail wcscat 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped wcscat 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(wcscat, dst, src)
+// RUN: %run %t --success wcsncat 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped wcsncat 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail wcsncat 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped wcsncat 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(wcsncat, dst, src, size / 2)
+// RUN: %run %t --success wcscmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped wcscmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail wcscmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped wcscmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(wcscmp, dst, src)
+// note: clang does not actually emit a call to wcsncmp, for some reason
+// RUN: %run %t --success --wrapped wcsncmp 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail --wrapped wcsncmp 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_FUNCTION(wcsncmp, dst, src, size / 2)
+// RUN: %run %t --success wcschr 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: %run %t --success --wrapped wcschr 2>&1 | FileCheck %s --check-prefix=CHECK-SUCCESS
+// RUN: not %run %t --fail wcschr 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+// RUN: not %run %t --fail --wrapped wcschr 2>&1 | FileCheck %s --check-prefix=CHECK-FAIL
+TEST_NOT_FUNCTION(wcschr, dst, *(const wchar_t *)src)
+
+void help(const char *progname) {
+  fprintf(stderr, "Usage: %s (--fail|--success) [--wrapped] <intrinsic>\n",
+          progname);
+  exit(1);
+}
+
+enum SuccessOrFail {
+  SOF_None,
+  SOF_Succeed,
+  SOF_Fail,
+};
+
+int main(int argc, char *argv[]) {
+  if (argc < 3 || argc > 4) {
+    help(argv[0]);
+  }
+
+  const size_t size = 8;
+
+  void *src = malloc(size);
+  memset(src, 1, size);
+  ((wchar_t *)src)[size / 2 - 1] = L'\0';
+
+  void *dst = malloc(size * 2);
+  memset(dst, 2, size);
+  ((wchar_t *)dst)[size / 2 - 1] = L'\0';
+
+  bool wrapped = false;
+  enum SuccessOrFail succeed = SOF_None;
+  const char *function_name = 0;
+  for (int i = 1; i < argc; ++i) {
+    if (strcmp(argv[i], "--success") == 0) {
+      succeed = SOF_Succeed;
+    } else if (strcmp(argv[i], "--fail") == 0) {
+      succeed = SOF_Fail;
+    } else if (strcmp(argv[i], "--wrapped") == 0) {
+      wrapped = true;
+    } else {
+      function_name = argv[i];
+    }
+  }
+
+  if (!function_name || succeed == SOF_None) {
+    help(argv[0]);
+  } else if (succeed == SOF_Fail) {
+    free(
+        dst); // free dst, so that the function _should_ fail if ASan is correctly implemented
+  }
+
+  test_function_t *test_function = 0;
+  char buffer[32];
+  if (!wrapped) {
+    strcpy(buffer, "test_");
+  } else {
+    strcpy(buffer, "test_wrap_");
+  }
+
+  // CHECK-FAIL: ERROR: AddressSanitizer: heap-use-after-free
+  // CHECK-SUCCESS: pass
+  // CHECK-SUCCESS-NOT: ERROR: AddressSanitizer: heap-use-after-free
+
+  if (strncat(buffer, function_name, 32)) {
+    test_function = (test_function_t *)GetProcAddress(0, buffer);
+  }
+
+  if (!test_function) {
+    fprintf(stderr, "Unknown test: %s\n", argv[2]);
+    return 1;
+  }
+
+  return !(test_function(dst, src, size) && printf("pass\n"));
+}



More information about the llvm-commits mailing list