Address Sanitizer
Memory errors are common in C and C++ and can be hard to debug because they often manifest far from their source. For example, writing past the end of an array might corrupt data that isn't relied on until a completely different part of the program's execution.
Address sanitizer is a runtime tool that identifies memory errors at their source and makes debugging much simpler. This is an essential tool for C and C++ software development.
Address sanitizer is available for gcc/clang on linux and msvc on windows. To use it, simply pass the flag -fsanitize=address
to the compiler.
IMPORTANT
Make sure to turn on debug symbols with -g
for gcc/clang and -Zi
for msvc.
Example
Consider the following buggy program:
#include <stdlib.h>
#include <stdio.h>
int sum(int* array, int n) {
int sum = 0;
for(int i = 0; i < n; i++) {
sum += array[i];
}
return sum;
}
int main() {
int* array = malloc(100 * sizeof(int));
for(int i = 0; i < 100; i++) {
array[i] = i;
}
printf("sum: %d\n", sum(array, 200));
}
This program contains an error on line 17
: The wrong length is passed to sum
resulting in out of bounds memory access in that function.
Running this program normally produces surprising results, e.g.:
sum: 139047
If we turn on address sanitizer by passing -g -fsanitize=address
we get the following output when running our program:
=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6140000001d0 at pc 0x000000401211 bp 0x7ffe93ef4030 sp 0x7ffe93ef4028
READ of size 4 at 0x6140000001d0 thread T0
#0 0x401210 in sum /app/example.c:7
#1 0x4012e4 in main /app/example.c:17
#2 0x7131c5a29d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#3 0x7131c5a29e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#4 0x4010e4 in _start (/app/output.s+0x4010e4) (BuildId: 67a16a963c4044ff390d49491a3b5152485a6b09)
0x6140000001d0 is located 0 bytes after 400-byte region [0x614000000040,0x6140000001d0)
allocated by thread T0 here:
#0 0x7131c5df403f in malloc (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0xdc03f) (BuildId: 53df075b42b04e0fd573977feeb6ac6e330cfaaa)
#1 0x401238 in main /app/example.c:13
#2 0x7131c5a29d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
SUMMARY: AddressSanitizer: heap-buffer-overflow /app/example.c:7 in sum
Shadow bytes around the buggy address:
0x613fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x613fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x614000000000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x614000000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x614000000100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x614000000180: 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa
0x614000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x614000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x614000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x614000000380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x614000000400: 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
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==1==ABORTING
There's a lot of output here so let's break it down:
- The first two lines tell us that an address sanitizer error has occurred. More specifically, they tell us that a heap-buffer-overflow happened in the main thread (
T0
) when reading4
bytes at a specific address:==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6140000001d0 ... READ of size 4 at 0x6140000001d0 thread T0
- The next lines are the most important: They provide a stack trace showing the exact line where things went wrong and how the program got there. In this case, it tells us the error happened on line
7
insidesum
andsum
was called frommain
on line17
:While the memory error occurred on line#0 0x401210 in sum /app/example.c:7 #1 0x4012e4 in main /app/example.c:17 ...
7
, the linesum += array[i];
is itself is not a bug. Nor is anything else about thesum
function. Rather we must look in the caller to understand why this error has happened insum
. - The next set of lines tell us information about the memory error in relation to the relevant block of memory and where this block of memory originated from. Specifically, it tells us we're accessing memory immediately after a
400
-byte block of memory allocated by the main thread inmain
on line17
:This information about the allocation combined with information about where the program read past the end of the allocation is sufficient for us to understand this memory error and pinpoint how our program went wrong.0x6140000001d0 is located 0 bytes after 400-byte region [0x614000000040,0x6140000001d0) allocated by thread T0 here: #0 0x7131c5df403f in malloc (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0xdc03f) (BuildId: 53df075b42b04e0fd573977feeb6ac6e330cfaaa) #1 0x401238 in main /app/example.c:13
- Lastly, address sanitizer prints out a view of the memory in the program to illustrate the block of memory and where the attempted bad access was within the program memory:This bit of output is usually not necessary to understand memory errors or particularly helpful, however, there are cases where it can be helpful. It is, at the very least, quite pretty.
Shadow bytes around the buggy address: 0x613fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x613fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x614000000000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x614000000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x614000000100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x614000000180: 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa 0x614000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x614000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x614000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x614000000380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x614000000400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Try it yourself: Example
Other Sanitizers
In addition to address sanitizer, a handful of other sanitizers exist:
- Undefined behavior sanitizer (
-fsanitize=undefined
): Checks some array subscript out of bounds, signed integer overflow, misaligned and null pointers, etc. - Memory sanitizer (
-fsanitize=memory
): Checks for use of uninitialized memory - Thread sanitizer (
-fsanitize=thread
): Checks for data races