Index: lib/asan/asan_rtl.cc =================================================================== --- lib/asan/asan_rtl.cc +++ lib/asan/asan_rtl.cc @@ -125,6 +125,11 @@ ParseFlag(str, &f->replace_str, "replace_str", "If set, uses custom wrappers and replacements for libc string functions " "to find more errors."); + // The replace_str=false overwrites other replace_str* flags + if (!f->replace_str) { + cf->replace_strstr = false; + cf->replace_strcspn = false; + } ParseFlag(str, &f->replace_intrin, "replace_intrin", "If set, uses custom wrappers for memset/memcpy/memmove intinsics."); Index: lib/sanitizer_common/sanitizer_common_interceptors.inc =================================================================== --- lib/sanitizer_common/sanitizer_common_interceptors.inc +++ lib/sanitizer_common/sanitizer_common_interceptors.inc @@ -246,6 +246,92 @@ #define INIT_STRNCASECMP #endif +#if SANITIZER_INTERCEPT_STRSTR +INTERCEPTOR(char*, strstr, const char *s1, const char *s2) { + if COMMON_INTERCEPTOR_NOTHING_IS_INITIALIZED + return internal_strstr(s1, s2); + void *ctx; + char *r; + COMMON_INTERCEPTOR_ENTER(ctx, strstr, s1, s2); + if (!common_flags()->replace_strstr) { + r = REAL(strstr)(s1, s2); + } else { + uptr s2_len = REAL(strlen)(s2) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, s2, s2_len); + r = REAL(strstr)(s1, s2); + uptr bytes_read = r ? r - s1 + s2_len : REAL(strlen)(s1) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, s1, bytes_read); + } + return r; +} + +#define INIT_STRSTR COMMON_INTERCEPT_FUNCTION(strstr); + +INTERCEPTOR(char*, strcasestr, const char *s1, const char *s2) { + void *ctx; + char *r; + COMMON_INTERCEPTOR_ENTER(ctx, strcasestr, s1, s2); + if (!common_flags()->replace_strstr) { + r = REAL(strcasestr)(s1, s2); + } else { + uptr s2_len = REAL(strlen)(s2) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, s2, s2_len); + r = REAL(strcasestr)(s1, s2); + uptr bytes_read = r ? r - s1 + s2_len : REAL(strlen)(s1) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, s1, bytes_read); + } + return r; +} + +#define INIT_STRCASESTR COMMON_INTERCEPT_FUNCTION(strcasestr); + +#else +#define INIT_STRSTR +#define INIT_STRCASESTR +#endif + +#if SANITIZER_INTERCEPT_STRCSPN +INTERCEPTOR(SIZE_T, strcspn, const char *s1, const char *s2) { + void *ctx; + SIZE_T r; + COMMON_INTERCEPTOR_ENTER(ctx, strcspn, s1, s2); + if (!common_flags()->replace_strcspn) { + r = REAL(strcspn)(s1, s2); + } else { + uptr s2_len = REAL(strlen)(s2) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, s2, s2_len); + r = REAL(strcspn)(s1, s2); + uptr bytes_read = (r ? r : REAL(strlen)(s1)) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, s1, bytes_read); + } + return r; +} + +#define INIT_STRCSPN COMMON_INTERCEPT_FUNCTION(strcspn); + +INTERCEPTOR(char *, strpbrk, const char *s1, const char *s2) { + void *ctx; + char *r; + COMMON_INTERCEPTOR_ENTER(ctx, strpbrk, s1, s2); + if (!common_flags()->replace_strcspn) { + r = REAL(strpbrk)(s1, s2); + } else { + uptr s2_len = REAL(strlen)(s2) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, s2, s2_len); + r = REAL(strpbrk)(s1, s2); + uptr bytes_read = (r ? r - s1 : REAL(strlen)(s1)) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, s1, bytes_read); + } + return r; +} + +#define INIT_STRPBRK COMMON_INTERCEPT_FUNCTION(strpbrk); + +#else +#define INIT_STRCSPN +#define INIT_STRPBRK +#endif + #if SANITIZER_INTERCEPT_MEMCHR INTERCEPTOR(void*, memchr, const void *s, int c, SIZE_T n) { void *ctx; @@ -4738,6 +4824,10 @@ INIT_STRNCMP; INIT_STRCASECMP; INIT_STRNCASECMP; + INIT_STRSTR; + INIT_STRCASESTR; + INIT_STRCSPN; + INIT_STRPBRK; INIT_MEMCHR; INIT_MEMRCHR; INIT_READ; Index: lib/sanitizer_common/sanitizer_flags.h =================================================================== --- lib/sanitizer_common/sanitizer_flags.h +++ lib/sanitizer_common/sanitizer_flags.h @@ -60,6 +60,8 @@ const char *suppressions; bool print_suppressions; bool disable_coredump; + bool replace_strstr; + bool replace_strcspn; }; inline CommonFlags *common_flags() { Index: lib/sanitizer_common/sanitizer_flags.cc =================================================================== --- lib/sanitizer_common/sanitizer_flags.cc +++ lib/sanitizer_common/sanitizer_flags.cc @@ -66,6 +66,8 @@ f->suppressions = ""; f->print_suppressions = true; f->disable_coredump = (SANITIZER_WORDSIZE == 64); + f->replace_strstr = true; + f->replace_strcspn = true; } void ParseCommonFlagsFromString(CommonFlags *f, const char *str) { @@ -154,6 +156,10 @@ "Disable core dumping. By default, disable_core=1 on 64-bit to avoid " "dumping a 16T+ core file. Ignored on OSes that don't dump core by" "default and for sanitizers that don't reserve lots of virtual memory."); + ParseFlag(str, &f->replace_strstr, "replace_strstr", "If set, uses custom " + "wrappers for strstr and strcasestr functions to find more errors."); + ParseFlag(str, &f->replace_strcspn, "replace_strcspn", "If set, uses custom " + "wrappers for strcspn and strpbrk functions to find more errors."); // Do a sanity check for certain flags. if (f->malloc_context_size < 1) Index: lib/sanitizer_common/sanitizer_platform_interceptors.h =================================================================== --- lib/sanitizer_common/sanitizer_platform_interceptors.h +++ lib/sanitizer_common/sanitizer_platform_interceptors.h @@ -54,6 +54,10 @@ #endif #define SANITIZER_INTERCEPT_STRCMP 1 +#define SANITIZER_INTERCEPT_STRSTR 1 +#define SANITIZER_INTERCEPT_STRCASESTR SI_NOT_WINDOWS +#define SANITIZER_INTERCEPT_STRCSPN 1 +#define SANITIZER_INTERCEPT_STRPBRK 1 #define SANITIZER_INTERCEPT_TEXTDOMAIN SI_LINUX_NOT_ANDROID #define SANITIZER_INTERCEPT_STRCASECMP SI_NOT_WINDOWS #define SANITIZER_INTERCEPT_MEMCHR 1 Index: lib/tsan/rtl/tsan_interceptors.cc =================================================================== --- lib/tsan/rtl/tsan_interceptors.cc +++ lib/tsan/rtl/tsan_interceptors.cc @@ -709,16 +709,6 @@ return REAL(strncpy)(dst, src, n); } -TSAN_INTERCEPTOR(const char*, strstr, const char *s1, const char *s2) { - SCOPED_TSAN_INTERCEPTOR(strstr, s1, s2); - const char *res = REAL(strstr)(s1, s2); - uptr len1 = internal_strlen(s1); - uptr len2 = internal_strlen(s2); - MemoryAccessRange(thr, pc, (uptr)s1, len1 + 1, false); - MemoryAccessRange(thr, pc, (uptr)s2, len2 + 1, false); - return res; -} - TSAN_INTERCEPTOR(char*, strdup, const char *str) { SCOPED_TSAN_INTERCEPTOR(strdup, str); // strdup will call malloc, so no instrumentation is required here. @@ -2292,7 +2282,6 @@ TSAN_INTERCEPT(strrchr); TSAN_INTERCEPT(strcpy); // NOLINT TSAN_INTERCEPT(strncpy); - TSAN_INTERCEPT(strstr); TSAN_INTERCEPT(strdup); TSAN_INTERCEPT(pthread_create); Index: test/asan/TestCases/strcasestr-1.c =================================================================== --- /dev/null +++ test/asan/TestCases/strcasestr-1.c @@ -0,0 +1,23 @@ +// Test haystack overflow in strcasestr function +// RUN: %clang_asan %s -o %t && not %run %t 2>&1 | FileCheck %s + +// Test replace_strstr asan option +// RUN: ASAN_OPTIONS=replace_strstr=false %run %t 2>&1 + +// There's no interceptor for strcasestr on Windows +// XFAIL: win32 + +#define _GNU_SOURCE +#include +#include + +int main(int argc, char **argv) { + char *r = 0; + char s2[] = "c"; + char s1[] = {'a', 'b'}; + char s3[] = {'\n'}; + r = strcasestr(s1, s2); + // CHECK:'s{{[1|3]}}' <== Memory access at offset {{[0-9]+ .*}}flows this variable + assert(r == 0); + return 0; +} Index: test/asan/TestCases/strcasestr-2.c =================================================================== --- /dev/null +++ test/asan/TestCases/strcasestr-2.c @@ -0,0 +1,23 @@ +// Test needle overflow in strcasestr function +// RUN: %clang_asan %s -o %t && not %run %t 2>&1 | FileCheck %s + +// Test replace_strstr asan option +// RUN: ASAN_OPTIONS=replace_strstr=false %run %t 2>&1 + +// There's no interceptor for strcasestr on Windows +// XFAIL: win32 + +#define _GNU_SOURCE +#include +#include + +int main(int argc, char **argv) { + char *r = 0; + char s1[] = "ab"; + char s2[] = {'c'}; + char s3[] = {'\n'}; + r = strcasestr(s1, s2); + assert(r == 0); + // CHECK:'s{{[2|3]}}' <== Memory access at offset {{[0-9]+ .*}}flows this variable + return 0; +} Index: test/asan/TestCases/strcspn-1.c =================================================================== --- /dev/null +++ test/asan/TestCases/strcspn-1.c @@ -0,0 +1,19 @@ +// Test string s1 overflow in strcspn function +// RUN: %clang_asan %s -o %t && not %run %t 2>&1 | FileCheck %s + +// Test replace_strcspn asan option +// RUN: ASAN_OPTIONS=replace_strcspn=false %run %t 2>&1 + +#include +#include + +int main(int argc, char **argv) { + size_t r; + char s2[] = "ab"; + char s1[] = {'c', 'd'}; + char s3[] = {'\n'}; + r = strcspn(s1, s2); + // CHECK:'s{{[1|3]}}' <== Memory access at offset {{[0-9]+ .*}}flows this variable + assert(r >= sizeof(s1)); + return 0; +} Index: test/asan/TestCases/strcspn-2.c =================================================================== --- /dev/null +++ test/asan/TestCases/strcspn-2.c @@ -0,0 +1,19 @@ +// Test stopset overflow in strcspn function +// RUN: %clang_asan %s -o %t && not %run %t 2>&1 | FileCheck %s + +// Test replace_strcspn asan option +// RUN: ASAN_OPTIONS=replace_strcspn=false %run %t 2>&1 + +#include +#include + +int main(int argc, char **argv) { + size_t r; + char s1[] = "ab"; + char s2[] = {'c', 'd'}; + char s3[] = {'\n'}; + r = strcspn(s1, s2); + // CHECK:'s{{[2|3]}}' <== Memory access at offset {{[0-9]+ .*}}flows this variable + assert(r == sizeof(s1) - 1); + return 0; +} Index: test/asan/TestCases/strpbrk-1.c =================================================================== --- /dev/null +++ test/asan/TestCases/strpbrk-1.c @@ -0,0 +1,19 @@ +// Test string s1 overflow in strpbrk function +// RUN: %clang_asan %s -o %t && not %run %t 2>&1 | FileCheck %s + +// Test replace_strcspn asan option +// RUN: ASAN_OPTIONS=replace_strcspn=false %run %t 2>&1 + +#include +#include + +int main(int argc, char **argv) { + char *r; + char s2[] = "ab"; + char s1[] = {'c', 'd'}; + char s3[] = {'\n'}; + r = strpbrk(s1, s2); + // CHECK:'s{{[1|3]}}' <== Memory access at offset {{[0-9]+ .*}}flows this variable + assert(r == 0); + return 0; +} Index: test/asan/TestCases/strpbrk-2.c =================================================================== --- /dev/null +++ test/asan/TestCases/strpbrk-2.c @@ -0,0 +1,19 @@ +// Test stopset overflow in strpbrk function +// RUN: %clang_asan %s -o %t && not %run %t 2>&1 | FileCheck %s + +// Test replace_strcspn asan option +// RUN: ASAN_OPTIONS=replace_strcspn=false %run %t 2>&1 + +#include +#include + +int main(int argc, char **argv) { + char *r; + char s1[] = "ab"; + char s2[] = {'c', 'd'}; + char s3[] = {'\n'}; + r = strpbrk(s1, s2); + // CHECK:'s{{[2|3]}}' <== Memory access at offset {{[0-9]+ .*}}flows this variable + assert(r == 0); + return 0; +} Index: test/asan/TestCases/strstr-1.c =================================================================== --- /dev/null +++ test/asan/TestCases/strstr-1.c @@ -0,0 +1,19 @@ +// Test haystack overflow in strstr function +// RUN: %clang_asan %s -o %t && not %run %t 2>&1 | FileCheck %s + +// Test replace_strstr asan option +// RUN: ASAN_OPTIONS=replace_strstr=false %run %t 2>&1 + +#include +#include + +int main(int argc, char **argv) { + char *r = 0; + char s2[] = "c"; + char s1[] = {'a', 'b'}; + char s3[] = {'\n'}; + r = strstr(s1, s2); + // CHECK:'s{{[1|3]}}' <== Memory access at offset {{[0-9]+ .*}}flows this variable + assert(r == 0); + return 0; +} Index: test/asan/TestCases/strstr-2.c =================================================================== --- /dev/null +++ test/asan/TestCases/strstr-2.c @@ -0,0 +1,19 @@ +// Test needle overflow in strstr function +// RUN: %clang_asan %s -o %t && not %run %t 2>&1 | FileCheck %s + +// Test replace_strstr asan option +// RUN: ASAN_OPTIONS=replace_strstr=false %run %t 2>&1 + +#include +#include + +int main(int argc, char **argv) { + char *r = 0; + char s1[] = "ab"; + char s2[] = {'c'}; + char s3[] = {'\n'}; + r = strstr(s1, s2); + // CHECK:'s{{[2|3]}}' <== Memory access at offset {{[0-9]+ .*}}flows this variable + assert(r == 0); + return 0; +} Index: test/sanitizer_common/TestCases/strcasestr.c =================================================================== --- /dev/null +++ test/sanitizer_common/TestCases/strcasestr.c @@ -0,0 +1,16 @@ +// RUN: %clang %s -o %t && %run %t 2>&1 + +// There's no interceptor for strcasestr on Windows +// XFAIL: win32 + +#define _GNU_SOURCE +#include +#include +int main(int argc, char **argv) { + char *r = 0; + char s1[] = "aB"; + char s2[] = "b"; + r = strcasestr(s1, s2); + assert(r == s1 + 1); + return 0; +} Index: test/sanitizer_common/TestCases/strcspn.c =================================================================== --- /dev/null +++ test/sanitizer_common/TestCases/strcspn.c @@ -0,0 +1,13 @@ +// RUN: %clang %s -o %t && %run %t 2>&1 + +#include +#include + +int main(int argc, char **argv) { + size_t r; + char s1[] = "ad"; + char s2[] = "cd"; + r = strcspn(s1, s2); + assert(r == 1); + return 0; +} Index: test/sanitizer_common/TestCases/strpbrk.c =================================================================== --- /dev/null +++ test/sanitizer_common/TestCases/strpbrk.c @@ -0,0 +1,13 @@ +// RUN: %clang %s -o %t && %run %t 2>&1 + +#include +#include + +int main(int argc, char **argv) { + char *r = 0; + char s1[] = "ad"; + char s2[] = "cd"; + r = strpbrk(s1, s2); + assert(r == s1 + 1); + return 0; +} Index: test/sanitizer_common/TestCases/strstr.c =================================================================== --- /dev/null +++ test/sanitizer_common/TestCases/strstr.c @@ -0,0 +1,12 @@ +// RUN: %clang %s -o %t && %run %t 2>&1 + +#include +#include +int main(int argc, char **argv) { + char *r = 0; + char s1[] = "ab"; + char s2[] = "b"; + r = strstr(s1, s2); + assert(r == s1 + 1); + return 0; +}