Skip to content

Commit 98726dd

Browse files
committed
Document a define to allow library developers to support disabling
AddressSanitizer's container overflow detection in template code at compile time. The primary motivation is to reduce false positives in environments where libraries and frameworks that cannot be recompiled with sanitizers enabled are called from application code. This supports disabling checks when the runtime environment cannot be reliably controlled to use ASAN_OPTIONS. Key changes: - Use the define `__SANITIZER_DISABLE_CONTAINER_OVERFLOW__` to disable instrumentation at compile time - Implemented redefining the container overflow APIs in common_interface_defs.h to use define to provide null implementation when define is present - Update documentation in AddressSanitizer.rst to suggest and illustrate use of the define - Add details of the define in PrintContainerOverflowHint() - Add test disable_container_overflow_checks to verify new hints on the error and fill the testing gap that ASAN_OPTIONS=detect_container_overflow=0 works - Add tests demonstrating the issue around closed source libraries and instrumented apps that both modify containers This requires no compiler changes and should be supportable cross compiler toolchains. An RFC has been opened to discuss: https://discourse.llvm.org/t/rfc-add-fsanitize-address-disable-container-overflow-flag-to-addresssanitizer/88349
1 parent 951ab04 commit 98726dd

File tree

5 files changed

+278
-5
lines changed

5 files changed

+278
-5
lines changed

clang/docs/AddressSanitizer.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,18 @@ To summarize: ``-fsanitize-address-use-after-return=<mode>``
164164
* ``always``: Enables detection of UAR errors in all cases. (reduces code
165165
size, but not as much as ``never``).
166166

167+
Container Overflow Detection
168+
----------------------------
169+
170+
AddressSanitizer can detect overflows in containers with custom allocators
171+
(such as std::vector) where the library developers have added calls into the
172+
AddressSanitizer runtime to indicate which memory is poisoned etc.
173+
174+
In environments where not all the process binaries can be recompiled with
175+
AddressSanitizer enabled, these checks can cause false positives.
176+
177+
See `Disabling container overflow checks`_ for details on suppressing checks.
178+
167179
Memory leak detection
168180
---------------------
169181

@@ -242,6 +254,46 @@ AddressSanitizer also supports
242254
works similarly to ``__attribute__((no_sanitize("address")))``, but it also
243255
prevents instrumentation performed by other sanitizers.
244256

257+
Disabling container overflow checks
258+
-----------------------------------
259+
260+
Runtime suppression
261+
^^^^^^^^^^^^^^^^^^^
262+
263+
Container overflow checks can be disabled at runtime using the
264+
``ASAN_OPTIONS=detect_container_overflow=0`` environment variable.
265+
266+
Compile time suppression
267+
^^^^^^^^^^^^^^^^^^^^^^^^
268+
269+
``-D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__`` can be used at compile time to
270+
disable container overflow checks if the container library has added support
271+
for this define.
272+
273+
To support a standard way to disable container overflow checks at compile time,
274+
library developers should use this definition in conjunction with the
275+
AddressSanitizer feature test to conditionally include container overflow
276+
related code compiled into user code:
277+
278+
The recommended form is
279+
280+
.. code-block:: c
281+
282+
// include the sanitizer common interfaces
283+
#include <sanitizer/common_interface_defs.h>
284+
285+
#if __has_feature(address_sanitizer)
286+
// Container overflow detection enabled - include annotations
287+
__sanitizer_annotate_contiguous_container(beg, end, old_mid, new_mid);
288+
#endif
289+
290+
This pattern ensures that:
291+
292+
* Container overflow annotations are only included when AddressSanitizer is
293+
enabled
294+
* Container overflow detection can be disabled by passing
295+
``-D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__`` to the compiler
296+
245297
Suppressing Errors in Recompiled Code (Ignorelist)
246298
--------------------------------------------------
247299

compiler-rt/include/sanitizer/common_interface_defs.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,14 @@ int SANITIZER_CDECL __sanitizer_acquire_crash_state();
156156
/// \param end End of memory region.
157157
/// \param old_mid Old middle of memory region.
158158
/// \param new_mid New middle of memory region.
159+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
160+
__attribute__((__internal_linkage__)) inline
161+
void SANITIZER_CDECL __sanitizer_annotate_contiguous_container(
162+
const void *beg, const void *end, const void *old_mid, const void *new_mid) {};
163+
#else
159164
void SANITIZER_CDECL __sanitizer_annotate_contiguous_container(
160165
const void *beg, const void *end, const void *old_mid, const void *new_mid);
166+
#endif
161167

162168
/// Similar to <c>__sanitizer_annotate_contiguous_container</c>.
163169
///
@@ -188,10 +194,19 @@ void SANITIZER_CDECL __sanitizer_annotate_contiguous_container(
188194
/// \param old_container_end End of used region.
189195
/// \param new_container_beg New beginning of used region.
190196
/// \param new_container_end New end of used region.
197+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
198+
__attribute__((__internal_linkage__)) inline
199+
void SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container(
200+
const void *storage_beg, const void *storage_end,
201+
const void *old_container_beg, const void *old_container_end,
202+
const void *new_container_beg, const void *new_container_end) {};
203+
#else
191204
void SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container(
192205
const void *storage_beg, const void *storage_end,
193206
const void *old_container_beg, const void *old_container_end,
194207
const void *new_container_beg, const void *new_container_end);
208+
#endif
209+
195210

196211
/// Copies memory annotations from a source storage region to a destination
197212
/// storage region. After the operation, the destination region has the same
@@ -226,9 +241,16 @@ void SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container(
226241
/// \param src_end End of the source container region.
227242
/// \param dst_begin Begin of the destination container region.
228243
/// \param dst_end End of the destination container region.
244+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
245+
__attribute__((__internal_linkage__)) inline
246+
void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations(
247+
const void *src_begin, const void *src_end, const void *dst_begin,
248+
const void *dst_end) {};
249+
#else
229250
void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations(
230251
const void *src_begin, const void *src_end, const void *dst_begin,
231252
const void *dst_end);
253+
#endif
232254

233255
/// Returns true if the contiguous container <c>[beg, end)</c> is properly
234256
/// poisoned.
@@ -246,9 +268,16 @@ void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations(
246268
///
247269
/// \returns True if the contiguous container <c>[beg, end)</c> is properly
248270
/// poisoned.
271+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
272+
__attribute__((__internal_linkage__)) inline
273+
int SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg,
274+
const void *mid,
275+
const void *end) {};
276+
#else
249277
int SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg,
250278
const void *mid,
251279
const void *end);
280+
#endif
252281

253282
/// Returns true if the double ended contiguous
254283
/// container <c>[storage_beg, storage_end)</c> is properly poisoned.
@@ -271,9 +300,16 @@ int SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg,
271300
/// \returns True if the double-ended contiguous container <c>[storage_beg,
272301
/// container_beg, container_end, end)</c> is properly poisoned - only
273302
/// [container_beg; container_end) is addressable.
303+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
304+
__attribute__((__internal_linkage__)) inline
305+
int SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container(
306+
const void *storage_beg, const void *container_beg,
307+
const void *container_end, const void *storage_end) {};
308+
#else
274309
int SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container(
275310
const void *storage_beg, const void *container_beg,
276311
const void *container_end, const void *storage_end);
312+
#endif
277313

278314
/// Similar to <c>__sanitizer_verify_contiguous_container()</c> but also
279315
/// returns the address of the first improperly poisoned byte.
@@ -285,8 +321,14 @@ int SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container(
285321
/// \param end Old end of memory region.
286322
///
287323
/// \returns The bad address or NULL.
324+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
325+
__attribute__((__internal_linkage__)) inline
326+
const void *SANITIZER_CDECL __sanitizer_contiguous_container_find_bad_address(
327+
const void *beg, const void *mid, const void *end) {};
328+
#else
288329
const void *SANITIZER_CDECL __sanitizer_contiguous_container_find_bad_address(
289330
const void *beg, const void *mid, const void *end);
331+
#endif
290332

291333
/// returns the address of the first improperly poisoned byte.
292334
///
@@ -298,10 +340,18 @@ const void *SANITIZER_CDECL __sanitizer_contiguous_container_find_bad_address(
298340
/// \param storage_end End of memory region.
299341
///
300342
/// \returns The bad address or NULL.
343+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
344+
__attribute__((__internal_linkage__)) inline
345+
const void *SANITIZER_CDECL
346+
__sanitizer_double_ended_contiguous_container_find_bad_address(
347+
const void *storage_beg, const void *container_beg,
348+
const void *container_end, const void *storage_end) {};
349+
#else
301350
const void *SANITIZER_CDECL
302351
__sanitizer_double_ended_contiguous_container_find_bad_address(
303352
const void *storage_beg, const void *container_beg,
304353
const void *container_end, const void *storage_end);
354+
#endif
305355

306356
/// Prints the stack trace leading to this call (useful for calling from the
307357
/// debugger).

compiler-rt/lib/asan/asan_errors.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -514,11 +514,15 @@ ErrorGeneric::ErrorGeneric(u32 tid, uptr pc_, uptr bp_, uptr sp_, uptr addr,
514514
}
515515

516516
static void PrintContainerOverflowHint() {
517-
Printf("HINT: if you don't care about these errors you may set "
518-
"ASAN_OPTIONS=detect_container_overflow=0.\n"
519-
"If you suspect a false positive see also: "
520-
"https://github.com/google/sanitizers/wiki/"
521-
"AddressSanitizerContainerOverflow.\n");
517+
Printf(
518+
"HINT: if you don't care about these errors you may set "
519+
"ASAN_OPTIONS=detect_container_overflow=0.\n"
520+
"Or if supported by the container library, pass "
521+
"-D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ to the compiler to disable "
522+
" instrumentation.\n"
523+
"If you suspect a false positive see also: "
524+
"https://github.com/google/sanitizers/wiki/"
525+
"AddressSanitizerContainerOverflow.\n");
522526
}
523527

524528
static void PrintShadowByte(InternalScopedString *str, const char *before,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Test crash gives guidance on -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ and
2+
// ASAN_OPTIONS=detect_container_overflow=0
3+
// RUN: %clangxx_asan -O %s -o %t
4+
// RUN: not %run %t 2>&1 | FileCheck --check-prefix=CHECK-CRASH %s
5+
//
6+
// Test overflow checks can be disabled at runtime with
7+
// ASAN_OPTIONS=detect_container_overflow=0
8+
// RUN: %env_asan_opts=detect_container_overflow=0 %run %t 2>&1 | FileCheck --check-prefix=CHECK-NOCRASH %s
9+
//
10+
// Illustrate use of -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ flag to suppress
11+
// overflow checks at compile time.
12+
// RUN: %clangxx_asan -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ -O %s -o %t-no-overflow
13+
// RUN: %run %t-no-overflow 2>&1 | FileCheck --check-prefix=CHECK-NOCRASH %s
14+
//
15+
16+
#include <assert.h>
17+
#include <stdio.h>
18+
#include <string.h>
19+
20+
// public definition of __sanitizer_annotate_contiguous_container
21+
#include "sanitizer/common_interface_defs.h"
22+
23+
static volatile int one = 1;
24+
25+
int TestCrash() {
26+
long t[100];
27+
t[60] = 0;
28+
#if __has_feature(address_sanitizer)
29+
__sanitizer_annotate_contiguous_container(&t[0], &t[0] + 100, &t[0] + 100,
30+
&t[0] + 50);
31+
#endif
32+
// CHECK-CRASH: AddressSanitizer: container-overflow
33+
// CHECK-CRASH: ASAN_OPTIONS=detect_container_overflow=0
34+
// CHECK-CRASH: __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
35+
// CHECK-NOCRASH-NOT: AddressSanitizer: container-overflow
36+
// CHECK-NOCRASH-NOT: ASAN_OPTIONS=detect_container_overflow=0
37+
// CHECK-NOCRASH-NOT: __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
38+
return (int)t[60 * one]; // Touches the poisoned memory.
39+
}
40+
41+
int main(int argc, char **argv) {
42+
43+
int retval = 0;
44+
45+
retval = TestCrash();
46+
47+
printf("Exiting main\n");
48+
49+
return retval;
50+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Test to demonstrate compile-time disabling of container-overflow checks
2+
// in order to handle uninstrumented libraries
3+
4+
// Mimic a closed-source library compiled without ASan
5+
// RUN: %clangxx_asan -fno-sanitize=address -DSHARED_LIB %s -fPIC -shared -o %t-so.so
6+
7+
// Mimic multiple files being linked into a single executable,
8+
// %t-object.o and %t-main compiled seperately and then linked together
9+
// RUN: %clangxx_asan -DMULTI_SOURCE %s -c -o %t-object.o
10+
// RUN: %clangxx_asan %s -c -o %t-main.o
11+
// RUN: %clangxx_asan -o %t %t-main.o %t-object.o
12+
// RUN: not %run %t 2>&1 | FileCheck %s
13+
14+
// Disable container overflow checks at runtime using ASAN_OPTIONS=detect_container_overflow=0
15+
// RUN: %env_asan_opts=detect_container_overflow=0 %run %t 2>&1 | FileCheck --check-prefix=CHECK-NO-CONTAINER-OVERFLOW %s
16+
17+
// RUN: %clangxx_asan -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ -DMULTI_SOURCE %s -c -o %t-object.o
18+
// RUN: %clangxx_asan -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ %s -c -o %t-main.o
19+
// RUN: %clangxx_asan -D__SANITIZER_DISABLE_CONTAINER_OVERFLOW__ -o %t %t-main.o %t-object.o
20+
// RUN: %run %t 2>&1 | FileCheck --check-prefix=CHECK-NO-CONTAINER-OVERFLOW %s
21+
22+
#include <assert.h>
23+
#include <sanitizer/common_interface_defs.h>
24+
#include <stdio.h>
25+
26+
template <typename T> class Stack {
27+
private:
28+
T data[5];
29+
size_t size;
30+
31+
public:
32+
Stack() : size(0) {
33+
#if __has_feature(address_sanitizer) && !__ASAN_DISABLE_CONTAINER_OVERFLOW__
34+
// Mark entire storage as unaddressable initially
35+
__sanitizer_annotate_contiguous_container(data, data + 5, data + 5, data);
36+
#endif
37+
}
38+
39+
~Stack() {
40+
#if __has_feature(address_sanitizer) && !__ASAN_DISABLE_CONTAINER_OVERFLOW__
41+
__sanitizer_annotate_contiguous_container(data, data + 5, data + size,
42+
data + 5);
43+
#endif
44+
}
45+
46+
void push(const T &value) {
47+
assert(size < 5 && "Stack overflow");
48+
#if __has_feature(address_sanitizer) && !__ASAN_DISABLE_CONTAINER_OVERFLOW__
49+
__sanitizer_annotate_contiguous_container(data, data + 5, data + size,
50+
data + size + 1);
51+
#endif
52+
data[size++] = value;
53+
}
54+
55+
T pop() {
56+
assert(size > 0 && "Cannot pop from empty stack");
57+
T result = data[--size];
58+
#if __has_feature(address_sanitizer) && !__ASAN_DISABLE_CONTAINER_OVERFLOW__
59+
__sanitizer_annotate_contiguous_container(data, data + 5, data + size + 1,
60+
data + size);
61+
#endif
62+
return result;
63+
}
64+
};
65+
66+
#ifdef SHARED_LIB
67+
// Mimics a closed-source library compiled without ASan
68+
69+
extern "C" void push_value_to_stack(Stack<int> &stack) { stack.push(42); }
70+
#else // SHARED_LIB
71+
72+
# include <dlfcn.h>
73+
# include <string>
74+
75+
typedef void (*push_func_t)(Stack<int> &);
76+
77+
#if defined(MULTI_SOURCE)
78+
extern push_func_t push_value;
79+
80+
extern "C" void do_push_value_to_stack(Stack<int> &stack) {
81+
assert(push_value);
82+
push_value(stack);
83+
}
84+
85+
#else
86+
push_func_t push_value = nullptr;
87+
88+
extern "C" void do_push_value_to_stack(Stack<int> &stack);
89+
90+
int main(int argc, char *argv[]) {
91+
std::string path = std::string(argv[0]) + "-so.so";
92+
printf("Loading library: %s\n", path.c_str());
93+
94+
void *lib = dlopen(path.c_str(), RTLD_NOW);
95+
assert(lib);
96+
97+
push_value = (push_func_t)dlsym(lib, "push_value_to_stack");
98+
assert(push_value);
99+
100+
Stack<int> stack;
101+
do_push_value_to_stack(stack);
102+
103+
// BOOM! uninstrumented library didn't update container bounds
104+
int value = stack.pop();
105+
// CHECK: AddressSanitizer: container-overflow
106+
printf("Popped value: %d\n", value);
107+
assert(value == 42 && "Expected value 42");
108+
109+
dlclose(lib);
110+
printf("SUCCESS\n");
111+
// CHECK-NO-CONTAINER-OVERFLOW: SUCCESS
112+
return 0;
113+
}
114+
115+
#endif // MULTI_SOURCE
116+
117+
#endif // SHARED_LIB

0 commit comments

Comments
 (0)