Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions clang/docs/AddressSanitizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ To summarize: ``-fsanitize-address-use-after-return=<mode>``
* ``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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can they also cause crashes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ASan's expected behaviour is to output the error and kill the process unless a user explicitly changes this behaviour. This is done using the compiler switch -fsanitize-recover=address and setting the environment variable ASAN_OPTIONS=halt_on_error=false


See `Disabling container overflow checks`_ for details on suppressing checks.

Memory leak detection
---------------------

Expand Down Expand Up @@ -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 <sanitizer/common_interface_defs.h>
#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)
--------------------------------------------------

Expand Down
51 changes: 51 additions & 0 deletions compiler-rt/include/sanitizer/common_interface_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const void *new_mid) {};
const void *new_mid) {}

Throughout

#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 <c>__sanitizer_annotate_contiguous_container</c>.
///
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <c>[beg, end)</c> is properly
/// poisoned.
Expand All @@ -246,9 +269,16 @@ void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations(
///
/// \returns True if the contiguous container <c>[beg, end)</c> 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 <c>[storage_beg, storage_end)</c> is properly poisoned.
Expand All @@ -271,9 +301,16 @@ int SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg,
/// \returns True if the double-ended contiguous container <c>[storage_beg,
/// container_beg, container_end, end)</c> 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 <c>__sanitizer_verify_contiguous_container()</c> but also
/// returns the address of the first improperly poisoned byte.
Expand All @@ -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.
///
Expand All @@ -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).
Expand Down
14 changes: 9 additions & 5 deletions compiler-rt/lib/asan/asan_errors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <assert.h>
#include <stdio.h>
#include <string.h>

// 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;
}
118 changes: 118 additions & 0 deletions compiler-rt/test/asan/TestCases/stack_container_dynamic_lib.cpp
Original file line number Diff line number Diff line change
@@ -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 <assert.h>
#include <sanitizer/common_interface_defs.h>
#include <stdio.h>

template <typename T> 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<int> &stack) { stack.push(42); }
#else // SHARED_LIB

# include <dlfcn.h>
# include <string>

typedef void (*push_func_t)(Stack<int> &);

# if defined(MULTI_SOURCE)
extern push_func_t push_value;

extern "C" void do_push_value_to_stack(Stack<int> &stack) {
assert(push_value);
push_value(stack);
}

# else
push_func_t push_value = nullptr;

extern "C" void do_push_value_to_stack(Stack<int> &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<int> 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