Keamanan Perangkat Keras

Isu kelas Spectre: cara spekulasi membocorkan data dan bentuk mitigasi yang menyertainya.

Daftar isi

Prosesor modern mengeksekusi instruksi secara spekulatif sebelum keputusan cabang jatuh, dan mekanisme itu meninggalkan jejak yang terbaca meskipun jalurnya nanti dibatalkan. Topik ini membahas cara isi cache membocorkan pola akses program, gelombang publikasi Spectre dan Meltdown yang mengungkap kelemahan tersebut pada Januari 2018, tiga kategori mitigasinya, serta simulasi side channel yang aman untuk memperlihatkan prinsipnya tanpa kode eksploit. Dasar spekulasi dan prediksi cabang sudah dibahas pada topik Prediksi Cabang dan Spekulasi, dan pembahasan di sini memakainya sebagai titik berangkat.

Cache sebagai saluran samping#

Side channel (saluran samping) adalah jalur kebocoran informasi yang tidak melewati nilai itu sendiri, melainkan melewati efek samping perhitungannya seperti waktu, konsumsi daya, atau isi cache. Cache menjadi contoh yang penting karena semua program berbagi perangkat yang sama, sedangkan keadaannya berubah setiap ada akses, sehingga aktivitas satu program menggeser keadaan yang dibaca program lain. Perubahan itu halus, tetapi dapat diukur, dan ukuran inilah yang menjadi salurannya.

Perbedaan kuncinya ada pada dua tingkat keadaan prosesor. Keadaan arsitektural adalah isi register dan memori yang didefinisikan oleh ISA, sedangkan keadaan mikroarkitektural (microarchitectural state) adalah keadaan internal seperti isi cache, prediktor cabang, dan buffer lainnya. Pembatalan spekulasi, yaitu flush yang membuang instruksi yang belum selesai, mengembalikan keadaan arsitektural dengan sempurna, tetapi tidak mengembalikan keadaan mikroarkitekturalnya. Akibatnya, instruksi yang secara arsitektural seolah tidak pernah dieksekusi tetap menggeser isi cache, dan jejak itulah yang dapat diukur pihak lain.

Bentuknya yang paling sederhana dapat digambarkan dengan array probe, yaitu larik yang setiap elemennya menempati line cache yang berbeda. Program korban mengakses satu elemen yang indeksnya bergantung pada rahasia, dan indeks itu menentukan line mana yang ditarik ke cache. Pengamat kemudian membaca 16 line satu per satu dengan menghitung hit dan missnya, dan posisi satu-satunya line yang hit membocorkan nilai indeks itu. Dengan nn line probe, pengamat membaca n−1n - 1 miss dan satu hit, tanpa pernah melihat nilai rahasianya.

Spectre dan Meltdown#

Pada Januari 2018, dua penelitian terpisah dipublikasikan hampir bersamaan dan menjelaskan bahwa spekulasi dapat dipakai untuk membaca memori yang seharusnya tidak terjangkau. Paper Spectre karya Kocher dan koleganya menunjukkan pemanfaatan prediksi cabang, sedangkan paper Meltdown karya Lipp dan koleganya menunjukkan pemanfaatan celah pada pemeriksaan izin memori. Keduanya kini menjadi fakta sejarah yang terdokumentasi pada paper resminya, dan kelas kelemahan ini pun dinamai dari nama keduanya.

Cara kerja Spectre pada garis besarnya berupa dua tahap, dan prinsipnya dapat dipahami tanpa satu baris kode eksploit pun. Tahap pertama adalah melatih prediktor cabang dengan jalur yang memang biasa benar, sehingga prediktor terus menebak jalur itu. Tahap kedua adalah mengganti data yang diuji sehingga jalur tebakan memuat data rahasia dan menggeser cache sesuai isinya, lalu akses spekulatif itu ditolak oleh keputusan cabang yang akhirnya jatuh. Hasil perhitungannya dibatalkan seluruhnya, tetapi pergeseran cache sudah terjadi, dan pengamat membaca pergeseran itu melalui selisih waktu akses.

Meltdown berbeda mekanismenya. Prosesor yang terkena dampak menyediakan hasil akses memori sebelum pemeriksaan izinnya selesai, sehingga akses ke area kernel sempat dieksekusi secara spekulatif sebelum penolakannya jatuh. Jejak cache dari akses terlarang itu tetap tertinggal meskipun hasilnya dibatalkan, dan selisih waktu kemudian membocorkan isinya. Kedua serangan bertemu pada satu titik yang sama, yaitu jejak mikroarkitektural dari eksekusi yang gagal tetap terbaca, dan itulah alasan mitigasinya menyasar titik yang serupa.

Kategori mitigasi#

Mitigasi untuk kelas kelemahan ini berjalan pada tiga lapisan yang saling menutup celah satu sama lain, yaitu perbaikan perangkat lunak, perbaikan arsitektur, dan pembatasan alat ukurnya. Tidak ada satu lapisan yang menyelesaikan semuanya, karena setiap lapisan menutup sisi yang berbeda dari mekanisme yang sama. Tabel berikut merangkum ketiganya beserta celah yang ditutup masing-masing.

KategoriContohCelah yang ditutup
Perangkat lunakinstruksi barrier, isolasi page tablejalur spekulatif berhenti sebelum menyentuh data rahasia
Arsitekturpartisi cache, penandaan line spekulatifjejak tidak terbentuk atau tidak terbaca lintas proses
Alat ukurtimer yang resolusinya dihaluskanpembeda hit dan miss kehilangan ketelitiannya

Perbaikan perangkat lunak bekerja dengan menyisipkan barrier, yaitu instruksi yang menghentikan eksekusi spekulatif sebelum melewati titik tertentu, misalnya tepat setelah pemeriksaan batas array. Pemeriksaan izin antara kernel dan program pengguna ditutup dengan isolasi page table, yaitu pemisahan tabel terjemahan memori agar area kernel tidak terpetakan pada saat program pengguna berjalan. Kedua bentuk ini diterapkan melalui pembaruan sistem operasi dan kompilasi ulang program.

Perbaikan arsitektur menyasar perangkatnya sendiri pada generasi prosesor berikutnya, misalnya mempartisi cache agar program yang berbeda tidak saling menggeser isinya dan menandai line yang diisi oleh eksekusi spekulatif agar tidak memengaruhi pengamatan. Lapisan ketiga mengurangi kualitas alat ukurnya. Pada serangan nyata, pembedaan hit dan miss dicapai melalui selisih waktu, sehingga timer resolusi tinggi yang terbuka bagi program biasa dibulatkan nilainya oleh browser dan sistem operasi. Ketiganya bersama-sama menaikkan biaya serangan jauh di atas nilainya.

Simulasi jejak cache yang aman#

Simulasi berikut memperlihatkan prinsip pembocoran dalam bentuk yang sepenuhnya aman, yaitu cache direpresentasikan sebagai himpunan line dan pengamat menghitung hit dan miss secara eksak, tanpa pengukuran waktu dan tanpa akses perangkat keras sungguhan. Array probe berisi 16 elemen pada 16 line berbeda, dan program korban menyimpan nilai rahasia 5. Dua jalur diuji pada dua skenario, yaitu akses biasa dan akses spekulatif yang dibatalkan oleh pemeriksaan batas. Jalankan berkasnya, lalu bandingkan baris terakhir kedua skenario.

python jejak-cache.py
# Demonstrasi prinsip side channel cache yang sepenuhnya berupa simulasi:
# tidak ada pengukuran waktu dan tidak ada akses perangkat keras sungguhan.
# Pengamat menguji 16 line probe dengan menghitung hit dan miss, lalu
# posisi satu-satunya line yang hit membocorkan rahasia, bahkan ketika
# aksesnya sudah dibatalkan secara arsitektural.

UKURAN_LINE = 64    # byte per line, satu elemen probe menempati satu line
JUMLAH_ELEMEN = 16  # array probe: 16 elemen pada 16 line berbeda
RAHASIA = 5         # nilai yang hanya diketahui program korban
BATAS = 3           # batas indeks sah pada pemeriksaan jalur spekulatif


def bersihkan():
    """Kembalikan cache kosong, yaitu himpunan nomor line yang tersimpan."""
    return set()


def sentuh(cache, alamat):
    """Akses satu byte: line-nya ikut tersimpan di cache apa pun hasilnya."""
    cache.add(alamat // UKURAN_LINE)


def uji_probe(cache):
    """Pengamat membaca 16 line probe satu per satu dan mencatat statusnya."""
    return [(i, "hit" if i in cache else "miss") for i in range(JUMLAH_ELEMEN)]


def korban_biasa(cache):
    """Jalur biasa: korban membaca probe[RAHASIA] dan memakai hasilnya."""
    sentuh(cache, RAHASIA * UKURAN_LINE)
    return "akses dieksekusi dan hasilnya dipakai"


def korban_spekulatif(cache):
    """Jalur spekulatif: akses dieksekusi lebih dulu atas dasar tebakan
    prediktor, lalu pemeriksaan batas menolaknya. Hasil akses dibatalkan,
    sehingga secara arsitektural akses itu seolah tidak pernah terjadi,
    tetapi line yang disentuh tetap tersimpan di cache."""
    sentuh(cache, RAHASIA * UKURAN_LINE)  # eksekusi spekulatif terjadi
    sah = RAHASIA < BATAS                 # keputusan pemeriksaan jatuh setelahnya
    return "akses sah" if sah else "akses dibatalkan"


def lapor(judul, rinci, cache):
    hasil = uji_probe(cache)
    hit = [i for i, status in hasil if status == "hit"]
    miss = JUMLAH_ELEMEN - len(hit)
    print(judul)
    if rinci:
        for i, status in hasil:
            print(f"  line {i:2d}: {status}")
    print(f"  jumlah miss: {miss} dari {JUMLAH_ELEMEN} line, hit pada line {hit}")
    print(f"  tebakan pengamat atas rahasianya: {hit[0] if hit else 'tidak ada petunjuk'}")


cache = bersihkan()
status = korban_biasa(cache)
lapor(f"Skenario A, {status}", True, cache)
print()

cache = bersihkan()
status = korban_spekulatif(cache)
lapor(f"Skenario B, {status}", False, cache)
text Keluaran
Skenario A, akses dieksekusi dan hasilnya dipakai
  line  0: miss
  line  1: miss
  line  2: miss
  line  3: miss
  line  4: miss
  line  5: hit
  line  6: miss
  line  7: miss
  line  8: miss
  line  9: miss
  line 10: miss
  line 11: miss
  line 12: miss
  line 13: miss
  line 14: miss
  line 15: miss
  jumlah miss: 15 dari 16 line, hit pada line [5]
  tebakan pengamat atas rahasianya: 5

Skenario B, akses dibatalkan
  jumlah miss: 15 dari 16 line, hit pada line [5]
  tebakan pengamat atas rahasianya: 5

Kedua skenario menghasilkan jumlah miss yang sama, yaitu 15 dari 16 line, dan posisi hitnya jatuh pada line 5 di keduanya. Skenario A sesuai harapan, karena aksesnya memang dieksekusi. Skenario B adalah inti kelas Spectre dalam bentuk yang paling sederhana. Pemeriksaan batas menolak akses, sehingga secara arsitektural tidak ada yang dieksekusi dan tidak ada hasil yang tertulis, tetapi line 5 tetap tersimpan di cache karena eksekusi spekulatifnya sudah terjadi. Pembatalan tidak mengembalikan cache, dan pengamat membaca sisanya sebagai satu hit di antara 15 miss.

Kesalahan umum#

  1. Mengira pembatalan spekulasi membersihkan semuanya. Pembatalan mengembalikan register dan memori arsitektural, sedangkan cache dan prediktor tidak ikut dikembalikan karena keadaannya tidak dilacak oleh mekanisme pembatalan. Perbaikannya adalah memperlakukan keadaan mikroarkitektural sebagai jejak yang tetap tersisa setelah pembatalan.
  2. Menyamakan Spectre dan Meltdown. Spectre menyalahgunakan prediksi cabang sehingga menjangkau hampir semua prosesor modern, sedangkan Meltdown memanfaatkan jeda pemeriksaan izin pada desain tertentu sehingga cakupannya lebih sempit. Perbaikannya adalah menyebut mekanismenya setiap kali salah satu nama dipakai, bukan menganggap keduanya satu masalah yang sama.
  3. Menganggap satu mitigasi cukup. Perbaikan perangkat lunak menutup jalur tertentu, perbaikan arsitektur menutup mekanismenya, dan pembatasan timer menutup alat ukurnya, sedangkan sisi yang tidak tertutup tetap terbuka. Perbaikannya adalah menerapkan ketiga lapisan sekaligus dan meninjau ulang setiap kali varian baru dipublikasikan.
  4. Menganggap mitigasi menghentikan semua risiko. Mitigasi menaikkan biaya serangan dan mempersempit cakupannya, sedangkan varian baru terus dipublikasikan pada penelitian akademik. Perbaikannya adalah memperlakukan mitigasi sebagai pengelolaan risiko yang berjalan terus, bukan sebagai titik akhir.

Ringkasan#

  • Cache adalah side channel yang penting karena dipakai bersama dan keadaannya berubah setiap ada akses, sehingga pola akses satu program terbaca oleh program lain melalui penghitungan hit dan miss.
  • Pembatalan spekulasi mengembalikan keadaan arsitektural saja, sehingga akses yang dibatalkan tetap meninggalkan line di cache dan jejaknya tetap terukur.
  • Spectre dan Meltdown, dipublikasikan pada Januari 2018, mendokumentasikan eksploitasi prinsip ini pada paper resmi dan memicu pembaruan perangkat lunak masif pada tahun yang sama.
  • Mitigasinya berlapis, yaitu barrier perangkat lunak, perbaikan arsitektur seperti partisi cache, dan pembatasan timer presisi tinggi.

Referensi#