AddressSanitizer - A Tool for Programmers to Detect Memory Access Errors

Memory access errors are the most common software errors that often cause program crashes. The AddressSanitizer tool, developed by Google engineers in 2012, has become the first choice of C/C++ programmers for its wide coverage, high efficiency, and low overhead. Here is a brief introduction to its principle and usage.

One man's "magic" is another man's engineering. "Supernatural" is a null word.
Robert Anson Heinlein (American science fiction author, aeronautical engineer, and naval officer)

Tool Overview

The C/C++ language allows programmers to have low-level control over memory, and this direct memory management has made it possible to write efficient application software. However, this has also made memory access errors, including buffer overflows, accesses to freed memory, and memory leaks, a serious problem that must be coped with in program design and implementation. While there are tools and software that provide the ability to detect such errors, their operational efficiency, and functional coverage are often less than ideal.

In 2012, Google engineer Konstantin Serebryany and team members released an open-source memory access error detector for C/C++ programs called AddressSanitizer1. AddressSanitizer (ASan) applies new memory allocation, mapping, and code stubbing techniques to detect almost all memory access errors efficiently. Using the SPEC 2006 benchmark analysis package, ASan runs with an average slowdown of less than 2 and memory consumption of about 2.4 times. In comparison, another well-known detection tool Valgrind has an average slowdown of 20, which makes it almost impossible to put into practice.

The following table summarizes the types of memory access errors that ASan can detect for C/C++ programs:

Error Type Abbreviation Notes
heap use after free UAF Access freed memory (dangling pointer dereference)
heap buffer overflow Heap OOB Dynamic allocated memory out-of-bound read/write
heap memory leak HML Dynamic allocated memory not freed after use
global buffer overflow Global OOB Global object out-of-bound read/write
stack use after scope UAS Local object out-of-scope access
stack use after return UAR Local object out-of-scope access after return
stack buffer overflow Stack OOB Local object out-of-bound read/write

ASan itself cannot detect heap memory leaks. But when ASan is integrated into the compiler, as it replaces the memory allocation/free functions, the original leak detection feature of the compiler tool is consolidated with ASan. So, adding the ASan option to the compilation command line also turns on the leak detection feature by default.

This covers all common memory access errors except for "uninitialized memory reads" (UMR). ASan detects them with a false positive rate of 0, which is quite impressive. In addition, ASan detects several C++-specific memory access errors such as

  • Initialization Order Fiasco: When two static objects are defined in different source files and the constructor of one object calls the method of the other object, a program crash will occur if the former compilation unit is initialized first.
  • Container Overflow: Given libc++/libstdc++ container, access [container.end(), container.begin() + container.capacity())], which crosses the [container.begin(), container.end()] range but still within the dynamically allocated memory area.
  • Delete Mismatch: For the array object created by new foo[n], should not call delete foo for deletion, use delete [] foo instead.

ASan's high reliability and performance have made it the preferred choice of compiler and IDE developers since its introduction. Today ASan is integrated into all four major compilation toolsets:

Compiler/IDE First Support Version OS Platform
Clang/LLVM2 3.1 Unix-like Cross-platform
GCC 4.8 Unix-like Cross-platform
Xcode 7.0 Mac OS X Apple products
MSVC 16.9 Windows IA-32, x86-64 and ARM

ASan's developers first used the Chromium open-source browser for routine testing and found more than 300 memory access errors over 10 months. After integration into mainstream compilation tools, it reported long-hidden bugs in numerous popular open-source software, such as Mozilla Firefox, Perl, Vim, PHP, and MySQL. Interestingly, ASan also identified some memory access errors in the LLVM and GCC compilers' code. Now, many software companies have added ASan run to their mandatory quality control processes.

Working Principle

The USENIX conference paper 3, published by Serebryany in 2012, comprehensively describes the design principles, algorithmic ideas, and programming implementation of ASan. In terms of the overall structure, ASan consists of two parts.

  1. Compiler instrumentation - modifies the code to verify the shadow memory state at each memory access and creates poisoned red zones at the edges of global and stack objects to detect overflows or underflows.
  2. Runtime library replacement - replaces malloc/free and its related functions to create poisoned red zones at the edge of dynamically allocated heap memory regions, delay the reuse of memory regions after release, and generate error reports.

Here shadow memory, compiler instrumentation, and memory allocation function replacement are all previously available techniques, so how has ASan innovatively applied them for efficient error detection? Let's take a look at the details.

Shadow Memory

Many inspection tools use separated shadow memory to record metadata about program memory, and then apply instrumentation to check the shadow memory during memory accesses to confirm that reads and writes are safe. The difference is that ASan uses a more efficient direct mapping shadow memory.

The designers of ASan noted that typically the malloc function returns a memory address that is at least 8-byte aligned. For example, a request for 20 bytes of memory would divide 24 bytes of memory, with the last 3 bits of the actual return pointer being all zeros. in addition, any aligned 8-byte sequence would only have 9 different states: the first \(k\,(0\leq k \leq 8)\) bytes are accessible, and the last \(8-k\) are not. From this, they came up with a more compact shadow memory mapping and usage scheme:

  • Reserve one-eighth of the virtual address space for shadow memory
  • Directly map application memory to shadow memory using a formula that divides by 8 plus an offset
    • 32-bit application: Shadow = (Mem >> 3) + 0x20000000;
    • 64-bit application: Shadow = (Mem >> 3) + 0x7fff8000;
  • Each byte of shadow memory records one of the 9 states of the corresponding 8-byte memory block
    • 0 means all 8 bytes are addressable
    • Any negative value indicates that the entire 8-byte word is unaddressable (poisoned )
    • k (1 ≤ k ≤ 7) means that the first k bytes are addressable

The following figure shows the address space layout and mapping relationship of ASan. Pay attention to the Bad area in the middle, which is the address segment after the shadow memory itself is mapped. Because shadow memory is not visible to the application, ASan uses a page protection mechanism to make it inaccessible.

Compiler Instrumentation

Once the shadow memory design is determined, the implementation of compiler instrumentation to detect dynamic memory access errors is easy. For memory accesses of 8 bytes, the shadow memory bytes are checked by inserting instructions before the original read/write code, and an error is reported if they are not zero. For memory accesses of less than 8 bytes, the instrumentation is a bit more complicated, where the shadow memory byte values are compared with the last three bits of the read/write address. This situation is also known as the "slow path" and the sample code is as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Check the cases where we access first k bytes of the qword
// and these k bytes are unpoisoned.
bool SlowPathCheck(shadow_value, address, kAccessSize) {
last_accessed_byte = (address & 7) + kAccessSize - 1;
return (last_accessed_byte >= shadow_value);
}
...

byte *shadow_address = MemToShadow(address);
byte shadow_value = *shadow_address;
if (shadow_value) {
if (SlowPathCheck(shadow_value, address, kAccessSize)) {
ReportError(address, kAccessSize, kIsWrite);
}
}
*address = ...; // or: ... = *address;

For global and stack (local) objects, ASan has designed different instrumentation to detect their out-of-bounds access errors. The red zone around a global object is added by the compiler at compile time and its address is passed to the runtime library at application startup, where the runtime library function then poisons the red zone and writes down the address needed in error reporting. The stack object is created at function call time, and accordingly, its red zone is created and poisoned at runtime. In addition, because the stack object is deleted when the function returns, the instrumentation code must also zero out the shadow memory it is mapped to.

In practice, the ASan compiler instrumentation process is placed at the end of the compiler optimization pipeline so that instrumentation only applies to the remaining memory access instructions after variable and loop optimization. In the latest GCC distribution, the ASan compiler stubbing code is located in two files in the gcc subdirectory gcc/asan.[ch].

Runtime Library Replacement

The runtime library needs to include code to manage shadow memory. The address segment to which shadow memory itself is mapped is to be initialized at application startup to disable access to shadow memory by other parts of the program. The runtime library replaces the old memory allocation and free functions and also adds some error reporting functions such as __asan_report_load8.

The newly replaced memory allocation function malloc will allocate additional storage as a red zone before and after the requested memory block and set the red zone to be non-addressable. This is called the poisoning process. In practice, because the memory allocator maintains a list of available memory corresponding to different object sizes, if the list of a certain object is empty, the OS will allocate a large set of memory blocks and their red zones at once. As a result, the red zones of the preceding and following memory blocks will be connected, as shown in the following figure, where \(n\) memory blocks require only \(n+1\) red zones to be allocated.

The new free function needs to poison the entire storage area and place it in a quarantine queue after the memory is freed. This prevents the memory region from being allocated any time soon. Otherwise, if the memory region is reused immediately, there is no way to detect incorrect accesses to the recently freed memory. The size of the quarantine queue determines how long the memory region is in quarantine, and the larger it is the better its capability of detecting UAF errors!

By default, both the malloc and free functions log their call stacks to provide more detailed information in the error reports. The call stack for malloc is kept in the red zone to the left of the allocated memory, so a large red zone can retain more call stack frames. The call stack for free is stored at the beginning of the allocated memory region itself.

Integrated into the GCC compiler, the source code for the ASan runtime library replacement is located in the libsanitizer subdirectory libsanitizer/asan/*, and the resulting runtime library is compiled as libasan.so.

Application Examples

ASan is very easy to use. The following is an example of an Ubuntu Linux 20.4 + GCC 9.3.0 system running on an x86_64 virtual machine to demonstrate the ability to detect various memory access errors.

Test Cases

As shown below, the test program writes seven functions, each introducing a different error type. The function names are cross-referenced with the error types one by one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
* PakcteMania https://www.packetmania.net
*
* gcc asan-test.c -o asan-test -fsanitize=address -g
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
/* #include <sanitizer/lsan_interface.h> */

int ga[10] = {1};

int global_buffer_overflow() {
return ga[10];
}

void heap_leak() {
int* k = (int *)malloc(10*sizeof(int));
return;
}

int heap_use_after_free() {
int* u = (int *)malloc(10*sizeof(int));
u[9] = 10;
free(u);
return u[9];
}

int heap_buffer_overflow() {
int* h = (int *)malloc(10*sizeof(int));
h[0] = 10;
return h[10];
}

int stack_buffer_overflow() {
int s[10];
s[0] = 10;
return s[10];
}

int *gp;

void stack_use_after_return() {
int r[10];
r[0] = 10;
gp = &r[0];
return;
}

void stack_use_after_scope() {
{
int c = 0;
gp = &c;
}
*gp = 10;
return;
}

The test program calls the getopt library function to support a single-letter command line option that allows the user to select the type of error to be tested. The command line option usage information is as follows.

1
2
3
4
5
6
7
8
9
10
11
12
$ ./asan-test

Test AddressSanitizer
usage: asan-test [ -bfloprs ]

-b heap buffer overflow
-f heap use after free
-l heap memory leak
-o global buffer overflow
-p stack use after scope
-r stack use after return
-s stack buffer overflow

The GCC compile command for the test program is simple, just add two compile options

  • -fsanitize=address: activates the ASan tool
  • -g: enable debugging and keep debugging information

OOB Test

For Heap OOB error, the run result is

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ ./asan-test -b
=================================================================
==57360==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x604000000038 at pc 0x55bf46fd64ed bp 0x7ffced908dc0 sp 0x7ffced908db0
READ of size 4 at 0x604000000038 thread T0
#0 0x55bf46fd64ec in heap_buffer_overflow /home/zixi/coding/asan-test.c:34
#1 0x55bf46fd6a3f in main /home/zixi/coding/asan-test.c:88
#2 0x7fd16f6560b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x55bf46fd628d in _start (/home/zixi/coding/asan-test+0x128d)

0x604000000038 is located 0 bytes to the right of 40-byte region [0x604000000010,0x604000000038)
allocated by thread T0 here:
#0 0x7fd16f92ebc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x55bf46fd646c in heap_buffer_overflow /home/zixi/coding/asan-test.c:32
#2 0x55bf46fd6a3f in main /home/zixi/coding/asan-test.c:88
#3 0x7fd16f6560b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/zixi/coding/asan-test.c:34 in heap_buffer_overflow
Shadow bytes around the buggy address:
0x0c087fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c087fff8000: fa fa 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa
0x0c087fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
...
==57360==ABORTING

Referring to the heap-buffer-overflow function implementation, you can see that it requests 40 bytes of memory to hold 10 32-bit integers. However, on the return of the function, the code overruns to read the data after the allocated memory. As the above run log shows, the program detects a Heap OOB error and aborts immediately. ASan reports the name of the source file and line number asan-test.c:34 where the error occurred, and also accurately lists the original allocation function call stack for dynamically allocated memory. The "SUMMARY" section of the report also prints the shadow memory data corresponding to the address in question (observe the lines marked by =>). The address to be read is 0x604000000038, whose mapped shadow memory address 0x0c087fff8007 holds the negative value 0xfa (poisoned and not addressable). Because of this, ASan reports an error and aborts the program.

The Stack OOB test case is shown below. ASan reports an out-of-bounds read error for a local object. Since the local variables are located in the stack space, the starting line number asan-test.c:37 of the function stack_buffr_overflow is listed. Unlike the Heap OOB report, the shadow memory poisoning values for the front and back redzone of the local variable are different, with the previous Stack left redzone being 0xf1 and the later Stack right redzone being 0xf3. Using different poisoning values (both negative after 0x80) helps to quickly distinguish between the different error types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ ./asan-test -s
=================================================================
==57370==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f1cf5044058 at pc 0x55d8b7e9d601 bp 0x7ffc830c29e0 sp 0x7ffc830c29d0
READ of size 4 at 0x7f1cf5044058 thread T0
#0 0x55d8b7e9d600 in stack_buffer_overflow /home/zixi/coding/asan-test.c:40
#1 0x55d8b7e9daec in main /home/zixi/coding/asan-test.c:108
#2 0x7f1cf87760b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x55d8b7e9d28d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f1cf5044058 is located in stack of thread T0 at offset 88 in frame
#0 0x55d8b7e9d505 in stack_buffer_overflow /home/zixi/coding/asan-test.c:37

This frame has 1 object(s):
[48, 88) 's' (line 38) <== Memory access at offset 88 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/zixi/coding/asan-test.c:40 in stack_buffer_overflow
Shadow bytes around the buggy address:
0x0fe41ea007b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0fe41ea00800: f1 f1 f1 f1 f1 f1 00 00 00 00 00[f3]f3 f3 f3 f3
0x0fe41ea00810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00830: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
...
==57370==ABORTING

The following Global OOB test result also clearly shows the error line asan-test.c:16, the global variable name ga and its definition code location asan-test.c:13:5, and you can also see that the global object has a red zone poisoning value of 0xf9.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ ./asan-test -o
=================================================================
==57367==ERROR: AddressSanitizer: global-buffer-overflow on address 0x564363ea4048 at pc 0x564363ea1383 bp 0x7ffc0d6085d0 sp 0x7ffc0d6085c0
READ of size 4 at 0x564363ea4048 thread T0
#0 0x564363ea1382 in global_buffer_overflow /home/zixi/coding/asan-test.c:16
#1 0x564363ea1a6c in main /home/zixi/coding/asan-test.c:98
#2 0x7f8cb43890b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x564363ea128d in _start (/home/zixi/coding/asan-test+0x128d)

0x564363ea4048 is located 0 bytes to the right of global variable 'ga' defined in 'asan-test.c:13:5' (0x564363ea4020) of size 40
SUMMARY: AddressSanitizer: global-buffer-overflow /home/zixi/coding/asan-test.c:16 in global_buffer_overflow
Shadow bytes around the buggy address:
0x0ac8ec7cc7b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0ac8ec7cc800: 00 00 00 00 00 00 00 00 00[f9]f9 f9 f9 f9 f9 f9
0x0ac8ec7cc810: 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x0ac8ec7cc820: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x0ac8ec7cc830: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 00 00 00 00
0x0ac8ec7cc840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
...
==57367==ABORTING

Note that in this example, the global array int ga[10] = {1}; is initialized, what happens if it is uninitialized? Change the code slightly

1
2
3
4
5
6
int ga[10];

int global_buffer_overflow() {
ga[0] = 10;
return ga[10];
}

Surprisingly, ASan does not report the obvious Global OOB error here. Why?

The reason has to do with the way GCC treats global variables. The compiler treats functions and initialized variables as Strong symbols, while uninitialized variables are Weak symbols by default. Since the definition of weak symbols may vary from source file to source file, the size of the space required is unknown. The compiler cannot allocate space for weak symbols in the BSS segment, so it uses the COMMON block mechanism so that all weak symbols share a COMMON memory region, thus ASan cannot insert the red zone. During the linking process, after the linker reads all the input target files, it can determine the size of the weak symbols and allocate space for them in the BSS segment of the final output file.

Fortunately, GCC's -fno-common option turns off the COMMON block mechanism, allowing the compiler to add all uninitialized global variables directly to the BSS segment of the target file, also allowing ASan to work properly. This option also disables the linker from merging weak symbols, so the linker reports an error directly when it finds a compiled unit with duplicate global variables defined in the target file.

This is confirmed by a real test. Modify the GCC command line for the previous code segment

1
gcc asan-test.c -o asan-test -fsanitize=address -fno-common -g

then compile, link, and run. ASan successfully reported the Global OOB error.

UAF Test

The following is a running record of UAF error detection. Not only is the information about the code that went wrong reported here, but also the call stack of the original allocation and free functions of the dynamic memory is given. The log shows that the memory was allocated by asan-test.c:25, freed at asan-test.c:27, and yet read at asan-test.c:28. The shadow memory data printed later indicates that the data filled is negative 0xfd, which is also the result of the poisoning of the memory after it is freed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ ./asan-test -f
=================================================================
==57363==ERROR: AddressSanitizer: heap-use-after-free on address 0x604000000034 at pc 0x558b4a45444e bp 0x7ffccf4ca790 sp 0x7ffccf4ca780
READ of size 4 at 0x604000000034 thread T0
#0 0x558b4a45444d in heap_use_after_free /home/zixi/coding/asan-test.c:28
#1 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#2 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x558b4a45428d in _start (/home/zixi/coding/asan-test+0x128d)

0x604000000034 is located 36 bytes inside of 40-byte region [0x604000000010,0x604000000038)
freed by thread T0 here:
#0 0x7fc7ccc637cf in __interceptor_free (/lib/x86_64-linux-gnu/libasan.so.5+0x10d7cf)
#1 0x558b4a454412 in heap_use_after_free /home/zixi/coding/asan-test.c:27
#2 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#3 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

previously allocated by thread T0 here:
#0 0x7fc7ccc63bc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x558b4a4543bd in heap_use_after_free /home/zixi/coding/asan-test.c:25
#2 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#3 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: heap-use-after-free /home/zixi/coding/asan-test.c:28 in heap_use_after_free
Shadow bytes around the buggy address:
0x0c087fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c087fff8000: fa fa fd fd fd fd[fd]fa fa fa fa fa fa fa fa fa
0x0c087fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
...
==57363==ABORTING

HML Test

The results of the memory leak test are as follows. Unlike the other test cases, ABORTING is not printed at the end of the output record. This is because, by default, ASan only generates a memory leak report when the program terminates (process ends). If you want to check for leaks on the fly, you can call ASan's library function __lsan_do_recoverable_leak_check, whose definition is located in the header file sanitizer/lsan_interface.h.

1
2
3
4
5
6
7
8
9
10
11
$ ./asan-test -l
=================================================================
==57365==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x7f06b85b1bc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x5574a8bcd3a0 in heap_leak /home/zixi/coding/asan-test.c:20
#2 0x5574a8bcda5d in main /home/zixi/coding/asan-test.c:94
#3 0x7f06b82d90b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s).

UAS Test

See the stack_use_after_scope function code, where the memory unit holding the local variable c is written outside of its scope. The test log accurately reports the line number line 54 where the variable is defined and the location of the incorrect writing code asan-test.c:57:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
./asan-test -p
=================================================================
==57368==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7f06f0a9b020 at pc 0x56121a7548d9 bp 0x7ffd1de0d050 sp 0x7ffd1de0d040
WRITE of size 4 at 0x7f06f0a9b020 thread T0
#0 0x56121a7548d8 in stack_use_after_scope /home/zixi/coding/asan-test.c:57
#1 0x56121a754a7b in main /home/zixi/coding/asan-test.c:101
#2 0x7f06f42cd0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x56121a75428d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f06f0a9b020 is located in stack of thread T0 at offset 32 in frame
#0 0x56121a7547d0 in stack_use_after_scope /home/zixi/coding/asan-test.c:52

This frame has 1 object(s):
[32, 36) 'c' (line 54) <== Memory access at offset 32 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope /home/zixi/coding/asan-test.c:57 in stack_use_after_scope
Shadow bytes around the buggy address:
0x0fe15e14b5b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0fe15e14b600: f1 f1 f1 f1[f8]f3 f3 f3 00 00 00 00 00 00 00 00
0x0fe15e14b610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
...
==57368==ABORTING

UAR Test

The UAR test has its peculiarities. Because the stack memory of a function is reused immediately after it returns, to detect local object access errors after return, a "pseudo-stack" of dynamic memory allocation must be set up, for details check the relevant Wiki page of ASan4. Since this algorithm change has some performance impact, ASan does not detect UAR errors by default. If you really need to, you can set the environment variable ASAN_OPTIONS to detect_stack_use_after_return=1 before running. The corresponding test logs are as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ export ASAN_OPTIONS=detect_stack_use_after_return=1
$ env | grep ASAN
ASAN_OPTIONS=detect_stack_use_after_return=1
$ ./asan-test -r
=================================================================
==57369==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f5493e93030 at pc 0x55a356890ac9 bp 0x7ffd22c5cf30 sp 0x7ffd22c5cf20
READ of size 4 at 0x7f5493e93030 thread T0
#0 0x55a356890ac8 in main /home/zixi/coding/asan-test.c:105
#1 0x7f54975c50b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#2 0x55a35689028d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f5493e93030 is located in stack of thread T0 at offset 48 in frame
#0 0x55a356890682 in stack_use_after_return /home/zixi/coding/asan-test.c:45

This frame has 1 object(s):
[48, 88) 'r' (line 46) <== Memory access at offset 48 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-return /home/zixi/coding/asan-test.c:105 in main
Shadow bytes around the buggy address:
0x0feb127ca5b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0feb127ca600: f5 f5 f5 f5 f5 f5[f5]f5 f5 f5 f5 f5 f5 f5 f5 f5
0x0feb127ca610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
...
==57369==ABORTING

ASan supports many other compiler flags and runtime environment variable options to control and tune the functionality and scope of the tests. For those interested please refer to the ASan flags Wiki page5.

A zip archive of the complete test program is available for download here: asan-test.c.gz


  1. AddressSanitizer Wiki↩︎

  2. Clang 13 documentation: ADDRESSSANITIZER↩︎

  3. Serebryany, K.; Bruening, D.; Potapenko, A.; Vyukov, D. "AddressSanitizer: a fast address sanity checker". In USENIX ATC, 2012↩︎

  4. AddressSanitizerUseAfterReturn↩︎

  5. AddressSanitizerFlags↩︎