Buffer overflow adalah bug ketika program menulis melebihi batas buffer yang dialokasikan. C tidak melakukan bounds checking otomatis pada akses array—programmer harus memastikan setiap buf[i] berada dalam batas yang valid. Buffer overflow adalah salah satu bug klasik keamanan di C; banyak worm terkenal (Code Red, Slammer, Blaster) mengeksploitasi overflow di server. Untungnya, AddressSanitizer (ASan) dapat mendeteksi overflow baik di stack maupun heap secara akurat. Pada chapter ini kita akan membuat program dengan stack buffer overflow, lalu memverifikasi ASan menangkapnya.

Apa itu Buffer Overflow #

Buffer overflow terjadi ketika program menulis (atau membaca) melebihi batas buffer. Klasifikasinya:

  • Stack buffer overflow — menulis melebihi array lokal, dapat menimpa return address di stack. Inilah vektor klasik serangan stack smashing.
  • Heap buffer overflow — menulis melebihi blok heap, dapat merusak metadata allocator atau menimpa data alokasi tetangga.
  • Off-by-one error — menulis tepat satu byte melebihi batas, sering akibat salah memilih < vs <= atau lupa byte \0 penutup string.

Penyebab umum buffer overflow:

  • strcpy tanpa cek panjang — strcpy(dst, src) menyalin sampai \0 di src; bila src lebih panjang dari dst, terjadi overflow.
  • gets — fungsi berbahaya yang membaca input tanpa batas, sudah dihapus di C11.
  • Loop dengan <= — for (i = 0; i <= n; i++) buf[i] = ...; menulis n+1 elemen padahal buf hanya berisi n.
  • Aritmetika pointer salah — p + n di mana n melebihi ukuran blok yang dialokasikan.

Mekanisme kerja: di C, akses buf[i] diterjemahkan ke *(buf + i)—CPU hanya menghitung alamat dan mengaksesnya. Tidak ada pemeriksaan apakah alamat itu berada dalam buffer yang valid. Inilah alasan overflow sering tidak terdeteksi dalam testing singkat—data yang ditimpa mungkin tidak terpakai saat itu, dan bug baru muncul kemudian.

Demo: Stack Buffer Overflow #

Kita buat buffer 8 byte dan mencoba menulis 16 byte. Tanpa ASan, program mungkin berjalan “normal” atau crash secara misterius; dengan ASan, kita mendapatkan laporan tepat.

c
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char buf[8] = "hello";   /* buffer 8 byte, isi "hello\0" */
    printf("Sebelum overflow: buf = \"%s\", alamat = %p\n", buf, (void*)buf);

    /* BUG: menulis 16 byte ke buffer 8 byte -> buffer overflow */
    for (int i = 0; i < 16; i++) {
        buf[i] = 'A';
    }
    printf("Setelah overflow: buf = \"%s\"\n", buf);
    return 0;
}

Kompilasi dengan ASan dan jalankan:

bash
gcc -std=c11 -Wall -Wextra -fsanitize=address,undefined \
    -o /tmp/test_overflow /home/z/my-project/work/tests/test_overflow.c
/tmp/test_overflow

Output yang Dihasilkan:

text
test_overflow.c:13:12: runtime error: index 8 out of bounds for type 'char [8]'
test_overflow.c:13:16: runtime error: store to address 0x7f9186500028 with insufficient space for an object of type 'char'
=================================================================
==24610==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f9186500028 at pc 0x564c9bdef396 bp 0x7ffc056caba0 sp 0x7ffc056cab98
WRITE of size 1 at 0x7f9186500028 thread T0
    #0 0x564c9bdef395 in main (/tmp/test_overflow+0x1395)
    #1 0x7f9188ad4ca7 in (/lib/x86_64-linux-gnu/libc.so.6+0x29ca7)
    #2 0x7f9188ad4d64 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29d64)

Address 0x7f9186500028 is located in stack of thread T0 at offset 40 in frame
    #0 0x564c9bdef1f8 in main (/tmp/test_overflow+0x11f8)

  This frame has 1 object(s):
    [32, 40) 'buf' (line 6) <== Memory access at offset 40 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow (/tmp/test_overflow+0x1395) in main

ASan mendeteksi WRITE of size 1 ke alamat 0x...028 yang berada di luar buffer buf (yang berada di offset 32-39 dalam stack frame). UBSan (yang terintegrasi dengan flag -fsanitize=undefined) juga melaporkan “index 8 out of bounds for type ‘char [8]’"—deteksi tingkat bahasa C yang lebih dini.

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (char buf[8] = "hello";): deklarasi buffer 8 byte di stack. String literal "hello" (5 huruf + \0 = 6 byte) muat di buffer; sisanya 2 byte otomatis diisi \0. Inisialisasi ini sah.
  2. Baris 7 (printf("Sebelum overflow: buf = \"%s\", alamat = %p\n", buf, (void*)buf);): mencetak isi dan alamat buf. Operasi aman karena buf masih dalam batas yang valid.
  3. Baris 11 (for (int i = 0; i < 16; i++)): loop iterasi dari 0 hingga 15. Buffer buf hanya berisi 8 elemen (indeks 0-7), sehingga akses buf[8] hingga buf[15] jelas overflow.
  4. Baris 12 (buf[i] = 'A';): penulisan karakter 'A' ke buf[i]. Saat i = 8, ASan menghentikan program karena akses ke alamat di luar buf.
  5. Baris 14 (printf("Setelah overflow: buf = \"%s\"\n", buf);): tidak pernah dieksekusi karena ASan membatalkan program di baris 12 saat i = 8.

Mencegah Buffer Overflow #

Strategi pencegahan:

  1. Gunakan strncpy, snprintf, strlcpy alih-alih strcpy, sprintf. Fungsi-fungsi ini menerima parameter ukuran dan tidak akan menulis melebihi batas.
  2. Validasi input — selalu periksa panjang input sebelum menyalin ke buffer.
  3. Gunakan fgets(buf, sizeof(buf), fp) alih-alih gets(buf)—fgets menerima parameter ukuran.
  4. Compile dengan -D_FORTIFY_SOURCE=2 — gcc/glibc menyediakan hardening yang menggantikan strcpy/memcpy dengan versi yang memeriksa batas.
  5. Stack protector (-fstack-protector-strong) — gcc menyisipkan canary di antara buffer dan return address; bila canary berubah, program membatalkan diri sendiri.
  6. Gunakan ASan di CI — testing rutin dengan ASan menangkap overflow yang tidak terlihat.

Pencegahan: strncpy sebagai Pengganti strcpy #

Sebagai kontras, kita tunjukkan cara menyalin string secara aman dengan strncpy yang menerima parameter ukuran. Buffer tujuan tetap berukuran 8 byte, tetapi strncpy tidak akan menulis melebihi batas.

c
#include <stdio.h>
#include <string.h>

int main(void) {
    char buf[8];
    const char *src = "Belajar C";   /* 9 char + '\0' = 10 byte */

    /* strncpy: menyalin maksimal sizeof(buf)-1 byte, sisanya '\0' */
    strncpy(buf, src, sizeof(buf) - 1);
    buf[sizeof(buf) - 1] = '\0';   /* pastikan null-terminated */

    printf("src: \"%s\" (panjang %zu)\n", src, strlen(src));
    printf("buf: \"%s\" (panjang %zu, ukuran buffer %zu)\n",
           buf, strlen(buf), sizeof(buf));

    return 0;
}

Output yang Dihasilkan:

text
src: "Belajar C" (panjang 9)
buf: "Belajar" (panjang 7, ukuran buffer 8)

String "Belajar C" (9 karakter) tidak muat di buffer 8 byte. strncpy menyalin maksimal sizeof(buf) - 1 = 7 karakter (yaitu "Belajar") dan menghentikan penyalinan—tidak ada overflow. Baris buf[sizeof(buf) - 1] = '\0' memastikan string selalu null-terminated bahkan bila strncpy memotong. Inilah cara menghindari buffer overflow di C: selalu sertakan parameter ukuran dan hormati batas tersebut.