Runtime Detection of Memory Errors in Linux with AddressSanitizer
1. Introduction
Linux applications written in C and C++ often rely on manual memory management to achieve performance and low-level control. This is especially common in embedded Linux systems, where applications interact closely with hardware, manage buffers explicitly, and avoid unnecessary abstractions. While this approach is powerful, it also makes programs vulnerable to memory errors such as buffer overflows, use-after-free, and invalid pointer accesses.
Unlike logic errors, memory bugs do not always fail immediately. A program may run correctly for long periods of time before crashing, corrupting unrelated data, or failing only under specific workloads. Small changes such as adding logging, changing compiler optimizations, or running on different hardware can shift memory layouts enough to hide or reveal the problem. This makes memory-related bugs difficult to reproduce and debug using traditional techniques.
AddressSanitizer provides a practical way to detect these issues by monitoring memory accesses at runtime. Instead of waiting for visible corruption or crashes, it detects invalid memory operations as soon as they occur and reports them with detailed diagnostics.
2. Memory Errors in Linux Applications
Memory errors occur when a program accesses memory outside the bounds of allocated objects or continues to use memory after it has been freed. In user-space Linux programs, these errors commonly involve stack buffers, heap allocations, and pointer arithmetic. Because C and C++ do not perform bounds checking, such errors result in undefined behavior rather than predictable failures.
In embedded Linux systems, memory bugs often appear in code that processes raw data buffers, handles network packets, or communicates with hardware devices. A simple off-by-one error in a buffer index or a missing length check can overwrite adjacent memory. Similarly, freeing an object while another part of the program still holds a pointer to it can lead to subtle and timing-dependent failures.
These problems may go unnoticed during testing, especially when memory layouts are stable or allocations happen in a predictable order. As the codebase grows and execution paths multiply, identifying the exact source of corruption becomes increasingly difficult. This is where a runtime memory checker like AddressSanitizer becomes essential.
3. AddressSanitizer Overview and Internal Operation
AddressSanitizer works by instrumenting the program at compile time. When enabled, the compiler inserts additional checks around memory accesses and replaces standard memory allocation routines with instrumented versions. These checks allow the runtime to verify whether each access is valid.
At runtime, AddressSanitizer uses shadow memory to track the accessibility of regions in the program's address space. For every byte of application memory, the shadow memory records whether that byte is addressable, partially addressable, or invalid. Red zones are placed around heap and stack allocations so that out-of-bounds accesses are detected immediately.
When an invalid access occurs, such as reading past the end of a buffer or accessing freed memory, AddressSanitizer terminates the program and prints a detailed report. The report includes the type of memory error, the address involved, and stack traces showing where the invalid access and the corresponding allocation occurred. This information is usually sufficient to locate and fix the bug quickly.
For Example addressSanitizer rewrites the stack frame to place poisoned red zones around a local buffer. Each memory access is checked against shadow memory, which marks bytes as valid or invalid, an out-of-bounds access into a red zone is immediately detected and reported.

Because of the additional checks and memory overhead, AddressSanitizer is intended for development and testing builds only.
4. Using AddressSanitizer in Embedded Linux
AddressSanitizer is enabled at build time and runs as part of the application during execution. In embedded Linux projects it is typically used in debug builds, where the increased memory usage and reduced performance are acceptable. A common example involves a buffer overflow caused by writing past the end of a statically allocated array.
The following program contains a simple out-of-bounds write. The bug may not cause an immediate crash when built normally, but AddressSanitizer detects it reliably at runtime.
#include <stdio.h>
int main(void)
{
char buffer[16];
/* buffer overflow */
buffer[16] = 15;
return 0;
}
To detect this error, the application must be compiled with AddressSanitizer enabled. This is typically done by adding the sanitizer flag at compile time, keeping optimization low, and including debug symbols so that stack traces are meaningful.
CC ?= clang
CFLAGS ?= -O1 -g -fno-omit-frame-pointer
LDLIBS ?=
ASANFLAGS = -fsanitize=address
all: asan_demo
asan_demo: asan_demo.c
$(CC) $(CFLAGS) $(ASANFLAGS) $< -o $@ $(LDLIBS)
clean:
rm -f asan_demo
When the instrumented program is executed, AddressSanitizer detects the out-of-bounds write and aborts execution. The reported output identifies the buffer overflow, shows the exact instruction where it occurred, and provides a stack trace pointing to the faulty code. It also reports where the affected buffer was allocated, making it clear how the memory layout was violated.
Address 0x7c5845800030 is located in stack of thread T0 at offset 48 in frame
#0 0x58d5965b71d8 in main /root/address_sanitier/asan_demo.c:5
This frame has 1 object(s):
[32, 48) 'buffer' (line 6) <== Memory access at offset 48 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 /root/address_sanitier/asan_demo.c:9 in main
Shadow bytes around the buggy address:
0x7c58457ffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c58457ffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c58457ffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c58457fff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c58457fff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7c5845800000: f1 f1 f1 f1 00 00[f3]f3 00 00 00 00 00 00 00 00
0x7c5845800080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c5845800100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c5845800180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c5845800200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c5845800280: 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):
5. Conclusion
Manual memory management remains common in Linux and embedded Linux applications, but it comes with significant risk. Memory errors can remain hidden for long periods of time and surface only under specific conditions, making them difficult to diagnose. AddressSanitizer exposes these issues by detecting invalid memory accesses at runtime and reporting them with detailed context, allowing developers to fix bugs early and improve system reliability.