Skip to content

Commit 6ca835b

Browse files
authored
[compiler-rt][asan] Add wcscpy/wcsncpy; enable wcscat/wcsncat on Windows (#160493)
Summary - Add ASan interceptors for wcscpy/wcsncpy on all platforms. - Enable wcscat/wcsncat on Windows (already enabled on POSIX via sanitizer_common). Motivation - Use of wchar string APIs is common on Windows; improve parity with char* string checks. Changes - Implement wcscpy/wcsncpy in asan_interceptors.cpp; check overlap and mark read/write ranges in bytes. - wcsncpy: compute write size in bytes (size * sizeof(wchar_t)) to avoid missed overflows when sizeof(wchar_t) != 1. - Use MaybeRealWcsnlen when available to bound reads. - Register Windows static thunk for wcscpy/wcsncpy/wcscat/wcsncat; rely on sanitizer_common interceptors for wcscat/wcsncat. - Tests: add wcscpy/wcsncpy/wcscat/wcsncat; flush stdout before crash; use resilient FileCheck patterns (reuse [[ADDR]], wildcard for function suffixes and paths, flexible line numbers). Testing - AArch64 Linux: new tests pass with check-asan locally. Follow-up to and based on prior work in PR #90909 (author: branh, Microsoft); builds on that work and addresses review feedback. Thanks! --------- Signed-off-by: Yixuan Cao <[email protected]>
1 parent 9d42c75 commit 6ca835b

File tree

8 files changed

+150
-4
lines changed

8 files changed

+150
-4
lines changed

compiler-rt/lib/asan/asan_interceptors.cpp

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,20 @@ namespace __asan {
5858

5959
static inline uptr MaybeRealStrnlen(const char *s, uptr maxlen) {
6060
#if SANITIZER_INTERCEPT_STRNLEN
61-
if (REAL(strnlen)) {
61+
if (REAL(strnlen))
6262
return REAL(strnlen)(s, maxlen);
63-
}
64-
#endif
63+
# endif
6564
return internal_strnlen(s, maxlen);
6665
}
6766

67+
static inline uptr MaybeRealWcsnlen(const wchar_t* s, uptr maxlen) {
68+
# if SANITIZER_INTERCEPT_WCSNLEN
69+
if (REAL(wcsnlen))
70+
return REAL(wcsnlen)(s, maxlen);
71+
# endif
72+
return internal_wcsnlen(s, maxlen);
73+
}
74+
6875
void SetThreadName(const char *name) {
6976
AsanThread *t = GetCurrentThread();
7077
if (t)
@@ -570,6 +577,20 @@ INTERCEPTOR(char *, strcpy, char *to, const char *from) {
570577
return REAL(strcpy)(to, from);
571578
}
572579

580+
INTERCEPTOR(wchar_t*, wcscpy, wchar_t* to, const wchar_t* from) {
581+
void* ctx;
582+
ASAN_INTERCEPTOR_ENTER(ctx, wcscpy);
583+
if (!TryAsanInitFromRtl())
584+
return REAL(wcscpy)(to, from);
585+
if (flags()->replace_str) {
586+
uptr size = (internal_wcslen(from) + 1) * sizeof(wchar_t);
587+
CHECK_RANGES_OVERLAP("wcscpy", to, size, from, size);
588+
ASAN_READ_RANGE(ctx, from, size);
589+
ASAN_WRITE_RANGE(ctx, to, size);
590+
}
591+
return REAL(wcscpy)(to, from);
592+
}
593+
573594
// Windows doesn't always define the strdup identifier,
574595
// and when it does it's a macro defined to either _strdup
575596
// or _strdup_dbg, _strdup_dbg ends up calling _strdup, so
@@ -633,6 +654,20 @@ INTERCEPTOR(char*, strncpy, char *to, const char *from, usize size) {
633654
return REAL(strncpy)(to, from, size);
634655
}
635656

657+
INTERCEPTOR(wchar_t*, wcsncpy, wchar_t* to, const wchar_t* from, uptr size) {
658+
void* ctx;
659+
ASAN_INTERCEPTOR_ENTER(ctx, wcsncpy);
660+
AsanInitFromRtl();
661+
if (flags()->replace_str) {
662+
uptr from_size =
663+
Min(size, MaybeRealWcsnlen(from, size) + 1) * sizeof(wchar_t);
664+
CHECK_RANGES_OVERLAP("wcsncpy", to, from_size, from, from_size);
665+
ASAN_READ_RANGE(ctx, from, from_size);
666+
ASAN_WRITE_RANGE(ctx, to, size * sizeof(wchar_t));
667+
}
668+
return REAL(wcsncpy)(to, from, size);
669+
}
670+
636671
template <typename Fn>
637672
static ALWAYS_INLINE auto StrtolImpl(void *ctx, Fn real, const char *nptr,
638673
char **endptr, int base)
@@ -809,6 +844,11 @@ void InitializeAsanInterceptors() {
809844
ASAN_INTERCEPT_FUNC(strncat);
810845
ASAN_INTERCEPT_FUNC(strncpy);
811846
ASAN_INTERCEPT_FUNC(strdup);
847+
848+
// Intercept wcs* functions.
849+
ASAN_INTERCEPT_FUNC(wcscpy);
850+
ASAN_INTERCEPT_FUNC(wcsncpy);
851+
812852
# if ASAN_INTERCEPT___STRDUP
813853
ASAN_INTERCEPT_FUNC(__strdup);
814854
#endif

compiler-rt/lib/asan/asan_interceptors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ DECLARE_REAL(char*, strchr, const char *str, int c)
129129
DECLARE_REAL(SIZE_T, strlen, const char *s)
130130
DECLARE_REAL(char*, strncpy, char *to, const char *from, SIZE_T size)
131131
DECLARE_REAL(SIZE_T, strnlen, const char *s, SIZE_T maxlen)
132+
DECLARE_REAL(SIZE_T, wcsnlen, const wchar_t* s, SIZE_T maxlen)
132133
DECLARE_REAL(char*, strstr, const char *s1, const char *s2)
133134

134135
# if !SANITIZER_APPLE

compiler-rt/lib/asan/asan_win_static_runtime_thunk.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ INTERCEPT_LIBRARY_FUNCTION_ASAN(strpbrk);
6363
INTERCEPT_LIBRARY_FUNCTION_ASAN(strspn);
6464
INTERCEPT_LIBRARY_FUNCTION_ASAN(strstr);
6565
INTERCEPT_LIBRARY_FUNCTION_ASAN(strtok);
66+
INTERCEPT_LIBRARY_FUNCTION_ASAN(wcscat);
67+
INTERCEPT_LIBRARY_FUNCTION_ASAN(wcscpy);
68+
INTERCEPT_LIBRARY_FUNCTION_ASAN(wcsncat);
69+
INTERCEPT_LIBRARY_FUNCTION_ASAN(wcsncpy);
6670
INTERCEPT_LIBRARY_FUNCTION_ASAN(wcslen);
6771
INTERCEPT_LIBRARY_FUNCTION_ASAN(wcsnlen);
6872

compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ SANITIZER_WEAK_IMPORT void *aligned_alloc(__sanitizer::usize __alignment,
551551
#define SANITIZER_INTERCEPT_MALLOC_USABLE_SIZE (!SI_MAC && !SI_NETBSD)
552552
#define SANITIZER_INTERCEPT_MCHECK_MPROBE SI_LINUX_NOT_ANDROID
553553
#define SANITIZER_INTERCEPT_WCSLEN 1
554-
#define SANITIZER_INTERCEPT_WCSCAT SI_POSIX
554+
#define SANITIZER_INTERCEPT_WCSCAT (SI_POSIX || SI_WINDOWS)
555555
#define SANITIZER_INTERCEPT_WCSDUP SI_POSIX
556556
#define SANITIZER_INTERCEPT_SIGNAL_AND_SIGACTION (!SI_WINDOWS && SI_NOT_FUCHSIA)
557557
#define SANITIZER_INTERCEPT_BSD_SIGNAL SI_ANDROID
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %clangxx_asan -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
2+
// RUN: %clangxx_asan -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
3+
// RUN: %clangxx_asan -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
4+
// RUN: %clangxx_asan -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
5+
6+
#include <stdio.h>
7+
#include <wchar.h>
8+
9+
int main() {
10+
wchar_t *start = L"X means ";
11+
wchar_t *append = L"dog";
12+
wchar_t goodDst[12];
13+
wcscpy(goodDst, start);
14+
wcscat(goodDst, append);
15+
16+
wchar_t badDst[9];
17+
wcscpy(badDst, start);
18+
printf("Good so far.\n");
19+
// CHECK: Good so far.
20+
fflush(stdout);
21+
wcscat(badDst, append); // Boom!
22+
// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] at pc {{0x[0-9a-f]+}} bp {{0x[0-9a-f]+}} sp {{0x[0-9a-f]+}}
23+
// CHECK: WRITE of size {{[0-9]+}} at [[ADDR:0x[0-9a-f]+]] thread T0
24+
// CHECK: #0 [[ADDR:0x[0-9a-f]+]] in wcscat{{.*}}sanitizer_common_interceptors.inc:{{[0-9]+}}
25+
printf("Should have failed with ASAN error.\n");
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %clangxx_asan -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
2+
// RUN: %clangxx_asan -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
3+
// RUN: %clangxx_asan -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
4+
// RUN: %clangxx_asan -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
5+
6+
#include <stdio.h>
7+
#include <wchar.h>
8+
9+
int main() {
10+
wchar_t *src = L"X means dog";
11+
wchar_t goodDst[12];
12+
wcscpy(goodDst, src);
13+
14+
wchar_t badDst[7];
15+
printf("Good so far.\n");
16+
// CHECK: Good so far.
17+
fflush(stdout);
18+
wcscpy(badDst, src); // Boom!
19+
// CHECK:ERROR: AddressSanitizer: stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] at pc {{0x[0-9a-f]+}} bp {{0x[0-9a-f]+}} sp {{0x[0-9a-f]+}}
20+
// CHECK: WRITE of size {{[0-9]+}} at [[ADDR:0x[0-9a-f]+]] thread T0
21+
// CHECK: #0 [[ADDR:0x[0-9a-f]+]] in wcscpy{{.*}}asan_interceptors.cpp:{{[0-9]+}}
22+
printf("Should have failed with ASAN error.\n");
23+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %clangxx_asan -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
2+
// RUN: %clangxx_asan -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
3+
// RUN: %clangxx_asan -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
4+
// RUN: %clangxx_asan -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
5+
6+
#include <stdio.h>
7+
#include <wchar.h>
8+
9+
int main() {
10+
wchar_t *start = L"X means ";
11+
wchar_t *append = L"dog";
12+
wchar_t goodDst[15];
13+
wcscpy(goodDst, start);
14+
wcsncat(goodDst, append, 5);
15+
16+
wchar_t badDst[11];
17+
wcscpy(badDst, start);
18+
wcsncat(badDst, append, 1);
19+
printf("Good so far.\n");
20+
// CHECK: Good so far.
21+
fflush(stdout);
22+
wcsncat(badDst, append, 3); // Boom!
23+
// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] at pc {{0x[0-9a-f]+}} bp {{0x[0-9a-f]+}} sp {{0x[0-9a-f]+}}
24+
// CHECK: WRITE of size {{[0-9]+}} at [[ADDR:0x[0-9a-f]+]] thread T0
25+
// CHECK: #0 [[ADDR:0x[0-9a-f]+]] in wcsncat{{.*}}sanitizer_common_interceptors.inc:{{[0-9]+}}
26+
printf("Should have failed with ASAN error.\n");
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %clangxx_asan -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
2+
// RUN: %clangxx_asan -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
3+
// RUN: %clangxx_asan -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
4+
// RUN: %clangxx_asan -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK
5+
6+
#include <stdio.h>
7+
#include <wchar.h>
8+
9+
int main() {
10+
wchar_t *src = L"X means dog";
11+
wchar_t goodDst[12];
12+
wcsncpy(goodDst, src, 12);
13+
14+
wchar_t badDst[7];
15+
wcsncpy(badDst, src, 7); // This should still work.
16+
printf("Good so far.\n");
17+
// CHECK: Good so far.
18+
fflush(stdout);
19+
20+
wcsncpy(badDst, src, 15); // Boom!
21+
// CHECK:ERROR: AddressSanitizer: stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] at pc {{0x[0-9a-f]+}} bp {{0x[0-9a-f]+}} sp {{0x[0-9a-f]+}}
22+
// CHECK: WRITE of size {{[0-9]+}} at [[ADDR:0x[0-9a-f]+]] thread T0
23+
// CHECK: #0 [[ADDR:0x[0-9a-f]+]] in wcsncpy{{.*}}asan_interceptors.cpp:{{[0-9]+}}
24+
printf("Should have failed with ASAN error.\n");
25+
}

0 commit comments

Comments
 (0)