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 @@ -238,6 +238,11 @@ return TrappingKinds; } +bool SanitizerArgs::needsFuzzerInterceptors() const { + return needsFuzzer() && !needsAsanRt() && !needsHwasanRt() && + !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 @@ -779,6 +779,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,161 @@ +//===-- 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 "FuzzerBuiltins.h" + +#if LIBFUZZER_LINUX + +#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); \ + } + +typedef unsigned long uptr; + +#include // for dlsym() + +static void *GetFuncAddr(const char *name, uptr 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 ((uptr)addr == wrapper_addr) + addr = nullptr; + } + return addr; +} + +extern "C" { + +// NOLINTNEXTLINE +void __sanitizer_weak_hook_memcmp(void *, const void *, const void *, size_t, + int); +// NOLINTNEXTLINE +void __sanitizer_weak_hook_strncmp(void *, const char *, const char *, size_t, + int); +// NOLINTNEXTLINE +void __sanitizer_weak_hook_strcmp(void *, const char *, const char *, int); +// NOLINTNEXTLINE +void __sanitizer_weak_hook_strncasecmp(void *, const char *, const char *, + size_t, int); +// NOLINTNEXTLINE +void __sanitizer_weak_hook_strcasecmp(void *, const char *, const char *, int); +// NOLINTNEXTLINE +void __sanitizer_weak_hook_strstr(void *, const char *, const char *, char *); +// NOLINTNEXTLINE +void __sanitizer_weak_hook_strcasestr(void *, const char *, const char *, + char *); +// NOLINTNEXTLINE +void __sanitizer_weak_hook_memmem(void *, const void *, size_t, const void *, + size_t, void *); + +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, char *, const char *) +DEFINE_REAL(char *, strcasestr, 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) { + 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) { + 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) { + 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) { + 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) { + int result = REAL(strcasecmp)(s1, s2); + __sanitizer_weak_hook_strcasecmp(GET_CALLER_PC(), s1, s2, result); + + return result; +} + +extern "C++" ATTRIBUTE_INTERFACE char *strstr(char *s1, const char *s2) { + char *result = REAL(strstr)(s1, s2); + __sanitizer_weak_hook_strstr(GET_CALLER_PC(), s1, s2, result); + + return result; +} + +extern "C++" ATTRIBUTE_INTERFACE char *strcasestr(char *s1, const char *s2) { + 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) { + void *result = REAL(memmem)(s1, len1, s2, len2); + __sanitizer_weak_hook_memmem(GET_CALLER_PC(), s1, len1, s2, len2, result); + + return result; +} + +static void __fuzzer_init() { + REAL(memcmp) = + reinterpret_cast(GetFuncAddr("memcmp", (uptr)&memcmp)); + REAL(strncmp) = + reinterpret_cast(GetFuncAddr("strncmp", (uptr)&strncmp)); + REAL(strcmp) = + reinterpret_cast(GetFuncAddr("strcmp", (uptr)&strcmp)); + REAL(strncasecmp) = reinterpret_cast( + GetFuncAddr("strncasecmp", (uptr)&strncasecmp)); + REAL(strcasecmp) = reinterpret_cast( + GetFuncAddr("strcasecmp", (uptr)&strcasecmp)); + REAL(strstr) = reinterpret_cast( + GetFuncAddr("strstr", (uptr)(strstr_type)&strstr)); + REAL(strcasestr) = reinterpret_cast( + GetFuncAddr("strcasestr", (uptr)(strcasestr_type)&strcasestr)); + REAL(memmem) = + reinterpret_cast(GetFuncAddr("memmem", (uptr)&memmem)); +} + +__attribute__((section(".preinit_array"), used)) static void ( + *__local_fuzzer_preinit)(void) = __fuzzer_init; +} + +#endif diff --git a/compiler-rt/test/fuzzer/no-asan-memcmp.test b/compiler-rt/test/fuzzer/no-asan-memcmp.test new file mode 100644 --- /dev/null +++ b/compiler-rt/test/fuzzer/no-asan-memcmp.test @@ -0,0 +1,4 @@ +UNSUPPORTED: freebsd +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-memcmp %S/MemcmpTest.cpp -o %t-MemcmpTest +RUN: not %run %t-MemcmpTest -seed=1 -runs=10000000 2>&1 | FileCheck %s +CHECK: BINGO diff --git a/compiler-rt/test/fuzzer/no-asan-strcmp.test b/compiler-rt/test/fuzzer/no-asan-strcmp.test new file mode 100644 --- /dev/null +++ b/compiler-rt/test/fuzzer/no-asan-strcmp.test @@ -0,0 +1,5 @@ +UNSUPPORTED: freebsd +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-strcmp %S/StrcmpTest.cpp -o %t-StrcmpTest +RUN: not %run %t-StrcmpTest -seed=1 -runs=2000000 2>&1 | FileCheck %s +CHECK: BINGO + diff --git a/compiler-rt/test/fuzzer/no-asan-strncmp.test b/compiler-rt/test/fuzzer/no-asan-strncmp.test new file mode 100644 --- /dev/null +++ b/compiler-rt/test/fuzzer/no-asan-strncmp.test @@ -0,0 +1,4 @@ +UNSUPPORTED: freebsd +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-strncmp %S/StrncmpTest.cpp -o %t-StrncmpTest +RUN: not %run %t-StrncmpTest -seed=2 -runs=10000000 2>&1 | FileCheck %s +CHECK: BINGO diff --git a/compiler-rt/test/fuzzer/no-asan-strstr.test b/compiler-rt/test/fuzzer/no-asan-strstr.test new file mode 100644 --- /dev/null +++ b/compiler-rt/test/fuzzer/no-asan-strstr.test @@ -0,0 +1,5 @@ +UNSUPPORTED: freebsd +RUN: %cpp_compiler -fno-sanitize=address -fno-builtin-strstr %S/StrstrTest.cpp -o %t-StrstrTest +RUN: not %run %t-StrstrTest -seed=1 -runs=2000000 2>&1 | FileCheck %s +CHECK: BINGO +