Episode ini membahas keamanan kode C: buffer overflow dan format string vulnerabilities beserta mitigasinya, secure coding dengan bounds checking memakai strncpy dan snprintf, perbedaan stack dan heap overflow, serta input validation dan defensive programming.

C memberi kalian kontrol penuh atas memori, dan kontrol penuh berarti tanggung jawab penuh. Episode 13 membahas kelas bug paling berbahaya dalam pemrograman C: buffer overflow dan format string vulnerabilities, yang selama bertahun-tahun menjadi penyebab exploit paling terkenal di internet.
Kabar baiknya, sebagian besar bug ini bisa dicegah dengan pola yang disiplin: memakai fungsi yang menerima ukuran buffer, memeriksa batas sebelum akses, dan memperlakukan semua input sebagai data yang tidak bisa dipercaya.
Episode ini adalah titik balik: setelah ini, kalian tidak akan lagi menulis fungsi string yang rentan, dan akan membangun kebiasaan defensive programming yang melindungi pengguna program kalian.
Buffer overflow terjadi ketika data ditulis melebihi kapasitas buffer, menginjak memori di sekitarnya. Contoh klasik:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
char buffer[16];
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
return 0;
}
EOFstrcpy(buffer, argv[1]) menyalin tanpa memeriksa panjang. Jika argv[1] lebih dari 15 karakter, data menimpa memori di sekitar buffer, bisa mengubah alamat kembali fungsi. Penyerang yang mengontrol isi overflow bisa mengeksekusi kode sewenang-wenang.
Sistem modern memasang pertahanan berlapis. Kompiler menambahkan stack protector yang mendeteksi penimpaan; kernel menandai halaman stack dan heap sebagai non-executable. Aktifkan pertahanan saat kompilasi:
gcc -fstack-protector-strong -D_FORTIFY_SOURCE=2 program.c -o programBendera -fstack-protector-strong menyisipkan pemeriksaan canary pada fungsi yang memakai buffer. Pertahanan ini memperlambat exploit, tetapi tetap wajib menulis kode yang tidak rentan sejak awal — mitigasi bukan pengganti kode aman.
Ketika string pengguna dipakai langsung sebagai format string printf, penyerang bisa membaca atau menulis memori. Kode berikut rentan:
printf(buffer);
printf("%s", buffer);Panggilan printf(buffer) memperlakukan isi buffer sebagai format string, sehingga token seperti %x dan %n dieksekusi. Versi aman printf("%s", buffer) selalu memperlakukan buffer sebagai data. Aturan emas: format string harus selalu literal, tidak pernah berasal dari input.
Serangan format string bisa membocorkan isi stack dan menulis ke memori arbitrer melalui %n. Alat seperti -Wformat-security dan clang-tidy memperingatkan ketika format string bukan literal. Kompilasi dengan -Wformat=2 -Wformat-security sejak awal untuk menangkap pola ini lebih dini.
Fungsi string lama menyalin tanpa batas. Penggantinya menerima ukuran buffer sebagai parameter:
#include <stdio.h>
#include <string.h>
int main(void) {
char nama[16];
strncpy(nama, "Arman Dwi Pangestu", sizeof(nama) - 1);
nama[sizeof(nama) - 1] = '\0';
char pesan[64];
snprintf(pesan, sizeof(pesan), "Halo %s", nama);
puts(pesan);
return 0;
}
EOFstrncpy(nama, "...", sizeof(nama) - 1) membatasi jumlah karakter yang disalin, dan baris berikutnya menjamin karakter nol di akhir. snprintf menulis paling banyak sizeof(pesan) byte dan selalu mengakhiri dengan nol. Kedua fungsi ini adalah standar emas untuk menyalin dan memformat string dengan aman.
Fungsi gets tidak memiliki parameter ukuran dan tidak pernah boleh dipakai. Untuk membaca baris, selalu gunakan fgets dengan ukuran buffer. Saat membaca angka dengan scanf, batasi lebar field, misalnya %19s untuk string berukuran 20.
Semua input eksternal — argumen, file, jaringan — harus dianggap tidak dapat dipercaya. Langkah pertamanya adalah memvalidasi sebelum memproses:
if (argc != 2) {
fprintf(stderr, "gunakan: program <nilai>\n");
return 1;
}
long nilai = strtol(argv[1], NULL, 10);
if (nilai < 0 || nilai > 1000) {
fprintf(stderr, "nilai di luar jangkauan\n");
return 1;
}strtol(argv[1], NULL, 10) mengonversi string ke bilangan dengan pemeriksaan error yang bisa dideteksi lewat errno dan pointer posisi akhir. Validasi jangkauan dilakukan sebelum nilai dipakai. Pola ini mencegah input aneh mencapai logika internal.
Tiga prinsip yang selalu diterapkan: periksa setiap nilai kembalian, periksa batas setiap akses array, dan asumsikan fungsi lain bisa gagal. Assert memperkuat asumsi internal, sanitizers di episode 11 memvalidasi kebenaran saat pengembangan, dan uji dengan input ekstrem menjadi kebiasaan rutin.
Warning
Validasi bukan hanya tentang keamanan. Input yang tidak terduga juga membuat program crash dengan cara yang tidak elegan. Menolak input sejak awal, dengan pesan jelas, jauh lebih baik daripada gagal di tengah pemrosesan.
Inti yang harus dibawa pulang:
Di episode 14 selanjutnya kita akan membahas cryptography dan data protection — konsep dasar cryptography di C, library OpenSSL dan libsodium untuk enkripsi, hashing, HMAC, dan random number generation, hingga secure key handling dan manajemen data sensitif.