AddressSanitizer (ASan) dan UndefinedBehaviorSanitizer (UBSan) adalah tool bawaan GCC dan Clang yang menginstrumentasi binary untuk mendeteksi bug memori (buffer overflow, use-after-free, double-free, leak) dan undefined behavior (signed overflow, invalid shift, null dereference) saat runtime. ASan jauh lebih cepat dari Valgrind (overhead ~2× vs 20×) dan terintegrasi langsung dengan compiler. Bab ini menuntun kita mengaktifkan ASan/UBSan, membaca output error, dan mengonfigurasi behavior via environment variable.
1. Mengaktifkan ASan dan UBSan #
ASan diaktifkan dengan flag -fsanitize=address saat compile dan link. UBSan dengan -fsanitize=undefined. Kombinasi keduanya direkomendasikan untuk testing.
gcc -std=c11 -Wall -Wextra -g -O1 \
-fsanitize=address,undefined \
-fno-omit-frame-pointer \
-o myapp myapp.cPenjelasan flag:
| Flag | Kegunaan |
|---|---|
-fsanitize=address | Aktifkan ASan |
-fsanitize=undefined | Aktifkan UBSan (kombinasi banyak check) |
-fsanitize=address,undefined | Kombinasi ASan + UBSan |
-g | Sertakan debug info (DWARF) untuk stack trace bermakna |
-O1 | Optimasi ringan—ASan bekerja lebih akurat dengan optimasi minimal |
-fno-omit-frame-pointer | Pertahankan frame pointer untuk stack trace akurat |
-fno-common | Hindari common symbol (default GCC 10+, pakai untuk safety) |
2. Program Demo: Heap Buffer Overflow dan Use-After-Free #
Program berikut berisi tiga bug yang akan ditangkap ASan: heap buffer overflow, use-after-free, dan stack use-after-scope.
/* File: asan1.c - Demo AddressSanitizer */
#include <stdio.h>
#include <stdlib.h>
static void heap_buffer_overflow(void) {
char *buf = malloc(8);
buf[8] = 'X'; /* write out-of-bound */
printf("[heap oob] buf = %p\n", (void*)buf);
free(buf);
}
static void use_after_free(void) {
int *p = malloc(sizeof(int));
*p = 42;
free(p);
int v = *p; /* use after free */
printf("[uaf] v = %d\n", v);
}
static void stack_use_after_scope(void) {
int *p;
{
int local = 99;
p = &local;
}
/* 'local' sudah keluar scope, *p invalid */
printf("[stack uas] *p = %d\n", *p);
}
int main(void) {
heap_buffer_overflow();
use_after_free();
stack_use_after_scope();
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -fsanitize=address -g -O1 -fno-omit-frame-pointer \
-o asan1 asan1.c
./asan1Output yang Dihasilkan (ASan mendeteksi error pertama dan abort):
=================================================================
==32506==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000018 at pc 0x55ce8a8c722b bp 0x7ffd68b0cd80 sp 0x7ffd68b0cd78
WRITE of size 1 at 0x502000000018 thread T0
#0 0x55ce8a8c722a in heap_buffer_overflow /tmp/cc/test/asan1.c:7
#1 0x55ce8a8c7472 in main /tmp/cc/test/asan1.c:31
#2 0x7fb5c61f0ca7 (/lib/x86_64-linux-gnu/libc.so.6+0x29ca7)
#3 0x7fb5c61f0d64 in __libc_start_main ...
#4 0x55ce8a8c7110 in _start ...
0x502000000018 is located 0 bytes after 8-byte region [0x502000000010,0x502000000018)
allocated by thread T0 here:
#0 0x7fb5c64afc57 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x55ce8a8c71ea in heap_buffer_overflow /tmp/cc/test/asan1.c:6
#2 0x55ce8a8c7472 in main /tmp/cc/test/asan1.c:31
SUMMARY: AddressSanitizer: heap-buffer-overflow /tmp/cc/test/asan1.c:7 in heap_buffer_overflow
Shadow bytes around the buggy address:
0x501ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x502000000000: fa fa 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000080: 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
==32506==ABORTINGMari kita telaah baris-baris output di atas:
heap-buffer-overflow on address 0x502000000018: ASan mendeteksi write ke alamat0x502000000018, yang berada tepat 0 byte setelah region 8-byte[0x502000000010,0x502000000018). Bufferbuf[8]valid hanya sampai index 7; index 8 sudah di luar.- Stack trace
#0 ... #4: ASan menampilkan call stack lengkap dengan nomor baris source. Frame#0adalah lokasi error (line 7buf[8] = 'X'); frame#1adalah pemanggil (main di line 31). - “allocated by thread T0 here”: ASan menampilkan juga lokasi
mallocyang mengalokasikan buffer. Berguna saat bug terjadi jauh setelah alokasi—kita bisa melacak ke mana buffer dibuat. Shadow bytes around the buggy address: Map shadow memory di sekitar alamat error. Karakterfa(heap left redzone) menandai area di luar buffer yang seharusnya tidak diakses. Karakter00menandai area yang valid (addressable).Shadow byte legend: Legenda arti setiap kode shadow. Berguna untuk membaca shadow dump—misalnyafdberarti freed heap region (use-after-free),f8berarti stack use after scope.
3. Program Demo: UBSan #
UBSan mendeteksi UB tanpa mengubah executable secara agresif—lebih cepat dari ASan dan tidak mengubah layout memori.
/* File: asan2.c - Demo UBSan */
#include <stdio.h>
#include <limits.h>
int main(void) {
int a = INT_MAX;
int b = a + 1; /* signed overflow (UB) */
printf("INT_MAX + 1 = %d\n", b);
int x = 1;
int s = 32;
int r = x << s; /* shift terlalu besar (UB) */
printf("1 << 32 = %d\n", r);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -fsanitize=undefined -g -o asan2 asan2.c
./asan2Output yang Dihasilkan (UBSan menampilkan runtime error ke stderr, tetapi program tetap berjalan):
asan2.c:7:9: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
asan2.c:12:15: runtime error: shift exponent 32 is too large for 32-bit type 'int'
INT_MAX + 1 = -2147483648
1 << 32 = 1Perbedaan penting dengan ASan: UBSan tidak memerintahkan program abort—hanya melaporkan ke stderr dan melanjutkan. Untuk membuat UBSan halt pada error pertama:
export UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1
./asan24. Deteksi Memory Leak dengan LSan #
ASan terintegrasi dengan LeakSanitizer (LSan) yang mendeteksi memory leak saat program exit. Program berikut sengaja tidak mem-free dua alokasi.
/* File: asan4.c - Demo leak detection via ASan LSan */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char *leak1 = malloc(100);
char *leak2 = malloc(50);
snprintf(leak1, 100, "data 1");
snprintf(leak2, 50, "data 2");
printf("leak1 = %s, leak2 = %s\n", leak1, leak2);
/* sengaja tidak free */
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -fsanitize=address -g -o asan4 asan4.c
./asan4Output yang Dihasilkan (program berjalan normal dulu, lalu ASan melaporkan leak saat exit):
leak1 = data 1, leak2 = data 2
=================================================================
==32562==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 100 byte(s) in 1 object(s) allocated from:
#0 0x7fe2eb402c57 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x55e3043771aa in main /tmp/cc/test/asan4.c:6
#2 0x7fe2eb143ca7 (/lib/x86_64-linux-gnu/libc.so.6+0x29ca7)
SUMMARY: AddressSanitizer: 100 byte(s) leaked in 1 allocation(s).5. Environment Variable Konfigurasi #
ASan dan UBSan dapat dikonfigurasi saat runtime via environment variable.
| Variabel | Efek |
|---|---|
ASAN_OPTIONS=halt_on_error=0 | Jangan abort, lanjutkan (untuk melihat semua error) |
ASAN_OPTIONS=detect_leaks=0 | Nonaktifkan LSan (untuk fokus ke bug memori) |
ASAN_OPTIONS=abort_on_error=1 | Panggil abort() agar core dump dihasilkan |
ASAN_OPTIONS=print_legend=1 | Tampilkan legenda shadow byte |
ASAN_OPTIONS=verbosity=1 | Cetak konfigurasi saat startup |
ASAN_OPTIONS=log_path=/tmp/asan.log | Tulis output ASan ke file |
UBSAN_OPTIONS=halt_on_error=1 | Halt pada UB pertama |
UBSAN_OPTIONS=print_stacktrace=1 | Tampilkan stack trace UB |
UBSAN_OPTIONS=silence_unsigned_overflow=1 | Abaikan unsigned overflow (tidak UB) |
Contoh pemakaian:
ASAN_OPTIONS=halt_on_error=0:detect_leaks=1:log_path=/tmp/asan \
./asan1Output ASan akan ditulis ke /tmp/asan.<pid> (satu file per proses).
6. Kombinasi dengan CMake #
Untuk proyek CMake, tambahkan opsi ASan di CMakeLists.txt:
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_UBSAN "Enable UBSan" OFF)
if(ENABLE_ASAN)
add_compile_options(-fsanitize=address -g -O1 -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
endif()
if(ENABLE_UBSAN)
add_compile_options(-fsanitize=undefined)
add_link_options(-fsanitize=undefined)
endif()Aktifkan saat configure:
cmake -DENABLE_ASAN=ON -DENABLE_UBSAN=ON ..
make
./myapp7. Troubleshooting #
Error: ASan runtime does not come first in initial library list #
ASan harus di-link pertama. Compile ulang dengan flag -fsanitize=address di kedua sisi compile dan link. Bila memakai library shared yang juga di-compile dengan ASan, pastikan preload library ASan:
LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./myappBinary sangat besar dan lambat #
ASan menambah overhead memori dan ukuran. Hanya pakai saat testing, bukan produksi. Bila masih terlalu lambat, coba ASan mode light:
gcc -fsanitize=address -fno-sanitize-recover=all \
-o myapp myapp.cAtau gunakan ASAN_OPTIONS=quarantine_size_mb=64 untuk mengurangi shadow memory.
Warning: ASan doesn't fully support makecontext/swapcontext #
Program memakai coroutine (mis. libco, boost.context) yang ASan tidak handle sempurna. Tambahkan:
ASAN_OPTIONS=handle_segv=0:allow_user_segv_handler=1 ./appAtau disable ASan check di area spesifik:
__attribute__((no_sanitize("address")))
void coro_switch(...) { ... }Deteksi miss di kode dengan custom allocator #
Bila program memakai allocator sendiri (mis. memory pool), ASan tidak melacak alokasi internal. Tandai area dengan API ASan:
#include <sanitizer/asan_interface.h>
void *my_pool_alloc(size_t n) {
void *p = malloc(n + 32);
__asan_poison_memory_region(p, 32); /* redzone kiri */
__asan_poison_memory_region(p + 32 + n, 32); /* redzone kanan */
return p + 32;
}