diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -74,6 +74,7 @@ !Sanitizers.has(SanitizerKind::Address) && !Sanitizers.has(SanitizerKind::HWAddress); } + bool needsFuzzerInterceptors() const; bool needsUbsanRt() const; bool requiresMinimalRuntime() const { return MinimalRuntime; } bool needsDfsanRt() const { return Sanitizers.has(SanitizerKind::DataFlow); } diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -240,6 +240,10 @@ return TrappingKinds; } +bool SanitizerArgs::needsFuzzerInterceptors() const { + return needsFuzzer() && !needsAsanRt() && !needsTsanRt() && !needsMsanRt(); +} + bool SanitizerArgs::needsUbsanRt() const { // All of these include ubsan. if (needsAsanRt() || needsMsanRt() || needsHwasanRt() || needsTsanRt() || diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp --- a/clang/lib/Driver/ToolChains/CommonArgs.cpp +++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp @@ -784,6 +784,9 @@ !Args.hasArg(options::OPT_shared)) { addSanitizerRuntime(TC, Args, CmdArgs, "fuzzer", false, true); + if (SanArgs.needsFuzzerInterceptors()) + addSanitizerRuntime(TC, Args, CmdArgs, "fuzzer_interceptors", false, + true); if (!Args.hasArg(clang::driver::options::OPT_nostdlibxx)) TC.AddCXXStdlibLibArgs(Args, CmdArgs); } diff --git a/compiler-rt/lib/fuzzer/CMakeLists.txt b/compiler-rt/lib/fuzzer/CMakeLists.txt --- a/compiler-rt/lib/fuzzer/CMakeLists.txt +++ b/compiler-rt/lib/fuzzer/CMakeLists.txt @@ -99,6 +99,13 @@ CFLAGS ${LIBFUZZER_CFLAGS} DEPS ${LIBFUZZER_DEPS}) +add_compiler_rt_object_libraries(RTfuzzer_interceptors + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + SOURCES FuzzerInterceptors.cpp + CFLAGS ${LIBFUZZER_CFLAGS} + DEPS ${LIBFUZZER_DEPS}) + add_compiler_rt_runtime(clang_rt.fuzzer STATIC OS ${FUZZER_SUPPORTED_OS} @@ -115,6 +122,14 @@ CFLAGS ${LIBFUZZER_CFLAGS} PARENT_TARGET fuzzer) +add_compiler_rt_runtime(clang_rt.fuzzer_interceptors + STATIC + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + OBJECT_LIBS RTfuzzer_interceptors + CFLAGS ${LIBFUZZER_CFLAGS} + PARENT_TARGET fuzzer) + if(OS_NAME MATCHES "Linux|Fuchsia" AND COMPILER_RT_LIBCXX_PATH AND COMPILER_RT_LIBCXXABI_PATH) @@ -148,7 +163,10 @@ add_dependencies(RTfuzzer.${arch} libcxx_fuzzer_${arch}-build) target_compile_options(RTfuzzer_main.${arch} PRIVATE -isystem ${LIBCXX_${arch}_PREFIX}/include/c++/v1) add_dependencies(RTfuzzer_main.${arch} libcxx_fuzzer_${arch}-build) + target_compile_options(RTfuzzer_interceptors.${arch} PRIVATE -isystem ${LIBCXX_${arch}_PREFIX}/include/c++/v1) + add_dependencies(RTfuzzer_interceptors.${arch} libcxx_fuzzer_${arch}-build) partially_link_libcxx(fuzzer_no_main ${LIBCXX_${arch}_PREFIX} ${arch}) + partially_link_libcxx(fuzzer_interceptors ${LIBCXX_${arch}_PREFIX} ${arch}) partially_link_libcxx(fuzzer ${LIBCXX_${arch}_PREFIX} ${arch}) endforeach() endif() diff --git a/compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp b/compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/fuzzer/FuzzerInterceptors.cpp @@ -0,0 +1,224 @@ +//===-- FuzzerInterceptors.cpp --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Intercept certain libc functions to aid fuzzing. +// Linked only when other RTs that define their own interceptors are not linked. +//===----------------------------------------------------------------------===// + +#include "FuzzerPlatform.h" + +#if LIBFUZZER_LINUX + +#define GET_CALLER_PC() __builtin_return_address(0) + +#define PTR_TO_REAL(x) real_##x +#define REAL(x) __interception::PTR_TO_REAL(x) +#define FUNC_TYPE(x) x##_type +#define DEFINE_REAL(ret_type, func, ...) \ + typedef ret_type (*FUNC_TYPE(func))(__VA_ARGS__); \ + namespace __interception { \ + FUNC_TYPE(func) PTR_TO_REAL(func); \ + } + +#include +#include +#include // for dlsym() +#include + +static void *getFuncAddr(const char *name, uintptr_t wrapper_addr) { + void *addr = dlsym(RTLD_NEXT, name); + if (!addr) { + // If the lookup using RTLD_NEXT failed, the sanitizer runtime library is + // later in the library search order than the DSO that we are trying to + // intercept, which means that we cannot intercept this function. We still + // want the address of the real definition, though, so look it up using + // RTLD_DEFAULT. + addr = dlsym(RTLD_DEFAULT, name); + + // In case `name' is not loaded, dlsym ends up finding the actual wrapper. + // We don't want to intercept the wrapper and have it point to itself. + if (reinterpret_cast(addr) == wrapper_addr) + addr = nullptr; + } + return addr; +} + +static int FuzzerInited = 0; +static bool FuzzerInitIsRunning; + +static void fuzzerInit(); + +static void ensureFuzzerInited() { + assert(!FuzzerInitIsRunning); + if (!FuzzerInited) { + fuzzerInit(); + } +} + +static int internal_strcmp_strncmp(const char *s1, const char *s2, bool strncmp, + size_t n) { + size_t i = 0; + while (true) { + if (strncmp) { + if (i == n) + break; + i++; + } + unsigned c1 = *s1; + unsigned c2 = *s2; + if (c1 != c2) + return (c1 < c2) ? -1 : 1; + if (c1 == 0) + break; + s1++; + s2++; + } + return 0; +} + +static int internal_strncmp(const char *s1, const char *s2, size_t n) { + return internal_strcmp_strncmp(s1, s2, true, n); +} + +static int internal_strcmp(const char *s1, const char *s2) { + return internal_strcmp_strncmp(s1, s2, false, 0); +} + +static int internal_memcmp(const void *s1, const void *s2, size_t n) { + const uint8_t *t1 = static_cast(s1); + const uint8_t *t2 = static_cast(s2); + for (size_t i = 0; i < n; ++i, ++t1, ++t2) + if (*t1 != *t2) + return *t1 < *t2 ? -1 : 1; + return 0; +} + +static size_t internal_strlen(const char *s) { + size_t i = 0; + while (s[i]) + i++; + return i; +} + +static char *internal_strstr(const char *haystack, const char *needle) { + // This is O(N^2), but we are not using it in hot places. + size_t len1 = internal_strlen(haystack); + size_t len2 = internal_strlen(needle); + if (len1 < len2) + return nullptr; + for (size_t pos = 0; pos <= len1 - len2; pos++) { + if (internal_memcmp(haystack + pos, needle, len2) == 0) + return const_cast(haystack) + pos; + } + return nullptr; +} + +extern "C" { + +DEFINE_REAL(int, memcmp, const void *, const void *, size_t) +DEFINE_REAL(int, strncmp, const char *, const char *, size_t) +DEFINE_REAL(int, strcmp, const char *, const char *) +DEFINE_REAL(int, strncasecmp, const char *, const char *, size_t) +DEFINE_REAL(int, strcasecmp, const char *, const char *) +DEFINE_REAL(char *, strstr, const char *, const char *) +DEFINE_REAL(char *, strcasestr, const char *, const char *) +DEFINE_REAL(void *, memmem, const void *, size_t, const void *, size_t) + +ATTRIBUTE_INTERFACE int memcmp(const void *s1, const void *s2, size_t n) { + if (!FuzzerInited) + return internal_memcmp(s1, s2, n); + int result = REAL(memcmp)(s1, s2, n); + __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strncmp(const char *s1, const char *s2, size_t n) { + if (!FuzzerInited) + return internal_strncmp(s1, s2, n); + int result = REAL(strncmp)(s1, s2, n); + __sanitizer_weak_hook_strncmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strcmp(const char *s1, const char *s2) { + if (!FuzzerInited) + return internal_strcmp(s1, s2); + int result = REAL(strcmp)(s1, s2); + __sanitizer_weak_hook_strcmp(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE int strncasecmp(const char *s1, const char *s2, size_t n) { + ensureFuzzerInited(); + int result = REAL(strncasecmp)(s1, s2, n); + __sanitizer_weak_hook_strncasecmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strcasecmp(const char *s1, const char *s2) { + ensureFuzzerInited(); + int result = REAL(strcasecmp)(s1, s2); + __sanitizer_weak_hook_strcasecmp(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE char *strstr(const char *s1, const char *s2) { + if (!FuzzerInited) + return internal_strstr(s1, s2); + char *result = REAL(strstr)(s1, s2); + __sanitizer_weak_hook_strstr(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE char *strcasestr(const char *s1, const char *s2) { + ensureFuzzerInited(); + char *result = REAL(strcasestr)(s1, s2); + __sanitizer_weak_hook_strcasestr(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE +void *memmem(const void *s1, size_t len1, const void *s2, size_t len2) { + ensureFuzzerInited(); + void *result = REAL(memmem)(s1, len1, s2, len2); + __sanitizer_weak_hook_memmem(GET_CALLER_PC(), s1, len1, s2, len2, result); + return result; +} + +__attribute__((section(".preinit_array"), + used)) static void (*__local_fuzzer_preinit)(void) = fuzzerInit; + +} // extern "C" + +static void fuzzerInit() { + assert(!FuzzerInitIsRunning); + if (FuzzerInited) + return; + FuzzerInitIsRunning = true; + + REAL(memcmp) = reinterpret_cast( + getFuncAddr("memcmp", reinterpret_cast(&memcmp))); + REAL(strncmp) = reinterpret_cast( + getFuncAddr("strncmp", reinterpret_cast(&strncmp))); + REAL(strcmp) = reinterpret_cast( + getFuncAddr("strcmp", reinterpret_cast(&strcmp))); + REAL(strncasecmp) = reinterpret_cast( + getFuncAddr("strncasecmp", reinterpret_cast(&strncasecmp))); + REAL(strcasecmp) = reinterpret_cast( + getFuncAddr("strcasecmp", reinterpret_cast(&strcasecmp))); + REAL(strstr) = reinterpret_cast( + getFuncAddr("strstr", reinterpret_cast(&strstr))); + REAL(strcasestr) = reinterpret_cast( + getFuncAddr("strcasestr", reinterpret_cast(&strcasestr))); + REAL(memmem) = reinterpret_cast( + getFuncAddr("memmem", reinterpret_cast(&memmem))); + + FuzzerInitIsRunning = false; + FuzzerInited = 1; +} + +#endif diff --git a/compiler-rt/test/fuzzer/CustomAllocator.cpp b/compiler-rt/test/fuzzer/CustomAllocator.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/fuzzer/CustomAllocator.cpp @@ -0,0 +1,46 @@ +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Test whether calling certain libFuzzer's interceptors inside allocators +// does not cause an assertion failure. +#include +#include +#include +#include +#include +#include +#include + +static const char *buf1 = "aaaa"; +static const char *buf2 = "bbbb"; + +static void callFuzzerInterceptors(const char *prefix) { + int memcmp_result = memcmp(buf1, buf2, 4); + if (memcmp_result != 0) { + fprintf(stderr, "%s-MEMCMP\n", prefix); + } + int strncmp_result = strncmp(buf1, buf2, 4); + if (strncmp_result != 0) { + fprintf(stderr, "%s-STRNCMP\n", prefix); + } + int strcmp_result = strcmp(buf1, buf2); + if (strcmp_result != 0) { + fprintf(stderr, "%s-STRCMP\n", prefix); + } + const char *strstr_result = strstr(buf1, buf2); + if (strstr_result == nullptr) { + fprintf(stderr, "%s-STRSTR\n", prefix); + } +} + +extern "C" void *__libc_calloc(size_t, size_t); + +extern "C" void *calloc(size_t n, size_t elem_size) { + static bool CalledOnce = false; + if (!CalledOnce) { + callFuzzerInterceptors("CALLOC"); + CalledOnce = true; + } + return __libc_calloc(n, elem_size); +} diff --git a/compiler-rt/test/fuzzer/custom-allocator.test b/compiler-rt/test/fuzzer/custom-allocator.test new file mode 100644 --- /dev/null +++ b/compiler-rt/test/fuzzer/custom-allocator.test @@ -0,0 +1,9 @@ +UNSUPPORTED: freebsd +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin %S/CustomAllocator.cpp %S/EmptyTest.cpp -o %t-NoAsanCustomAllocatorTest + +RUN: %run %t-NoAsanCustomAllocatorTest -runs=1 2>&1 | FileCheck %s + +CHECK: CALLOC-MEMCMP +CHECK-NEXT: CALLOC-STRNCMP +CHECK-NEXT: CALLOC-STRCMP +CHECK-NEXT: CALLOC-STRSTR diff --git a/compiler-rt/test/fuzzer/memcmp.test b/compiler-rt/test/fuzzer/memcmp.test --- a/compiler-rt/test/fuzzer/memcmp.test +++ b/compiler-rt/test/fuzzer/memcmp.test @@ -1,4 +1,11 @@ UNSUPPORTED: freebsd RUN: %cpp_compiler %S/MemcmpTest.cpp -o %t-MemcmpTest RUN: not %run %t-MemcmpTest -seed=1 -runs=10000000 2>&1 | FileCheck %s + +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-memcmp %S/MemcmpTest.cpp -o %t-NoAsanMemcmpTest +RUN: not %run %t-NoAsanMemcmpTest -seed=1 -runs=10000000 2>&1 | FileCheck %s + +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-calloc -fno-builtin-memcmp %S/CustomAllocator.cpp %S/MemcmpTest.cpp -o %t-NoAsanCustomAllocatorMemcmpTest +RUN: not %run %t-NoAsanCustomAllocatorMemcmpTest -seed=1 -runs=10000000 2>&1 | FileCheck %s + CHECK: BINGO diff --git a/compiler-rt/test/fuzzer/memcmp64.test b/compiler-rt/test/fuzzer/memcmp64.test --- a/compiler-rt/test/fuzzer/memcmp64.test +++ b/compiler-rt/test/fuzzer/memcmp64.test @@ -1,4 +1,8 @@ UNSUPPORTED: freebsd RUN: %cpp_compiler %S/Memcmp64BytesTest.cpp -o %t-Memcmp64BytesTest RUN: not %run %t-Memcmp64BytesTest -seed=1 -runs=1000000 2>&1 | FileCheck %s + +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-memcmp %S/Memcmp64BytesTest.cpp -o %t-NoAsanMemcmp64BytesTest +RUN: not %run %t-NoAsanMemcmp64BytesTest -seed=1 -runs=1000000 2>&1 | FileCheck %s + CHECK: BINGO diff --git a/compiler-rt/test/fuzzer/strcmp.test b/compiler-rt/test/fuzzer/strcmp.test --- a/compiler-rt/test/fuzzer/strcmp.test +++ b/compiler-rt/test/fuzzer/strcmp.test @@ -1,5 +1,11 @@ UNSUPPORTED: freebsd RUN: %cpp_compiler %S/StrcmpTest.cpp -o %t-StrcmpTest RUN: not %run %t-StrcmpTest -seed=1 -runs=2000000 2>&1 | FileCheck %s -CHECK: BINGO +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-strcmp %S/StrcmpTest.cpp -o %t-NoAsanStrcmpTest +RUN: not %run %t-NoAsanStrcmpTest -seed=1 -runs=2000000 2>&1 | FileCheck %s + +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-calloc -fno-builtin-strcmp %S/CustomAllocator.cpp %S/StrcmpTest.cpp -o %t-NoAsanCustomAllocatorStrcmpTest +RUN: not %run %t-NoAsanCustomAllocatorStrcmpTest -seed=1 -runs=2000000 2>&1 | FileCheck %s + +CHECK: BINGO diff --git a/compiler-rt/test/fuzzer/strncmp.test b/compiler-rt/test/fuzzer/strncmp.test --- a/compiler-rt/test/fuzzer/strncmp.test +++ b/compiler-rt/test/fuzzer/strncmp.test @@ -1,5 +1,11 @@ UNSUPPORTED: freebsd RUN: %cpp_compiler %S/StrncmpTest.cpp -o %t-StrncmpTest RUN: not %run %t-StrncmpTest -seed=2 -runs=10000000 2>&1 | FileCheck %s -CHECK: BINGO +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-strncmp %S/StrncmpTest.cpp -o %t-NoAsanStrncmpTest +RUN: not %run %t-NoAsanStrncmpTest -seed=2 -runs=10000000 2>&1 | FileCheck %s + +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-calloc -fno-builtin-strncmp %S/CustomAllocator.cpp %S/StrncmpTest.cpp -o %t-NoAsanCustomAllocatorStrncmpTest +RUN: not %run %t-NoAsanCustomAllocatorStrncmpTest -seed=2 -runs=10000000 2>&1 | FileCheck %s + +CHECK: BINGO diff --git a/compiler-rt/test/fuzzer/strstr.test b/compiler-rt/test/fuzzer/strstr.test --- a/compiler-rt/test/fuzzer/strstr.test +++ b/compiler-rt/test/fuzzer/strstr.test @@ -1,5 +1,11 @@ UNSUPPORTED: freebsd RUN: %cpp_compiler %S/StrstrTest.cpp -o %t-StrstrTest RUN: not %run %t-StrstrTest -seed=1 -runs=2000000 2>&1 | FileCheck %s -CHECK: BINGO +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-strstr %S/StrstrTest.cpp -o %t-NoAsanStrstrTest +RUN: not %run %t-NoAsanStrstrTest -seed=1 -runs=2000000 2>&1 | FileCheck %s + +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-calloc -fno-builtin-strstr %S/CustomAllocator.cpp %S/StrstrTest.cpp -o %t-NoAsanCustomAllocatorStrstrTest +RUN: not %run %t-NoAsanCustomAllocatorStrstrTest -seed=1 -runs=2000000 2>&1 | FileCheck %s + +CHECK: BINGO