Chapter Integer Overflow di Section Pemrograman Numerik membahas luapan sebagai masalah kebenaran angka. Di sini kita melihat sisi lain yang lebih dingin: bagi penyerang, luapan bukan sekadar angka salah — ia alat untuk menipu perhitungan ukuran dan indeks. Alokasi yang dihitung dari input bisa diarahkan mengalokasikan kecil, pemeriksaan batas bisa dibuat “lolos” lewat bungkus, dan konversi tipe yang diam-diam memotong nilai bisa mengubah data terpercaya menjadi sampah. Chapter ini memetakan pola-pola itu, lalu menyodorkan alat pertahanannya: aritmetika yang memeriksa dirinya sendiri dan parsing yang menolak format buruk.
Overflow sebagai Pintu Masuk #
Pola serangan paling khas menyerang urutan “hitung ukuran, alokasikan, salin”. Bila ukuran dihitung dari input — n * sizeof(Elemen) — dan perhitungannya meluap, hasil bungkus bisa jadi angka kecil: alokasi sukses, lalu penyalinan sebanyak n elemen asli menulis jauh melampaui buffer kecil tadi. Serangan ini menutup semua pertahanan berbasis “alokasi sudah sesuai ukuran” sekaligus.
Keluarga tipe tak bertanda punya jebakan bungkusnya sendiri yang sah menurut standar. Konversi std::size_t j = 0; --j; menghasilkan 18446744073709551615 — angka raksasa, bukan -1. Perulangan klasik for (std::size_t i = panjang - 1; i >= 0; --i) dengan panjang == 0 karenanya berputar sangat lama pada indeks gila: luapan tidak terjadi, yang salah adalah logika yang mengandalkan “angka minus” pada tipe yang tak mengenalnya. Pada int, pola serupa justru UB — dua wajah dari masalah yang sama, dan keduanya perlu dijaga.
Konversi Diam yang Menggeser Batas #
Konversi implisit antar tipe bilangan bulat terjadi tanpa suara: int lebar 32 bit dimasukkan ke short memotong byte atas, size_t dimasukkan ke int bisa menjadi negatif, dan perbandingan campuran tanda membuat angka negatif dianggap raksasa. Compiler menolong sejauh terlihat:
tanda.cpp:3:23: warning: comparison of integer expressions of different
signedness: 'int' and 'std::vector<int>::size_type' {aka 'long unsigned int'}
[-Wsign-compare]
3 | for (int i = 0; i < v.size(); ++i) { // int vs size_type
| ~~^~~~~~~~~~Peringatan itu bisa dibereskan dengan menjadikan variabel loop std::size_t — atau lebih baik lagi, dengan range-for yang menghilangkan indeks sama sekali. Untuk pemotongan nilai (int ke short, size_t ke int), flag -Wconversion memberi pemeriksaan yang lebih ketat daripada -Wall -Wextra; aktifkan di build pengujian dan tangani tiap laporan dengan konversi eksplisit plus cek rentang.
Aritmetika yang Memeriksa Dirinya #
Menulis pemeriksaan luapan manual (“apakah a > max - b?”) rawan salah dan susah dibaca. GCC menyediakan builtin yang menghitung dan memeriksa sekaligus: __builtin_add_overflow, __builtin_sub_overflow, dan __builtin_mul_overflow menerima operand apa pun tipe bilangan bulat, menghitung dengan benar (tanpa UB), mengisi hasil bila aman, dan mengembalikan true bila meluap:
// hitung-aman.cpp — aritmetika yang memeriksa dirinya dan parsing yang menolak sampah.
#include <charconv>
#include <iostream>
#include <limits>
#include <string>
#include <string_view>
int main() {
// 1. Ukuran alokasi: n * ukuran diperiksa sebelum hasilnya dipakai.
std::size_t n = 1'000'000;
std::size_t total = 0;
bool aman = !__builtin_mul_overflow(n, sizeof(double), &total);
std::cout << "n * 8 = " << (aman ? std::to_string(total) : std::string("meluap")) << "\n";
// 2. Penjumlahan dekat batas: ditolak, bukan dibungkus diam-diam.
int a = std::numeric_limits<int>::max();
int hasil = 0;
if (__builtin_add_overflow(a, 1, &hasil)) {
std::cout << "a + 1 meluap, operasi ditolak\n";
}
// 3. Parsing teks dari luar: from_chars menolak format salah dan rentang jauh.
for (std::string_view teks : {"42", " 7", "42abc", "99999999999999999999"}) {
int nilai = 0;
auto [ptr, ec] = std::from_chars(teks.data(), teks.data() + teks.size(), nilai);
if (ec == std::errc{}) {
std::cout << "'" << teks << "' -> " << nilai << ", sisa: '"
<< std::string_view(ptr, static_cast<std::size_t>(teks.data() + teks.size() - ptr))
<< "'\n";
} else {
std::cout << "'" << teks << "' -> ditolak: "
<< (ec == std::errc::invalid_argument ? std::string("format salah")
: std::string("di luar rentang"))
<< "\n";
}
}
return 0;
}Output yang Dihasilkan:
n * 8 = 8000000
a + 1 meluap, operasi ditolak
'42' -> 42, sisa: ''
' 7' -> ditolak: format salah
'42abc' -> 42, sisa: 'abc'
'99999999999999999999' -> ditolak: di luar rentangBedah baris-per-baris:
- Baris 10–11 (
__builtin_mul_overflow(n, sizeof(double), &total)) — perhitungan ukuran alokasi diperiksa sebelum dipakai;amanbernilaitruedi sini karena 8.000.000 jauh dari batassize_t. Bilandari input dan hasilnya meluap, kita berhenti sebelumnewdipanggil. - Baris 16–19 (
__builtin_add_overflow(a, 1, &hasil)) —aberada tepat diint::max(), dan builtin menolak penjumlahannya: tidak ada bungkus, tidak ada UB, hanya cabang penolakan yang bisa diprogram. - Baris 23–24 (
std::from_chars(...)) — parsing murni: tidak melompati spasi, tidak menerima suffix, tidak melempar exception, tidak bergantung locale. Sisa karakter yang tidak terbaca dilaporkan lewatptr. - Baris 25–31 (cabang
ec) — hasil parsing diperiksa dua arah:invalid_argumentuntuk format yang salah total (" 7", spasi awal ditolak),result_out_of_rangeuntuk angka yang terlalu besar untukint, dan parsial ("42abc") hanya diterima bila sisa memang diharapkan.
Builtin ini milik GCC dan Clang; standar C++26 menyiapkan padanan saturasi (std::add_sat dan teman-temannya). Sampai saat itu, pola builtin adalah cara paling ringkas untuk menutup CWE-190 di jalur yang menghitung ukuran dan indeks.
Parsing Angka dari Luar: from_chars Dulu #
Parsing adalah gerbang masuk angka ke program, dan std::from_chars dirancang sebagai gerbang yang ketat: cepat, tanpa alokasi, tanpa exception, dan laporan kegagalannya eksplisit. Pasangannya, std::stoi, jauh lebih longgar — ia melompati spasi awal dan berhenti di karakter pertama yang bukan digit, sehingga "17 tahun" menghasilkan 17 tanpa protes:
#include <iostream>
#include <string>
int main() {
int usia = std::stoi("17 tahun"); // berhenti di spasi, tanpa error
std::cout << "usia = " << usia << "\n";
}usia = 17Untuk input yang harus murni angka, gunakan from_chars dan tolak parsial dengan membandingkan ptr terhadap akhir masukan. Bila format longgar memang diizinkan, std::stoi boleh dipakai dengan dua syarat: dibungkus try (bisa melempar std::invalid_argument/std::out_of_range) dan parameter kedua pos diperiksa untuk mengetahui sampai di mana teks benar-benar terbaca. Babak berikutnya memperluas gerbang itu ke seluruh input program.
Referensi: cppreference — std::from_chars , cppreference — std::stoi .