Index: lib/sanitizer_common/sanitizer_common_interceptors.inc =================================================================== --- lib/sanitizer_common/sanitizer_common_interceptors.inc +++ lib/sanitizer_common/sanitizer_common_interceptors.inc @@ -473,6 +473,32 @@ #define INIT_STRSTR #endif +INTERCEPTOR(char*, strtok, char *str, const char *delimiters) { + void *ctx; + COMMON_INTERCEPTOR_ENTER(ctx, strtok, str, delimiters); + if (common_flags()->intercept_strtok) { + char* result; + if (str == nullptr) { + // We can only check the first argument upon the first + // invocation of a token chain since strtok saves it in a + // static buffer for subsequent invocations. + result = REAL(strtok)(str, delimiters); + } else { + uptr str_length = REAL(strlen)(str); + result = REAL(strtok)(str, delimiters); + uptr result_length = REAL(strlen)(result); + COMMON_INTERCEPTOR_READ_STRING_OF_LEN(ctx, str, str_length, + result_length+1); + } + uptr del_length = REAL(strlen)(delimiters); + COMMON_INTERCEPTOR_READ_STRING_OF_LEN(ctx, delimiters, del_length, 1); + return result; + } + return REAL(strtok)(str, delimiters); +} +#define INIT_STRTOK COMMON_INTERCEPT_FUNCTION(strtok) + + #if SANITIZER_INTERCEPT_STRCASESTR DECLARE_WEAK_INTERCEPTOR_HOOK(__sanitizer_weak_hook_strcasestr, uptr called_pc, @@ -6052,6 +6078,7 @@ INIT_STRCHRNUL; INIT_STRRCHR; INIT_STRSPN; + INIT_STRTOK; INIT_STRPBRK; INIT_MEMSET; INIT_MEMMOVE; Index: lib/sanitizer_common/sanitizer_flags.inc =================================================================== --- lib/sanitizer_common/sanitizer_flags.inc +++ lib/sanitizer_common/sanitizer_flags.inc @@ -191,6 +191,9 @@ COMMON_FLAG(bool, intercept_strspn, true, "If set, uses custom wrappers for strspn and strcspn function " "to find more errors.") +COMMON_FLAG(bool, intercept_strtok, true, + "If set, uses a custom wrapper for the strtok function " + "to find more errors.") COMMON_FLAG(bool, intercept_strpbrk, true, "If set, uses custom wrappers for strpbrk function " "to find more errors.") Index: test/asan/TestCases/strtok.c =================================================================== --- test/asan/TestCases/strtok.c +++ test/asan/TestCases/strtok.c @@ -0,0 +1,85 @@ +// Test overflows with strict_string_checks +// RUN: %clang_asan %s -o %t && %env_asan_opts=strict_string_checks=true not %run %t test1 2>&1 | FileCheck %s --check-prefix=CHECK1 +// RUN: %env_asan_opts=intercept_strtok=false:intercept_strlen=false %run %t test1 2>&1 +// RUN: %clang_asan %s -o %t && %env_asan_opts=strict_string_checks=true not %run %t test2 2>&1 | FileCheck %s --check-prefix=CHECK2 +// RUN: %env_asan_opts=intercept_strtok=false:intercept_strlen=false %run %t test2 2>&1 +// RUN: %clang_asan %s -o %t && %env_asan_opts=strict_string_checks=true not %run %t test3 2>&1 | FileCheck %s --check-prefix=CHECK3 +// RUN: %env_asan_opts=intercept_strtok=false:intercept_strlen=false %run %t test3 2>&1 + +// Test overflows with !strict_string_checks +// RUN: %clang_asan %s -o %t && %env_asan_opts=strict_string_checks=false not %run %t test4 2>&1 | FileCheck %s --check-prefix=CHECK4 +// RUN: %env_asan_opts=intercept_strtok=false:intercept_strlen=false %run %t test4 2>&1 +// RUN: %clang_asan %s -o %t && %env_asan_opts=strict_string_checks=false not %run %t test5 2>&1 | FileCheck %s --check-prefix=CHECK5 +// RUN: %env_asan_opts=intercept_strtok=false:intercept_strlen=false %run %t test5 2>&1 + + + + +#include +#include +#include + +// test overflow in delimiter +void test1() { + char *token; + char s[4] = "abc"; + char token_delimiter[2] = "b"; + __asan_poison_memory_region ((char *)&token_delimiter[1], 2); + token = strtok(s, token_delimiter); + // CHECK1:'token_delimiter' <== Memory access at offset {{[0-9]+}} partially overflows this variable + assert(strcmp(token, "a") == 0); +} + +void test2() { + char *token; + char s[4] = "abc"; + char token_delimiter[2] = "b"; + token = strtok(s, token_delimiter); + assert(strcmp(token, "a") == 0); + __asan_poison_memory_region ((char *)&token_delimiter[1], 2); + token = strtok(NULL, token_delimiter); + // CHECK2:'token_delimiter' <== Memory access at offset {{[0-9]+}} partially overflows this variable + assert(strcmp(token, "c") == 0); +} + +void test3() { + char *token; + char s[4] = "abc"; + char token_delimiter[2] = "b"; + __asan_poison_memory_region ((char *)&s[3], 2); + token = strtok(s, token_delimiter); + // CHECK3:'s' <== Memory access at offset {{[0-9]+}} partially overflows this variable + assert(token == s); +} + +void test4() { + char *token; + char s[4] = "abc"; + char token_delimiter[2] = "d"; + __asan_poison_memory_region ((char *)&s[2], 2); + __asan_poison_memory_region ((char *)&token_delimiter[1], 2); + token = strtok(s, token_delimiter); + // CHECK4:'s' <== Memory access at offset {{[0-9]+}} partially overflows this variable + assert(token == s); +} + +void test5() { + char *token; + char s[4] = "abc"; + char token_delimiter[1] = {'d'}; + __asan_poison_memory_region ((char *)&token_delimiter[1], 2); + token = strtok(s, &token_delimiter[1]); + // CHECK5:'token_delimiter' <== Memory access at offset {{[0-9]+}} overflows this variable + assert(strcmp(token, "abc") == 0); +} + + +int main(int argc, char **argv) { + if (argc != 2) return 1; + if (!strcmp(argv[1], "test1")) test1(); + if (!strcmp(argv[1], "test2")) test2(); + if (!strcmp(argv[1], "test3")) test3(); + if (!strcmp(argv[1], "test4")) test4(); + if (!strcmp(argv[1], "test5")) test5(); + return 0; +}