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.

bash
gcc -std=c11 -Wall -Wextra -g -O1 \
    -fsanitize=address,undefined \
    -fno-omit-frame-pointer \
    -o myapp myapp.c

Penjelasan flag:

FlagKegunaan
-fsanitize=addressAktifkan ASan
-fsanitize=undefinedAktifkan UBSan (kombinasi banyak check)
-fsanitize=address,undefinedKombinasi ASan + UBSan
-gSertakan debug info (DWARF) untuk stack trace bermakna
-O1Optimasi ringan—ASan bekerja lebih akurat dengan optimasi minimal
-fno-omit-frame-pointerPertahankan frame pointer untuk stack trace akurat
-fno-commonHindari 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.

c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -fsanitize=address -g -O1 -fno-omit-frame-pointer \
    -o asan1 asan1.c
./asan1

Output yang Dihasilkan (ASan mendeteksi error pertama dan abort):

text
=================================================================
==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==ABORTING

Mari kita telaah baris-baris output di atas:

  1. heap-buffer-overflow on address 0x502000000018: ASan mendeteksi write ke alamat 0x502000000018, yang berada tepat 0 byte setelah region 8-byte [0x502000000010,0x502000000018). Buffer buf[8] valid hanya sampai index 7; index 8 sudah di luar.
  2. Stack trace #0 ... #4: ASan menampilkan call stack lengkap dengan nomor baris source. Frame #0 adalah lokasi error (line 7 buf[8] = 'X'); frame #1 adalah pemanggil (main di line 31).
  3. “allocated by thread T0 here”: ASan menampilkan juga lokasi malloc yang mengalokasikan buffer. Berguna saat bug terjadi jauh setelah alokasi—kita bisa melacak ke mana buffer dibuat.
  4. Shadow bytes around the buggy address: Map shadow memory di sekitar alamat error. Karakter fa (heap left redzone) menandai area di luar buffer yang seharusnya tidak diakses. Karakter 00 menandai area yang valid (addressable).
  5. Shadow byte legend: Legenda arti setiap kode shadow. Berguna untuk membaca shadow dump—misalnya fd berarti freed heap region (use-after-free), f8 berarti 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.

c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -fsanitize=undefined -g -o asan2 asan2.c
./asan2

Output yang Dihasilkan (UBSan menampilkan runtime error ke stderr, tetapi program tetap berjalan):

text
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 = 1

Perbedaan penting dengan ASan: UBSan tidak memerintahkan program abort—hanya melaporkan ke stderr dan melanjutkan. Untuk membuat UBSan halt pada error pertama:

bash
export UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1
./asan2

4. 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.

c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -fsanitize=address -g -o asan4 asan4.c
./asan4

Output yang Dihasilkan (program berjalan normal dulu, lalu ASan melaporkan leak saat exit):

text
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.

VariabelEfek
ASAN_OPTIONS=halt_on_error=0Jangan abort, lanjutkan (untuk melihat semua error)
ASAN_OPTIONS=detect_leaks=0Nonaktifkan LSan (untuk fokus ke bug memori)
ASAN_OPTIONS=abort_on_error=1Panggil abort() agar core dump dihasilkan
ASAN_OPTIONS=print_legend=1Tampilkan legenda shadow byte
ASAN_OPTIONS=verbosity=1Cetak konfigurasi saat startup
ASAN_OPTIONS=log_path=/tmp/asan.logTulis output ASan ke file
UBSAN_OPTIONS=halt_on_error=1Halt pada UB pertama
UBSAN_OPTIONS=print_stacktrace=1Tampilkan stack trace UB
UBSAN_OPTIONS=silence_unsigned_overflow=1Abaikan unsigned overflow (tidak UB)

Contoh pemakaian:

bash
ASAN_OPTIONS=halt_on_error=0:detect_leaks=1:log_path=/tmp/asan \
    ./asan1

Output ASan akan ditulis ke /tmp/asan.<pid> (satu file per proses).

6. Kombinasi dengan CMake #

Untuk proyek CMake, tambahkan opsi ASan di CMakeLists.txt:

cmake
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:

bash
cmake -DENABLE_ASAN=ON -DENABLE_UBSAN=ON ..
make
./myapp

7. 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:

bash
LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./myapp

Binary sangat besar dan lambat #

ASan menambah overhead memori dan ukuran. Hanya pakai saat testing, bukan produksi. Bila masih terlalu lambat, coba ASan mode light:

bash
gcc -fsanitize=address -fno-sanitize-recover=all \
    -o myapp myapp.c

Atau 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:

bash
ASAN_OPTIONS=handle_segv=0:allow_user_segv_handler=1 ./app

Atau disable ASan check di area spesifik:

c
__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:

c
#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;
}