Introduction
AddressSanitizer (ASan) is a memory error detector for C/C++ programming languages.
AddressSanitizer is a part of LLVM starting with version 3.1 and a part of GCC starting with version 4.8
According to AddressSanitizer — Clang 18.0.0git documentation (llvm.org),
AddressSanitizer is a fast memory error detector. It consists of a compiler instrumentation module and a run-time library. The tool can detect the following types of bugs:
- Out-of-bounds accesses to heap, stack and globals
- Use-after-free
- Use-after-return (clang flag
-fsanitize-address-use-after-return=(never|runtime|always)
default:runtime
)- Enable with:
ASAN_OPTIONS=detect_stack_use_after_return=1
(already enabled on Linux). - Disable with:
ASAN_OPTIONS=detect_stack_use_after_return=0
.
- Enable with:
- Use-after-scope (clang flag
-fsanitize-address-use-after-scope
) - Double-free, invalid free
- Memory leaks (experimental)
Typical slowdown introduced by AddressSanitizer is 2x
In this blog, I explain how to compile a program and use Address Sanitizer. I also explain several Address Sanitizer logs, which indicates common error.
Using Address Sanitizer
Basic commands
- using Clang command
clang -o myfile myfile.c -Wall -g -fsanitize=address
- using gcc command
g++ --std=c++17 -Wall -Werror -pedantic -g -fsanitize=address -myfile.cpp -o myfile.exe
- using CMake flag: the following FLAGs should be used:
CXXFLAGS = --std=c++17 -Wall -Werror -pedantic -g -fsanitize=address -fsanitize=undefined -D_GLIBCXX_DEBUG
ASan flags
-fsanitize=memory
-fsanitize=memory
is NOT ASan flag !!!
AddressSanitizer does not detect reads of uninitialized memory. MemorySanitizer was developed for that. It needs a separate compilation and run. The flag -fsanitize=memory
is used instead and cannot be combined with other AddressSanitizer flags.
-fsanitize=address
-fsanitize=address
flag is required in the compile command to include AddressSanitizer tool.
-fsanitize=undefined
The following program has no compilation error nor run time error. It may print warning. However, it behavior is undefined because there no defined behavior for the case of adding value larger than integer's limit.
#include <limits.h>
int main(int argc, char **argv) {
int x = INT_MAX + 5; //INT_MAX is used with limits.h
}
If the -fsanitize=undefined
flag is used, the compiler recognizes the error at line 4:
/app/example.cpp:4:19: runtime error: signed integer overflow: 2147483647 + 5 cannot be represented in type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /app/example.cpp:4:19 in
This option could be used with -fsanitize=address
Interpreting Address Sanitizer
Shadow bytes
According to AddressSanitizer shadow bytes | Microsoft Learn
Every 8 bytes in your application's virtual address space can be described using one shadow byte.
One shadow byte describes how many bytes are currently accessible as follows:
- 0 means all 8 bytes
- 1-7 means one to seven bytes
- Negative numbers encode context for the runtime to use for reporting diagnostics.
Types of errors
Memory leak (missing free/delete)
Memory leak occurs when programmer fails to delete
a pointer (after new
) or free
a memory section (after malloc
)
#include <stdlib.h>
void *p;
int main() {
p = malloc(7);
p = 0; // The memory is leaked here.
return 0;
}
The following log indicates:
- error type is: memory leaks
- missing free is done for malloc operation at line 6
=================================================================
==1==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 7 byte(s) in 1 object(s) allocated from:
#0 0x7f5b7856a9df in malloc (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0xda9df) (BuildId: 5ce9c09d3612315d01d50bcaafaea176e7ddab77)
#1 0x401173 in main /app/example.cpp:6
#2 0x7f5b77ee9082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
SUMMARY: AddressSanitizer: 7 byte(s) leaked in 1 allocation(s).
Attempting to free address which was not 'malloc'
The error sounds strainghtforward. However, it mistake that causes it is common. The following program program illustrate such scenario.
- a pointer was created and allocated memory with new keyword.
- that pointer is mistakenly assigned to another address.
delete
pointer causes error because the address was not allocated bymalloc
.
#include <iostream>
#include <string>
using namespace std;
int main(){
int a = 5;
int *ptr = new int();
cout<<*ptr<<endl;
ptr = &a; // pointer is reassigned to region that was not "malloc"
cout<<*ptr<<endl;
delete ptr; // error
}
ASan log indicates the delete
action which is problematic, but does not show the line that explicitly causes such error.
=================================================================
==1==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x7f76a3700020 in thread T0
#0 0x55649c84e9ed in operator delete(void*) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:152:3
#1 0x55649c8506b7 in main /app/example.cpp:12:5
#2 0x7f76a5db3082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
#3 0x55649c77530d in _start (/app/output.s+0x2c30d)
Address 0x7f76a3700020 is located in stack of thread T0 at offset 32 in frame
#0 0x55649c85047f in main /app/example.cpp:5
This frame has 1 object(s):
[32, 36) 'a' (line 7) <== 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: bad-free /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:152:3 in operator delete(void*)
==1==ABORTING
Delete mismatch
This C++ program (Source: HOWTO: Use Address Sanitizer | Ohio Supercomputer Center (osc.edu)) distinguishes delete
and delete[]
. Without correct usage, delete mismatch error occurs
- The memory pointed to by "cstr" was allocated with new[].
- An array allocation must be deleted with the delete[] operator, not "delete".
#include <iostream>
#include <cstring>
int main(int argc, const char *argv[]) {
char *cstr = new char[100];
strcpy(cstr, "Hello World");
std::cout << cstr << std::endl;
delete cstr;
return 0;
}
The error log indicates that:
- error occurs at line 9.
- error type is alloc-dealloc-mismatch.
- it happens when operation DELETE is invoked.
=================================================================
==1==ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new [] vs operator delete) on 0x60b0000000f0
#0 0x7f07f79ef418 in operator delete(void*, unsigned long) (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0xdc418) (BuildId: 5ce9c09d3612315d01d50bcaafaea176e7ddab77)
#1 0x40121c in main /app/example.cpp:9
#2 0x7f07f736c082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
#3 0x4010fd in _start (/app/output.s+0x4010fd) (BuildId: 638a5c8b3ccb6ea6773000c2320a7ad85f586b88)
0x60b0000000f0 is located 0 bytes inside of 100-byte region [0x60b0000000f0,0x60b000000154)
allocated by thread T0 here:
#0 0x7f07f79ee678 in operator new[](unsigned long) (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0xdb678) (BuildId: 5ce9c09d3612315d01d50bcaafaea176e7ddab77)
#1 0x4011ce in main /app/example.cpp:5
#2 0x7f07f736c082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
SUMMARY: AddressSanitizer: alloc-dealloc-mismatch (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0xdc418) (BuildId: 5ce9c09d3612315d01d50bcaafaea176e7ddab77) in operator delete(void*, unsigned long)
Stack buffer over flow
Stack overflow occurs when the program tries to access memory outside of the stack memory it was allocated.
- common cause of stack overflow is excessive call of recursive functions.
- Stack overflow errors can also occur if too much data is assigned to the variables in the stack frame (Source: What is a stack overflow error? (techtarget.com))
- Stack overflow errors can also occur if too much data is assigned to the variables in the stack frame. Array variables are particularly susceptible to stack overflow errors, especially if no logic has been implemented to prevent excess data from being written to the array.
While a program can crash in the case of stack overflow, it may also keep running in the case of such error. The following program will not have error on compilation nor run time. It returns some dummy, invalid value:
#include <iostream>
using namespace std;
int main(int argc, char **argv) {
int stack_array[10];
stack_array[1] = 0;
std::cout<<stack_array[13]<<std::endl;
return 0;
}
When the compiling option -fsanitize=address
is used, we have the following error:
- error type is stack-buffer-overflow
- the error occurs while reading
stack_array
at line 6 - the error occurs with variable
stack_array
created at line 4
==1==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fbc48500054 at pc 0x563a92b945e4 bp 0x7fffcc993c30 sp 0x7fffcc993c28
READ of size 4 at 0x7fbc48500054 thread T0
#0 0x563a92b945e3 in main /app/example.cpp:6:14
#1 0x7fbc4aa44082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
#2 0x563a92ab930d in _start (/app/output.s+0x2c30d)
Address 0x7fbc48500054 is located in stack of thread T0 at offset 84 in frame
#0 0x563a92b9447f in main /app/example.cpp:3
This frame has 1 object(s):
[32, 72) 'stack_array' (line 4) <== Memory access at offset 84 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 /app/example.cpp:6:14 in main
Shadow bytes around the buggy address:
0x7fbc484ffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fbc484ffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fbc484ffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fbc484fff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fbc484fff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7fbc48500000: f1 f1 f1 f1 00 00 00 00 00 f3[f3]f3 f3 f3 f3 f3
0x7fbc48500080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fbc48500100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fbc48500180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fbc48500200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fbc48500280: 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 buffer under flow
Heap buffer over flow
Heap use after free
Heap use after free error (dangling pointer error) occurs when accessing the area that the pointer pointed to after the pointer is deleted/free.
The following C program (Source: HOWTO: Use Address Sanitizer | Ohio Supercomputer Center (osc.edu)) illustrates the erroneous scenario:
- char pointer is created and free.
- the same char pointer is called and the memory location that the pointer points to is accessed after it was free.
- Compiling and running the program without ASan would returns no error. The output could be undefined or simply
string is: Hello world!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[]) {
char *s = malloc(100);
free(s);
strcpy(s, "Hello world!");
printf("string is: %s\n", s);
return 0;
}
The following ASan logs indicates errors:
- occurs at line 8 when strcpy is called.
- occurs when WRITE operation is invoked. Otherwise, it could be action READ.
- the memory location is within 100 byte region, that was allocated by
malloc(100)
.
=================================================================
==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x60b0000000f0 at pc 0x7f97e87935df bp 0x7fff84689510 sp 0x7fff84688cd0
WRITE of size 13 at 0x60b0000000f0 thread T0
#0 0x7f97e87935de in __interceptor_memcpy (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0x715de) (BuildId: 5ce9c09d3612315d01d50bcaafaea176e7ddab77)
#1 0x4011d4 in main /app/example.c:8
#2 0x7f97e8554082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
#3 0x4010dd in _start (/app/output.s+0x4010dd) (BuildId: 32416b4141d61eb2a58582d81a98f7d8c06ad2de)
0x60b0000000f0 is located 0 bytes inside of 100-byte region [0x60b0000000f0,0x60b000000154)
freed by thread T0 here:
#0 0x7f97e87fb6a8 (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0xd96a8) (BuildId: 5ce9c09d3612315d01d50bcaafaea176e7ddab77)
#1 0x4011be in main /app/example.c:7
#2 0x7f97e8554082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
previously allocated by thread T0 here:
#0 0x7f97e87fc9df in malloc (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0xda9df) (BuildId: 5ce9c09d3612315d01d50bcaafaea176e7ddab77)
#1 0x4011ae in main /app/example.c:6
#2 0x7f97e8554082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
SUMMARY: AddressSanitizer: heap-use-after-free (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0x715de) (BuildId: 5ce9c09d3612315d01d50bcaafaea176e7ddab77) in __interceptor_memcpy
Stack use after return
In general, the variables you create within a function will be allocated on the stack and will be freed when the function returns. Thus accessing those variables cause error during run time.
The following program (Source: Compare tools for C and C++ error checking | Red Hat Developer) illustrates the error stack use after return:
- when
f()
exists, the local variablei
goes out of scope. - thus both
i
and pointerp
are invalid afterf()
returns. - the return value is then passed as parameter to
g()
. Thusg()
's input parameter is invalid
int *f() {
int i = 42;
int *p = &i;
return p;
}
int g(int *p) {
return *p;
}
int main() {
return g(f());
}
To be able to debug this error with ASan, the following are needed:
- setting environment variable with command
set ASAN_OPTIONS=detect_stack_use_after_return=1
. - use compiler options (by setting FLAGs on CMake or adding respective option on compiling command):
/fsanitize-address-use-after-return
The following error logs indicates:
- error type is stack-use-after-return
- occurs at line 7 because of the program tries to access variable
i

References
Address Sanitizer official document AddressSanitizer — Clang 18.0.0git documentation (llvm.org)
Overview and explaining ASan log AddressSanitizer | Microsoft Learn
Basic build command with ASan Setup Address Sanitizer | EECS 280 Tutorials (eecs280staff.github.io)
Explaining ASan options Compare tools for C and C++ error checking | Red Hat Developer
Sample programs with ASan AddressSanitizer · google/sanitizers Wiki · GitHub
What are shadow bytes c - What are "shadow bytes" in AddressSanitizer and how should I interpret them? - Stack Overflow