diff --git a/clang/docs/AddressSanitizer.rst b/clang/docs/AddressSanitizer.rst index 2c2131b01d361..ed59afa1e0af5 100644 --- a/clang/docs/AddressSanitizer.rst +++ b/clang/docs/AddressSanitizer.rst @@ -164,6 +164,18 @@ To summarize: ``-fsanitize-address-use-after-return=`` * ``always``: Enables detection of UAR errors in all cases. (reduces code size, but not as much as ``never``). +Container Overflow Detection +---------------------------- + +AddressSanitizer can detect overflows in containers with custom allocators +(such as std::vector) where the library developers have added calls into the +AddressSanitizer runtime to indicate which memory is poisoned etc. + +In environments where not all the process binaries can be recompiled with +AddressSanitizer enabled, these checks can cause false positives. + +See `Disabling container overflow checks`_ for details on suppressing checks. + Memory leak detection --------------------- @@ -242,6 +254,46 @@ AddressSanitizer also supports works similarly to ``__attribute__((no_sanitize("address")))``, but it also prevents instrumentation performed by other sanitizers. +Disabling container overflow checks +----------------------------------- + +Runtime suppression +^^^^^^^^^^^^^^^^^^^ + +Container overflow checks can be disabled at runtime using the +``ASAN_OPTIONS=detect_container_overflow=0`` environment variable. + +Compile time suppression +^^^^^^^^^^^^^^^^^^^^^^^^ + +``-D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__`` can be used at compile time to +disable container overflow checks if the container library has added support +for this define. + +To support a standard way to disable container overflow checks at compile time, +library developers should use this definition in conjunction with the +AddressSanitizer feature test to conditionally include container overflow +related code compiled into user code: + +The recommended form is + +.. code-block:: c + + // include the sanitizer common interfaces + #include + + #if __has_feature(address_sanitizer) + // Container overflow detection enabled - include annotations + __sanitizer_annotate_contiguous_container(beg, end, old_mid, new_mid); + #endif + +This pattern ensures that: + +* Container overflow annotations are only included when AddressSanitizer is + enabled +* Container overflow detection can be disabled by passing + ``-D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__`` to the compiler + Suppressing Errors in Recompiled Code (Ignorelist) -------------------------------------------------- diff --git a/compiler-rt/include/sanitizer/common_interface_defs.h b/compiler-rt/include/sanitizer/common_interface_defs.h index 57313f9bc80e6..609a89460cb4a 100644 --- a/compiler-rt/include/sanitizer/common_interface_defs.h +++ b/compiler-rt/include/sanitizer/common_interface_defs.h @@ -156,8 +156,15 @@ int SANITIZER_CDECL __sanitizer_acquire_crash_state(); /// \param end End of memory region. /// \param old_mid Old middle of memory region. /// \param new_mid New middle of memory region. +#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ +__attribute__((__internal_linkage__)) inline void SANITIZER_CDECL +__sanitizer_annotate_contiguous_container(const void *beg, const void *end, + const void *old_mid, + const void *new_mid) {}; +#else void SANITIZER_CDECL __sanitizer_annotate_contiguous_container( const void *beg, const void *end, const void *old_mid, const void *new_mid); +#endif /// Similar to __sanitizer_annotate_contiguous_container. /// @@ -188,10 +195,18 @@ void SANITIZER_CDECL __sanitizer_annotate_contiguous_container( /// \param old_container_end End of used region. /// \param new_container_beg New beginning of used region. /// \param new_container_end New end of used region. +#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ +__attribute__((__internal_linkage__)) inline void + SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container( + const void *storage_beg, const void *storage_end, + const void *old_container_beg, const void *old_container_end, + const void *new_container_beg, const void *new_container_end) {}; +#else void SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container( const void *storage_beg, const void *storage_end, const void *old_container_beg, const void *old_container_end, const void *new_container_beg, const void *new_container_end); +#endif /// Copies memory annotations from a source storage region to a destination /// storage region. After the operation, the destination region has the same @@ -226,9 +241,17 @@ void SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container( /// \param src_end End of the source container region. /// \param dst_begin Begin of the destination container region. /// \param dst_end End of the destination container region. +#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ +__attribute__((__internal_linkage__)) inline void SANITIZER_CDECL +__sanitizer_copy_contiguous_container_annotations(const void *src_begin, + const void *src_end, + const void *dst_begin, + const void *dst_end) {}; +#else void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations( const void *src_begin, const void *src_end, const void *dst_begin, const void *dst_end); +#endif /// Returns true if the contiguous container [beg, end) is properly /// poisoned. @@ -246,9 +269,16 @@ void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations( /// /// \returns True if the contiguous container [beg, end) is properly /// poisoned. +#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ +__attribute__((__internal_linkage__)) inline int + SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg, + const void *mid, + const void *end) {}; +#else int SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg, const void *mid, const void *end); +#endif /// Returns true if the double ended contiguous /// container [storage_beg, storage_end) is properly poisoned. @@ -271,9 +301,16 @@ int SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg, /// \returns True if the double-ended contiguous container [storage_beg, /// container_beg, container_end, end) is properly poisoned - only /// [container_beg; container_end) is addressable. +#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ +__attribute__((__internal_linkage__)) inline int + SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container( + const void *storage_beg, const void *container_beg, + const void *container_end, const void *storage_end) {}; +#else int SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container( const void *storage_beg, const void *container_beg, const void *container_end, const void *storage_end); +#endif /// Similar to __sanitizer_verify_contiguous_container() but also /// returns the address of the first improperly poisoned byte. @@ -285,8 +322,15 @@ int SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container( /// \param end Old end of memory region. /// /// \returns The bad address or NULL. +#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ +__attribute__((__internal_linkage__)) inline const void *SANITIZER_CDECL +__sanitizer_contiguous_container_find_bad_address(const void *beg, + const void *mid, + const void *end) {}; +#else const void *SANITIZER_CDECL __sanitizer_contiguous_container_find_bad_address( const void *beg, const void *mid, const void *end); +#endif /// returns the address of the first improperly poisoned byte. /// @@ -298,10 +342,17 @@ const void *SANITIZER_CDECL __sanitizer_contiguous_container_find_bad_address( /// \param storage_end End of memory region. /// /// \returns The bad address or NULL. +#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ +__attribute__((__internal_linkage__)) inline const void *SANITIZER_CDECL +__sanitizer_double_ended_contiguous_container_find_bad_address( + const void *storage_beg, const void *container_beg, + const void *container_end, const void *storage_end) {}; +#else const void *SANITIZER_CDECL __sanitizer_double_ended_contiguous_container_find_bad_address( const void *storage_beg, const void *container_beg, const void *container_end, const void *storage_end); +#endif /// Prints the stack trace leading to this call (useful for calling from the /// debugger). diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp index 2a207cd06ccac..99d6bdac3d720 100644 --- a/compiler-rt/lib/asan/asan_errors.cpp +++ b/compiler-rt/lib/asan/asan_errors.cpp @@ -514,11 +514,15 @@ ErrorGeneric::ErrorGeneric(u32 tid, uptr pc_, uptr bp_, uptr sp_, uptr addr, } static void PrintContainerOverflowHint() { - Printf("HINT: if you don't care about these errors you may set " - "ASAN_OPTIONS=detect_container_overflow=0.\n" - "If you suspect a false positive see also: " - "https://github.com/google/sanitizers/wiki/" - "AddressSanitizerContainerOverflow.\n"); + Printf( + "HINT: if you don't care about these errors you may set " + "ASAN_OPTIONS=detect_container_overflow=0.\n" + "Or if supported by the container library, pass " + "-D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ to the compiler to disable " + " instrumentation.\n" + "If you suspect a false positive see also: " + "https://github.com/google/sanitizers/wiki/" + "AddressSanitizerContainerOverflow.\n"); } static void PrintShadowByte(InternalScopedString *str, const char *before, diff --git a/compiler-rt/test/asan/TestCases/disable_container_overflow_checks.cpp b/compiler-rt/test/asan/TestCases/disable_container_overflow_checks.cpp new file mode 100644 index 0000000000000..680eb252411da --- /dev/null +++ b/compiler-rt/test/asan/TestCases/disable_container_overflow_checks.cpp @@ -0,0 +1,50 @@ +// Test crash gives guidance on -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ and +// ASAN_OPTIONS=detect_container_overflow=0 +// RUN: %clangxx_asan -O %s -o %t +// RUN: not %run %t 2>&1 | FileCheck --check-prefix=CHECK-CRASH %s +// +// Test overflow checks can be disabled at runtime with +// ASAN_OPTIONS=detect_container_overflow=0 +// RUN: %env_asan_opts=detect_container_overflow=0 %run %t 2>&1 | FileCheck --check-prefix=CHECK-NOCRASH %s +// +// Illustrate use of -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ flag to suppress +// overflow checks at compile time. +// RUN: %clangxx_asan -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ -O %s -o %t-no-overflow +// RUN: %run %t-no-overflow 2>&1 | FileCheck --check-prefix=CHECK-NOCRASH %s +// + +#include +#include +#include + +// public definition of __sanitizer_annotate_contiguous_container +#include "sanitizer/common_interface_defs.h" + +static volatile int one = 1; + +int TestCrash() { + long t[100]; + t[60] = 0; +#if __has_feature(address_sanitizer) + __sanitizer_annotate_contiguous_container(&t[0], &t[0] + 100, &t[0] + 100, + &t[0] + 50); +#endif + // CHECK-CRASH: AddressSanitizer: container-overflow + // CHECK-CRASH: ASAN_OPTIONS=detect_container_overflow=0 + // CHECK-CRASH: __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ + // CHECK-NOCRASH-NOT: AddressSanitizer: container-overflow + // CHECK-NOCRASH-NOT: ASAN_OPTIONS=detect_container_overflow=0 + // CHECK-NOCRASH-NOT: __SANITIZER_DISABLE_CONTAINER_OVERFLOW__ + return (int)t[60 * one]; // Touches the poisoned memory. +} + +int main(int argc, char **argv) { + + int retval = 0; + + retval = TestCrash(); + + printf("Exiting main\n"); + + return retval; +} diff --git a/compiler-rt/test/asan/TestCases/stack_container_dynamic_lib.cpp b/compiler-rt/test/asan/TestCases/stack_container_dynamic_lib.cpp new file mode 100644 index 0000000000000..e17c537f4ac1c --- /dev/null +++ b/compiler-rt/test/asan/TestCases/stack_container_dynamic_lib.cpp @@ -0,0 +1,118 @@ +// Test to demonstrate compile-time disabling of container-overflow checks +// in order to handle uninstrumented libraries +// UNSUPPORTED: target={{.*windows-msvc.*}} + +// Mimic a closed-source library compiled without ASan +// RUN: %clangxx_asan -fno-sanitize=address -DSHARED_LIB %s %fPIC -shared -o %t-so.so + +// Mimic multiple files being linked into a single executable, +// %t-object.o and %t-main compiled seperately and then linked together +// RUN: %clangxx_asan -DMULTI_SOURCE %s -c -o %t-object.o +// RUN: %clangxx_asan %s -c -o %t-main.o +// RUN: %clangxx_asan -o %t %t-main.o %t-object.o +// RUN: not %run %t 2>&1 | FileCheck %s + +// Disable container overflow checks at runtime using ASAN_OPTIONS=detect_container_overflow=0 +// RUN: %env_asan_opts=detect_container_overflow=0 %run %t 2>&1 | FileCheck --check-prefix=CHECK-NO-CONTAINER-OVERFLOW %s + +// RUN: %clangxx_asan -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ -DMULTI_SOURCE %s -c -o %t-object.o +// RUN: %clangxx_asan -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ %s -c -o %t-main.o +// RUN: %clangxx_asan -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ -o %t %t-main.o %t-object.o +// RUN: %run %t 2>&1 | FileCheck --check-prefix=CHECK-NO-CONTAINER-OVERFLOW %s + +#include +#include +#include + +template class Stack { +private: + T data[5]; + size_t size; + +public: + Stack() : size(0) { +#if __has_feature(address_sanitizer) && !__ASAN_DISABLE_CONTAINER_OVERFLOW__ + // Mark entire storage as unaddressable initially + __sanitizer_annotate_contiguous_container(data, data + 5, data + 5, data); +#endif + } + + ~Stack() { +#if __has_feature(address_sanitizer) && !__ASAN_DISABLE_CONTAINER_OVERFLOW__ + __sanitizer_annotate_contiguous_container(data, data + 5, data + size, + data + 5); +#endif + } + + void push(const T &value) { + assert(size < 5 && "Stack overflow"); +#if __has_feature(address_sanitizer) && !__ASAN_DISABLE_CONTAINER_OVERFLOW__ + __sanitizer_annotate_contiguous_container(data, data + 5, data + size, + data + size + 1); +#endif + data[size++] = value; + } + + T pop() { + assert(size > 0 && "Cannot pop from empty stack"); + T result = data[--size]; +#if __has_feature(address_sanitizer) && !__ASAN_DISABLE_CONTAINER_OVERFLOW__ + __sanitizer_annotate_contiguous_container(data, data + 5, data + size + 1, + data + size); +#endif + return result; + } +}; + +#ifdef SHARED_LIB +// Mimics a closed-source library compiled without ASan + +extern "C" void push_value_to_stack(Stack &stack) { stack.push(42); } +#else // SHARED_LIB + +# include +# include + +typedef void (*push_func_t)(Stack &); + +# if defined(MULTI_SOURCE) +extern push_func_t push_value; + +extern "C" void do_push_value_to_stack(Stack &stack) { + assert(push_value); + push_value(stack); +} + +# else +push_func_t push_value = nullptr; + +extern "C" void do_push_value_to_stack(Stack &stack); + +int main(int argc, char *argv[]) { + std::string path = std::string(argv[0]) + "-so.so"; + printf("Loading library: %s\n", path.c_str()); + + void *lib = dlopen(path.c_str(), RTLD_NOW); + assert(lib); + + push_value = (push_func_t)dlsym(lib, "push_value_to_stack"); + assert(push_value); + + Stack stack; + do_push_value_to_stack(stack); + + // BOOM! uninstrumented library didn't update container bounds + int value = stack.pop(); + // CHECK: AddressSanitizer: container-overflow + printf("Popped value: %d\n", value); + assert(value == 42 && "Expected value 42"); + + dlclose(lib); + printf("SUCCESS\n"); + // CHECK-NO-CONTAINER-OVERFLOW: SUCCESS + return 0; +} + +# endif // MULTI_SOURCE + +#endif // SHARED_LIB