Out-of-bounds access (OOB) adalah akses array atau pointer ke indeks yang berada di luar rentang yang valid. Berbeda dengan buffer overflow yang biasanya merujuk pada penulisan melebihi batas, OOB mencakup baik baca maupun tulis. C tidak melakukan bounds checking otomatis; arr[i] diterjemahkan ke *(arr + i) tanpa pemeriksaan apakah i berada dalam batas. Hasilnya: baca OOB mengembalikan data acak dari memori tetangga (data leak), tulis OOB menimpa data yang bukan milik array (corruption). Pada chapter ini kita akan mendemonstrasikan OOB untuk baca dan tulis, lalu memverifikasi ASan menangkapnya.

Karakteristik Out-of-Bounds Access #

OOB dibagi dalam dua kategori:

  • Baca OOB (int x = arr[10]) — mengembalikan nilai acak dari memori tetangga. Bisa berupa data lokal fungsi lain, return address, atau bahkan data dari alokasi heap lain. Bug ini sering tidak terdeteksi—program “berjalan normal” dengan data sampah.
  • Tulis OOB (arr[10] = 99) — menimpa data di luar array. Konsekuensinya bergantung pada apa yang ditimpa: variabel lokal (data corruption), return address (keamanan kritis), metadata allocator (heap corruption).

Tipe berdasarkan lokasi:

  • Stack OOB — akses ke array lokal di luar batas. Dapat menimpa variabel lokal lain atau return address.
  • Heap OOB — akses ke blok heap di luar ukuran yang dialokasikan. Dapat menimpa metadata allocator atau data alokasi tetangga.

Mekanisme kerja: di C, arr[i] di mana arr bertipe int * diterjemahkan ke *(arr + i). Kompilasi menghasilkan instruksi yang menghitung arr + i * sizeof(int), lalu membaca/menulis memori di alamat tersebut. CPU tidak tahu apakah alamat itu berada dalam batas arr; CPU hanya menjalankan instruksi. Inilah alasan OOB sering tidak terdeteksi sampai dampaknya terlihat—data yang ditimpa mungkin tidak langsung dipakai.

Demo: Stack OOB Baca #

Kita akses arr[5] dan arr[10] pada array int arr[5]. Indeks yang valid hanya 0-4.

c
#include <stdio.h>

int main(void) {
    int arr[5] = {10, 20, 30, 40, 50};

    printf("Array arr (5 elemen):\n");
    for (int i = 0; i < 5; i++) {
        printf("  arr[%d] = %d (alamat %p)\n", i, arr[i], (void*)&arr[i]);
    }

    /* Akses di luar batas: indeks 5 (valid hanya 0-4) */
    printf("\nAkses arr[5]  (out-of-bounds baca): %d\n", arr[5]);
    printf("Akses arr[10] (out-of-bounds baca): %d\n", arr[10]);

    return 0;
}

Output yang Dihasilkan (tanpa ASan):

text
Array arr (5 elemen):
  arr[0] = 10 (alamat 0x7ffe1b6c69d0)
  arr[1] = 20 (alamat 0x7ffe1b6c69d4)
  arr[2] = 30 (alamat 0x7ffe1b6c69d8)
  arr[3] = 40 (alamat 0x7ffe1b6c69dc)
  arr[4] = 50 (alamat 0x7ffe1b6c69e0)

Akses arr[5]  (out-of-bounds baca): 0
Akses arr[10] (out-of-bounds baca): -1976759128

Perhatikan bahwa program “berjalan normal” — tidak ada crash, tidak ada error. Tetapi nilai yang dikembalikan (0 untuk arr[5], -1976759128 untuk arr[10]) adalah data sampah dari memori tetangga. Nilai ini tidak deterministik—berubah di setiap eksekusi tergantung kondisi stack. Inilah sebabnya OOB baca sangat berbahaya: program membuat keputusan berdasarkan data yang tidak valid, dan bug ini sulit dideteksi tanpa alat khusus.

Dengan ASan, hasilnya berbeda total:

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

Output yang Dihasilkan (dengan ASan):

text
Array arr (5 elemen):
  arr[0] = 10 (alamat 0x7fdf31a00020)
  arr[1] = 20 (alamat 0x7fdf31a00024)
  arr[2] = 30 (alamat 0x7fdf31a00028)
  arr[3] = 40 (alamat 0x7fdf31a0002c)
  arr[4] = 50 (alamat 0x7fdf31a00030)
test_oob.c:12:61: runtime error: index 5 out of bounds for type 'int [5]'
test_oob.c:12:5: runtime error: load of address 0x7fdf31a00034 with insufficient space for an object of type 'int'
=================================================================
==24721==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fdf31a00034
READ of size 4 at 0x7fdf31a00034 thread T0
    #0 0x55a562574605 in main (/tmp/test_oob_asan+0x2605)
    #1 0x7fdf33e62ca7 in (/lib/x86_64-linux-gnu/libc.so.6+0x29ca7)

Address 0x7fdf31a00034 is located in stack of thread T0 at offset 52 in frame
    #0 0x55a562574208 in main (/tmp/test_oob_asan+0x2208)

  This frame has 1 object(s):
    [32, 52) 'arr' (line 4) <== Memory access at offset 52 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow (/tmp/test_oob_asan+0x2605) in main

ASan melaporkan “index 5 out of bounds for type ‘int [5]’” dengan UBSan dan “stack-buffer-overflow” dengan ASan—dua lapisan deteksi. Alamat 0x...034 berada tepat di offset 52 dari stack frame, sedangkan arr hanya menempati offset 32-51. Akses ke offset 52 jelas di luar arr.

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (int arr[5] = {10, 20, 30, 40, 50};): deklarasi array 5 elemen di stack. Indeks yang valid: 0-4.
  2. Baris 6-8 (for (int i = 0; i < 5; i++)): loop akses sah—i dari 0 hingga 4, semua dalam batas.
  3. Baris 12 (printf("... arr[5] ...", arr[5]);): BUG baca OOB. arr[5] berada di byte ke-20 dari awal array, di luar wilayah arr. Tanpa ASan, program membaca memori tetangga—sering padding atau variabel lokal lain—dan mengembalikan nilai acak.
  4. Baris 13 (printf("... arr[10] ...", arr[10]);): BUG baca OOB yang lebih jauh. arr[10] berada 40 byte setelah akhir arr, sangat jauh dari wilayah yang valid. Nilai -1976759128 adalah interprestasi byte-byte di sana sebagai int.

Demo: Heap OOB Tulis #

OOB juga terjadi pada array heap. Kita alokasikan malloc(5 * sizeof(int)) lalu menulis arr[5] dan arr[6].

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

int main(void) {
    int *arr = malloc(5 * sizeof(int));
    if (arr == NULL) return 1;
    for (int i = 0; i < 5; i++) arr[i] = (i + 1) * 10;

    printf("Isi array heap (5 elemen):\n");
    for (int i = 0; i < 5; i++) {
        printf("  arr[%d] = %d\n", i, arr[i]);
    }

    /* BUG: tulis out-of-bounds -- arr hanya punya 5 elemen */
    arr[5] = 999;     /* heap buffer overflow write */
    arr[6] = 888;

    printf("\nSetelah write OOB:\n");
    printf("  arr[5] = %d (tidak seharusnya diakses)\n", arr[5]);

    free(arr);
    return 0;
}

Output yang Dihasilkan (dengan ASan):

text
Isi array heap (5 elemen):
  arr[0] = 10
  arr[1] = 20
  arr[2] = 30
  arr[3] = 40
  arr[4] = 50
=================================================================
==24729==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x503000000054 at pc 0x558a00b8d4fb bp 0x7ffcc8eca960 sp 0x7ffcc8eca958
WRITE of size 4 at 0x503000000054 thread T0
    #0 0x558a00b8d4fa in main (/tmp/test_oob2_asan+0x24fa)
    #1 0x7f2050cbcca7 in (/lib/x86_64-linux-gnu/libc.so.6+0x29ca7)

0x503000000054 is located 0 bytes after 20-byte region [0x503000000040,0x503000000054)
allocated by thread T0 here:
    #0 0x7f205158ec57 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x558a00b8d21f in main (/tmp/test_oob2_asan+0x221f)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/tmp/test_oob2_asan+0x24fa) in main

ASan mendeteksi WRITE of size 4 di alamat 0x...054 yang berada 0 byte setelah wilayah 20-byte [0x...040, 0x...054). Wilayah ini dialokasikan oleh malloc dan dimaksudkan untuk 5 elemen int (20 byte). Akses ke arr[5] menulis di byte ke-20—tepat di luar batas. Tanpa ASan, penulisan ini akan menimpa metadata allocator atau data alokasi tetangga—heap corruption yang bisa menyebabkan crash acak berjam-jam kemudian.