Buffer overflow sudah berada di CWE Top 25 selama dua puluh tahun. Kita punya stack canary, ASLR, DEP, control-flow integrity, dan bahasa memory-safe, namun mereka terus muncul di kode kritis. Alasannya sederhana: setiap mitigasi tersebut berjalan di software, dan software bisa di-bypass, dikonfigurasi salah, atau sama sekali tidak digunakan.

Bagaimana jika hardware itu sendiri menolak membiarkanmu membaca melewati akhir array?

Mengapa mitigasi software terus kalah

Buffer overflow terjadi ketika program menulis melewati batas region memori yang dialokasikan. Di C, ini trivial karena pointer hanyalah address. Compiler dan CPU mempercayai bahwa programmer tahu apa yang dilakukannya. Jika kamu mengalokasikan 64 byte dan menulis ke offset 80, CPU akan melakukannya. CPU tidak tahu di mana buffer berakhir.

Mitigasi software mencoba menangkap ini setelah kejadian. Stack canary menempatkan nilai yang diketahui sebelum return address dan memeriksanya sebelum kembali. ASLR merandomisasi layout memori untuk membuat eksploit lebih sulit dibuat. Control-flow integrity membatasi di mana indirect jump boleh mendarat. Ini membantu, tapi bersifat probabilistik atau tidak lengkap. Attacker yang bertekad dengan cukup waktu dan infoleak biasanya bisa mengakalinya.

Bahasa memory-safe seperti Rust menyelesaikan masalah di level bahasa, tapi tidak membantu miliaran baris C dan C++ yang sudah berjalan di produksi. Menulis ulang kernel Linux, OpenSSL, atau backend legacy-mu dalam Rust tidak akan terjadi dalam dekade ini. Kita perlu defense yang melindungi kode yang ada tanpa memerlukan rewrite.

Apa yang sebenarnya CHERI lakukan pada pointer

CHERI, singkatan dari Capability Hardware Enhanced RISC Instructions, adalah ekstensi ISA yang dikembangkan di University of Cambridge dan kini didukung oleh Arm dalam bentuk Morello. Ia mengubah apa itu pointer.

Di sistem 64-bit konvensional, pointer adalah 64 bit: hanya sebuah address. Di sistem CHERI, pointer menjadi capability 128-bit atau 256-bit. Bit ekstra menyimpan metadata: base address alokasi, bounds (seberapa jauh mencapainya), dan permissions (read, write, execute). Capability tersebut dilindungi oleh hardware integrity tag sehingga manipulasi metadata membuatnya invalid.

Saat kamu mengompilasi kode untuk CHERI, malloc tidak hanya mengembalikan address. Ia mengembalikan capability yang bounds-nya tepat sesuai ukuran yang diminta. Saat kamu menaikkan pointer tersebut, hardware memeriksa setiap akses terhadap bounds tersebut. Jika kamu mencoba membaca atau menulis di luar bounds, CPU memunculkan synchronous exception. Tidak ada cara untuk memalsukan capability dengan bounds yang lebih lebar. Hardware sama sekali tidak mengizinkannya.

Ini perbedaan kuncinya. Software bounds checking menyisipkan checks di sekitar operasi memori, yang bisa dioptimasi compiler, dilupakan programmer, dan di-bypass attacker. Hardware bounds checking terjadi pada setiap load dan store, tanpa syarat, tanpa menambah instruction ke programmu.

Bagaimana enforce bounds bekerja di level instruction

Begini tampilan buffer overflow khas di C:

#include <string.h>

void vulnerable(char *input) {
    char buf[64];
    strcpy(buf, input);  // No bounds check. Classic overflow.
}

Di arsitektur normal, ini merusak stack. Di CHERI, buf bukan raw address. Ia adalah capability yang bounds-nya tepat 64 byte. Saat strcpy mencoba menulis melewati byte 64, hardware memunculkan exception Capability Bounds Violation. Program langsung crash pada instruction yang bersalah secara tepat.

Perlindungan yang sama berlaku untuk alokasi heap:

#include <stdlib.h>
#include <cheri.h>  // CHERI intrinsics header

void heap_example(void) {
    char *buf = malloc(32);
    // buf is a capability with base=buf, length=32

    // This works fine
    buf[0] = 'A';
    buf[31] = 'Z';

    // This traps at the hardware level
    buf[32] = 'X';  // Capability bounds violation
}

Crash-nya presisi. Kamu mendapatkan instruction yang tepat, capability yang tepat, dan bounds yang tepat yang dilanggar. Mendebug ini lebih mudah daripada mengejar stack frame yang corrupted tiga frame kemudian.

CHERI juga mencegah pointer forgery. Kamu tidak bisa meng-cast integer ke pointer dan mulai dereferensi memori arbitrer. Hardware hanya mengenali capability yang dibuat oleh instruction yang legitimate seperti malloc, stack allocation, atau capability derivation yang eksplisit. Cast integer-to-pointer menghasilkan nilai untagged yang trap saat digunakan.

Masalah kompatibilitas itu nyata

Jika CHERI sebagus itu, mengapa tidak setiap server menjalankannya? Karena mengubah apa itu pointer merusak asumsi yang diandalkan oleh dekade kode C.

Masalah pertama adalah ukuran. Capability lebih besar dari raw pointer. Di Morello, capability adalah 128 bit plus tag 1-bit yang disimpan secara terpisah oleh hardware. Ini meningkatkan penggunaan memori untuk struktur data yang banyak pointer-nya. Linked list atau tree yang penuh pointer menjadi jauh lebih besar. Untuk banyak aplikasi overhead-nya single-digit persentase, tapi untuk workload pointer-chasing bisa lebih buruk.

Masalah kedua adalah casting. Kode C meng-cast pointer ke uintptr_t, melakukan aritmatika, dan meng-cast kembali. Di CHERI, uintptr_t sebenarnya adalah tipe capability, bukan integer. Kode yang mengasumsikan bisa melakukan integer math arbitrer pada nilai pointer akan gagal compile atau trap saat runtime. Solusinya biasanya menggunakan ptrdiff_t untuk offset dan menerapkannya dengan cheri_address_set, tapi itu memerlukan perubahan source.

Masalah ketiga adalah ekosistem. Hardware CHERI langka. Board Arm Morello ada tapi bukan commodity server. CheriBSD dan Linux yang di-enable CHERI cukup matang untuk menjalankan software nyata, tapi kebanyakan distribusi Linux tidak mengirimkan package CHERI. Container registry-mu, CI runner-mu, dan cloud provider-mu belum mendukungnya.

Kamu bisa mencobanya hari ini tanpa custom silicon

Kamu tidak memerlukan board Morello untuk bereksperimen dengan CHERI. Proyek CHERI memelihara dukungan QEMU, jadi kamu bisa menjalankan userspace CHERI di host Linux atau macOS apa pun.

Begini cara mendapatkan toolchain CHERI dan menjalankan contoh sederhana di QEMU:

# Clone the CHERI SDK setup
$ git clone https://github.com/CTSRD-CHERI/cheribuild.git
$ cd cheribuild

# Build the CheriBSD disk image and QEMU
$ ./cheribuild.py cheribsd-riscv64-purecap -d
$ ./cheribuild.py qemu -d

# Boot CheriBSD in QEMU
$ ./cheribuild.py run-cheribsd-riscv64

Setelah berada di dalam CheriBSD, compiler-nya adalah clang dengan dukungan target CHERI. Kamu mengompilasi dengan bounds checking yang di-enforce secara default:

$ clang -o test test.c
$ ./test
# Buffer overflows trap immediately with a clear message

Jika ingin menguji software yang ada, cheribuild bisa mengompilasi package populer dari FreeBSD ports dengan dukungan CHERI. Tim CHERI telah mem-porting OpenSSH, nginx, PostgreSQL, dan bagian besar dari sistem base FreeBSD. Banyak program berjalan tanpa perubahan. Yang rusak biasanya karena unsafe pointer casts atau asumsi tentang ukuran pointer.

Untuk start yang lebih cepat tanpa QEMU, University of Cambridge juga menerbitkan image Docker dengan toolchain LLVM CHERI yang sudah terinstal sebelumnya. Kamu bisa mengompilasi binary CHERI dan memeriksa assembly capability-aware yang dihasilkan tanpa harus boot OS lengkap.

Apa artinya hardware memory safety untuk codebase yang ada

CHERI bukan pengganti menulis kode yang aman. Ia adalah safety net untuk kode yang sudah kamu miliki. Sistem CHERI yang menjalankan C legacy masih akan memiliki logic bug, race condition, dan error use-after-free. Tapi ia tidak akan memiliki buffer overflow yang menimpa return address, merusak heap metadata yang bersebelahan, atau membocorkan secret melewati batas alokasi.

Progresinya sudah terlihat. Arm telah mengintegrasikan fitur yang diturunkan dari CHERI ke dalam Memory Tagging Extension (MTE)-nya, yang saat ini dikirimkan di perangkat Android produksi. MTE lebih kasar dari CHERI (ia men-tag granul 16-byte daripada alokasi individual), tapi menangkap banyak bug yang sama dengan overhead yang lebih rendah. CHERI penuh kemungkinan besar akan menyusul seiring dengan kematangan ekosistem.

Jika kamu memelihara kode C atau C++ yang memproses untrusted input, pertanyaannya bukan apakah hardware memory safety akan tiba. Melainkan apakah kamu akan siap ketika itu terjadi. Mulailah dengan mengaudit kode-mu untuk pointer-to-integer casts, aritmatika pointer pada objek yang tidak terkait, dan asumsi tentang sizeof(void*). Itu adalah pattern yang rusak di bawah CHERI, dan memperbaikinya membuat kode-mu lebih bersih bahkan di hardware konvensional.

Hardware akhirnya bersedia mengatakan tidak. Kita harus membiarkannya.