Skip to content

Commit 9f2fe70

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 9f2fe70

File tree

5 files changed

+280
-5
lines changed

5 files changed

+280
-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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,15 @@ 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 void SANITIZER_CDECL
161+
__sanitizer_annotate_contiguous_container(const void *beg, const void *end,
162+
const void *old_mid,
163+
const void *new_mid) {};
164+
#else
159165
void SANITIZER_CDECL __sanitizer_annotate_contiguous_container(
160166
const void *beg, const void *end, const void *old_mid, const void *new_mid);
167+
#endif
161168

162169
/// Similar to <c>__sanitizer_annotate_contiguous_container</c>.
163170
///
@@ -188,10 +195,18 @@ void SANITIZER_CDECL __sanitizer_annotate_contiguous_container(
188195
/// \param old_container_end End of used region.
189196
/// \param new_container_beg New beginning of used region.
190197
/// \param new_container_end New end of used region.
198+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
199+
__attribute__((__internal_linkage__)) inline void
200+
SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container(
201+
const void *storage_beg, const void *storage_end,
202+
const void *old_container_beg, const void *old_container_end,
203+
const void *new_container_beg, const void *new_container_end) {};
204+
#else
191205
void SANITIZER_CDECL __sanitizer_annotate_double_ended_contiguous_container(
192206
const void *storage_beg, const void *storage_end,
193207
const void *old_container_beg, const void *old_container_end,
194208
const void *new_container_beg, const void *new_container_end);
209+
#endif
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,17 @@ 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 void SANITIZER_CDECL
246+
__sanitizer_copy_contiguous_container_annotations(const void *src_begin,
247+
const void *src_end,
248+
const void *dst_begin,
249+
const void *dst_end) {};
250+
#else
229251
void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations(
230252
const void *src_begin, const void *src_end, const void *dst_begin,
231253
const void *dst_end);
254+
#endif
232255

233256
/// Returns true if the contiguous container <c>[beg, end)</c> is properly
234257
/// poisoned.
@@ -246,9 +269,16 @@ void SANITIZER_CDECL __sanitizer_copy_contiguous_container_annotations(
246269
///
247270
/// \returns True if the contiguous container <c>[beg, end)</c> is properly
248271
/// poisoned.
272+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
273+
__attribute__((__internal_linkage__)) inline int
274+
SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg,
275+
const void *mid,
276+
const void *end) {};
277+
#else
249278
int SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg,
250279
const void *mid,
251280
const void *end);
281+
#endif
252282

253283
/// Returns true if the double ended contiguous
254284
/// container <c>[storage_beg, storage_end)</c> is properly poisoned.
@@ -271,9 +301,16 @@ int SANITIZER_CDECL __sanitizer_verify_contiguous_container(const void *beg,
271301
/// \returns True if the double-ended contiguous container <c>[storage_beg,
272302
/// container_beg, container_end, end)</c> is properly poisoned - only
273303
/// [container_beg; container_end) is addressable.
304+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
305+
__attribute__((__internal_linkage__)) inline int
306+
SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container(
307+
const void *storage_beg, const void *container_beg,
308+
const void *container_end, const void *storage_end) {};
309+
#else
274310
int SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container(
275311
const void *storage_beg, const void *container_beg,
276312
const void *container_end, const void *storage_end);
313+
#endif
277314

278315
/// Similar to <c>__sanitizer_verify_contiguous_container()</c> but also
279316
/// returns the address of the first improperly poisoned byte.
@@ -285,8 +322,15 @@ int SANITIZER_CDECL __sanitizer_verify_double_ended_contiguous_container(
285322
/// \param end Old end of memory region.
286323
///
287324
/// \returns The bad address or NULL.
325+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
326+
__attribute__((__internal_linkage__)) inline const void *SANITIZER_CDECL
327+
__sanitizer_contiguous_container_find_bad_address(const void *beg,
328+
const void *mid,
329+
const void *end) {};
330+
#else
288331
const void *SANITIZER_CDECL __sanitizer_contiguous_container_find_bad_address(
289332
const void *beg, const void *mid, const void *end);
333+
#endif
290334

291335
/// returns the address of the first improperly poisoned byte.
292336
///
@@ -298,10 +342,17 @@ const void *SANITIZER_CDECL __sanitizer_contiguous_container_find_bad_address(
298342
/// \param storage_end End of memory region.
299343
///
300344
/// \returns The bad address or NULL.
345+
#ifdef __SANITIZER_DISABLE_CONTAINER_OVERFLOW__
346+
__attribute__((__internal_linkage__)) inline const void *SANITIZER_CDECL
347+
__sanitizer_double_ended_contiguous_container_find_bad_address(
348+
const void *storage_beg, const void *container_beg,
349+
const void *container_end, const void *storage_end) {};
350+
#else
301351
const void *SANITIZER_CDECL
302352
__sanitizer_double_ended_contiguous_container_find_bad_address(
303353
const void *storage_beg, const void *container_beg,
304354
const void *container_end, const void *storage_end);
355+
#endif
305356

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

0 commit comments

Comments
 (0)