Bagikan melalui


Panduan Pengembang C++ untuk Saluran Sisi Eksekusi Spekulatif

Artikel ini berisi panduan bagi pengembang untuk membantu mengidentifikasi dan mengurangi kerentanan perangkat keras saluran samping eksekusi spekulatif dalam perangkat lunak C++. Kerentanan ini dapat mengungkapkan informasi sensitif di seluruh batas kepercayaan dan dapat memengaruhi perangkat lunak yang berjalan pada prosesor yang mendukung eksekusi instruksi yang spekulatif dan tidak berurutan. Kelas kerentanan ini pertama kali dijelaskan pada januari 2018 dan latar belakang dan panduan tambahan dapat ditemukan di penasihat keamanan Microsoft.

Panduan yang diberikan oleh artikel ini terkait dengan kelas kerentanan yang diwakili oleh:

  1. CVE-2017-5753, juga dikenal sebagai varian Spectre 1. Kelas kerentanan perangkat keras ini terkait dengan saluran samping yang dapat muncul karena eksekusi spekulatif yang terjadi sebagai akibat dari kesalahan penyusunan cabang kondisional. Pengkompilasi Microsoft C++ di Visual Studio 2017 (dimulai dengan versi 15.5.5) menyertakan dukungan untuk /Qspectre sakelar yang menyediakan mitigasi waktu kompilasi untuk serangkaian pola pengkodian yang berpotensi rentan terbatas yang terkait dengan CVE-2017-5753. Sakelar /Qspectre juga tersedia di Visual Studio 2015 Update 3 hingga KB 4338871. Dokumentasi untuk /Qspectre bendera memberikan informasi lebih lanjut tentang efek dan penggunaannya.

  2. CVE-2018-3639, juga dikenal sebagai Speculative Store Bypass (SSB). Kelas kerentanan perangkat keras ini terkait dengan saluran samping yang dapat muncul karena eksekusi spekulatif beban di depan penyimpanan dependen sebagai akibat dari kesalahan penyusunan akses memori.

Pengenalan yang dapat diakses untuk kerentanan saluran samping eksekusi spekulatif dapat ditemukan dalam presentasi berjudul Kasus Spectre dan Meltdown oleh salah satu tim peneliti yang menemukan masalah-masalah ini.

Apa itu kerentanan perangkat keras Saluran Sisi Eksekusi Spekulatif?

CPU modern memberikan tingkat performa yang lebih tinggi dengan memanfaatkan eksekusi instruksi yang spekulatif dan di luar urutan. Misalnya, ini sering dicapai dengan memprediksi target cabang (kondisi dan tidak langsung) yang memungkinkan CPU untuk mulai secara spekulatif menjalankan instruksi pada target cabang yang diprediksi, sehingga menghindari kios sampai target cabang yang sebenarnya diselesaikan. Jika CPU kemudian menemukan bahwa terjadi kesalahan kata sandi, semua status komputer yang dihitung secara spekulatif dibuang. Ini memastikan bahwa tidak ada efek arsitektur yang terlihat dari spekulasi yang salah diprediksi.

Meskipun eksekusi spekulatif tidak memengaruhi status yang terlihat secara arsitektur, itu dapat meninggalkan jejak sisa dalam status non-arsitektur, seperti berbagai cache yang digunakan oleh CPU. Jejak sisa eksekusi spekulatif inilah yang dapat menimbulkan kerentanan saluran samping. Untuk lebih memahami ini, pertimbangkan fragmen kode berikut yang memberikan contoh CVE-2017-5753 (Bounds Check Bypass):

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

Dalam contoh ini, ReadByte disediakan buffer, ukuran buffer, dan indeks ke dalam buffer tersebut. Parameter indeks, seperti yang ditentukan oleh untrusted_index, disediakan oleh konteks yang kurang istimewa, seperti proses non-administratif. Jika untrusted_index kurang dari buffer_size, maka karakter di indeks tersebut dibaca dari buffer dan digunakan untuk mengindeks ke wilayah memori bersama yang disebut oleh shared_buffer.

Dari perspektif arsitektur, urutan kode ini sangat aman karena dijamin akan untrusted_index selalu kurang dari buffer_size. Namun, dengan adanya eksekusi spekulatif, ada kemungkinan bahwa CPU akan salah menduga cabang bersyarat dan menjalankan isi pernyataan jika bahkan ketika untrusted_index lebih besar dari atau sama dengan buffer_size. Sebagai konsekuensi dari ini, CPU dapat secara spekulatif membaca byte dari luar batas buffer (yang bisa menjadi rahasia) dan kemudian dapat menggunakan nilai byte tersebut untuk menghitung alamat beban berikutnya melalui shared_buffer.

Sementara CPU pada akhirnya akan mendeteksi kesalahan prediksi ini, efek samping residu dapat dibiarkan di cache CPU yang mengungkapkan informasi tentang nilai byte yang dibaca keluar dari batas dari buffer. Efek samping ini dapat dideteksi oleh konteks yang kurang istimewa yang berjalan pada sistem dengan memeriksa seberapa cepat setiap baris cache masuk shared_buffer diakses. Langkah-langkah yang dapat diambil untuk mencapai hal ini adalah:

  1. ReadByte Panggil beberapa kali dengan untrusted_index kurang dari buffer_size. Konteks penyerangan dapat menyebabkan konteks korban memanggil ReadByte (misalnya melalui RPC) sehingga prediktor cabang dilatih untuk tidak diambil karena untrusted_index kurang dari buffer_size.

  2. Bersihkan semua baris cache di shared_buffer. Konteks menyerang harus menghapus semua baris cache di wilayah memori bersama yang disebut oleh shared_buffer. Karena wilayah memori dibagikan, ini mudah dan dapat dicapai menggunakan intrinsik seperti _mm_clflush.

  3. ReadByte Panggil dengan untrusted_index menjadi lebih besar dari buffer_size. Konteks penyerangan menyebabkan konteks korban memanggil ReadByte sehingga salah memprediksi bahwa cabang tidak akan diambil. Ini menyebabkan prosesor secara spekulatif mengeksekusi isi blok jika dengan untrusted_index lebih besar dari buffer_size, sehingga mengarah ke pembacaan yang di luar batas dari buffer. Akibatnya, shared_buffer diindeks menggunakan nilai yang berpotensi rahasia yang dibaca di luar batas, sehingga menyebabkan baris cache masing-masing dimuat oleh CPU.

  4. Baca setiap baris cache untuk shared_buffer melihat mana yang diakses dengan paling cepat. Konteks menyerang dapat membaca setiap baris cache di shared_buffer dan mendeteksi baris cache yang dimuat secara signifikan lebih cepat daripada yang lain. Ini adalah baris cache yang kemungkinan telah dibawa pada langkah 3. Karena ada hubungan 1:1 antara nilai byte dan baris cache dalam contoh ini, ini memungkinkan penyerang untuk menyimpulkan nilai aktual byte yang dibaca di luar batas.

Langkah-langkah di atas memberikan contoh penggunaan teknik yang dikenal sebagai FLUSH+RELOAD bersama dengan mengeksploitasi instans CVE-2017-5753.

Skenario perangkat lunak apa yang dapat terpengaruh?

Mengembangkan perangkat lunak aman menggunakan proses seperti Security Development Lifecycle (SDL) biasanya mengharuskan pengembang mengidentifikasi batas kepercayaan yang ada di aplikasi mereka. Batas kepercayaan ada di tempat di mana aplikasi dapat berinteraksi dengan data yang disediakan oleh konteks yang kurang tepercaya, seperti proses lain pada sistem atau proses mode pengguna non-administratif dalam kasus driver perangkat mode kernel. Kelas kerentanan baru yang melibatkan saluran sisi eksekusi spekulatif relevan dengan banyak batas kepercayaan dalam model keamanan perangkat lunak yang ada yang mengisolasi kode dan data pada perangkat.

Tabel berikut ini menyediakan ringkasan model keamanan perangkat lunak di mana pengembang mungkin perlu khawatir tentang kerentanan ini terjadi:

Batas kepercayaan Deskripsi
Batas mesin virtual Aplikasi yang mengisolasi beban kerja di komputer virtual terpisah yang menerima data yang tidak tepercaya dari komputer virtual lain mungkin berisiko.
Batas kernel Driver perangkat mode kernel yang menerima data yang tidak tepercaya dari proses mode pengguna non-administratif mungkin berisiko.
Batas proses Aplikasi yang menerima data yang tidak tepercaya dari proses lain yang berjalan pada sistem lokal, seperti melalui Panggilan Prosedur Jarak Jauh (RPC), memori bersama, atau mekanisme Komunikasi Antar Proses (IPC) lainnya mungkin berisiko.
Batas Enklave Aplikasi yang dijalankan dalam enklave aman (seperti Intel SGX) yang menerima data yang tidak tepercaya dari luar enklave mungkin berisiko.
Batas bahasa Aplikasi yang menginterpretasikan atau Just-In-Time (JIT) mengkompilasi dan menjalankan kode yang tidak tepercaya yang ditulis dalam bahasa tingkat yang lebih tinggi mungkin berisiko.

Aplikasi yang memiliki permukaan serangan yang terekspos ke salah satu batas kepercayaan di atas harus meninjau kode pada permukaan serangan untuk mengidentifikasi dan mengurangi kemungkinan instans kerentanan saluran sisi eksekusi spekulatif. Perlu dicatat bahwa batas kepercayaan yang terpapar ke permukaan serangan jarak jauh, seperti protokol jaringan jarak jauh, belum ditunjukkan berisiko terhadap kerentanan saluran samping eksekusi spekulatif.

Pola pengkodian yang berpotensi rentan

Kerentanan saluran samping eksekusi spekulatif dapat muncul sebagai konsekuensi dari beberapa pola pengodean. Bagian ini menjelaskan pola pengkodean yang berpotensi rentan dan memberikan contoh untuk masing-masing, tetapi harus dikenali bahwa variasi pada tema ini mungkin ada. Dengan demikian, pengembang disarankan untuk mengambil pola ini sebagai contoh dan bukan sebagai daftar lengkap dari semua pola pengkodian yang berpotensi rentan. Kelas kerentanan keamanan memori yang sama yang dapat ada dalam perangkat lunak saat ini mungkin juga ada di sepanjang jalur eksekusi spekulatif dan di luar urutan, termasuk tetapi tidak terbatas pada overrun buffer, akses array di luar batas, penggunaan memori yang tidak diinisialisasi, kebingungan jenis, dan sebagainya. Primitif yang sama yang dapat digunakan penyerang untuk mengeksploitasi kerentanan keamanan memori di sepanjang jalur arsitektur juga dapat berlaku untuk jalur spekulatif.

Secara umum, saluran sisi eksekusi spekulatif yang terkait dengan kesalahan penyusunan cabang kondisional dapat muncul ketika ekspresi kondisional beroperasi pada data yang dapat dikontrol atau dipengaruhi oleh konteks yang kurang tepercaya. Misalnya, ini dapat mencakup ekspresi bersyarat yang digunakan dalam ifpernyataan , , forwhile, switch, atau ternary. Untuk masing-masing pernyataan ini, kompilator dapat menghasilkan cabang kondisional yang kemudian dapat diprediksi CPU untuk target cabang saat runtime.

Untuk setiap contoh, komentar dengan frasa "SPECULATION BARRIER" dimasukkan di mana pengembang dapat memperkenalkan hambatan sebagai mitigasi. Ini dibahas secara lebih rinci di bagian tentang mitigasi.

Beban di luar batas spekulatif

Kategori pola pengkodian ini melibatkan kesalahan penyimpangan cabang kondisional yang mengarah pada akses memori yang di luar batas spekulatif.

Array di luar batas memuat beban

Pola pengodean ini adalah pola pengodean yang awalnya dijelaskan untuk CVE-2017-5753 (Bounds Check Bypass). Bagian latar belakang artikel ini menjelaskan pola ini secara rinci.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        // SPECULATION BARRIER
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

Demikian pula, beban array di luar batas dapat terjadi bersama dengan perulangan yang melebihi kondisi penghentiannya karena kesalahan prasangka. Dalam contoh ini, cabang bersyarah yang terkait dengan x < buffer_size ekspresi dapat salah diprediksi dan secara spekulatif menjalankan isi for perulangan ketika x lebih besar dari atau sama dengan buffer_size, sehingga menghasilkan beban di luar batas yang spekulatif.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadBytes(unsigned char *buffer, unsigned int buffer_size) {
    for (unsigned int x = 0; x < buffer_size; x++) {
        // SPECULATION BARRIER
        unsigned char value = buffer[x];
        return shared_buffer[value * 4096];
    }
}

Array di luar batas beban memberi makan cabang tidak langsung

Pola pengkodian ini melibatkan kasus di mana kesalahan prediksi cabang bersyarkat dapat menyebabkan akses di luar batas ke array penunjuk fungsi yang kemudian mengarah ke cabang tidak langsung ke alamat target yang dibaca di luar batas. Cuplikan berikut memberikan contoh yang menunjukkan hal ini.

Dalam contoh ini, pengidentifikasi pesan yang tidak tepercaya disediakan untuk DispatchMessage melalui untrusted_message_id parameter . Jika untrusted_message_id kurang dari MAX_MESSAGE_ID, maka digunakan untuk mengindeks ke dalam array pointer fungsi dan cabang ke target cabang yang sesuai. Kode ini aman secara arsitektur, tetapi jika CPU salah mendepresiasikan cabang kondisional, itu dapat mengakibatkan diindeks DispatchTable oleh untrusted_message_id ketika nilainya lebih besar dari atau sama dengan MAX_MESSAGE_ID, sehingga mengarah ke akses di luar batas. Ini dapat mengakibatkan eksekusi spekulatif dari alamat target cabang yang diturunkan di luar batas array yang dapat menyebabkan pengungkapan informasi tergantung pada kode yang dijalankan secara spekulatif.

#define MAX_MESSAGE_ID 16

typedef void (*MESSAGE_ROUTINE)(unsigned char *buffer, unsigned int buffer_size);

const MESSAGE_ROUTINE DispatchTable[MAX_MESSAGE_ID];

void DispatchMessage(unsigned int untrusted_message_id, unsigned char *buffer, unsigned int buffer_size) {
    if (untrusted_message_id < MAX_MESSAGE_ID) {
        // SPECULATION BARRIER
        DispatchTable[untrusted_message_id](buffer, buffer_size);
    }
}

Seperti halnya array di luar batas yang memberi makan beban lain, kondisi ini juga dapat muncul bersama dengan perulangan yang melebihi kondisi penghentiannya karena kesalahan prasangka.

Array di luar batas menyimpan umpan cabang tidak langsung

Sementara contoh sebelumnya menunjukkan bagaimana beban out-of-bounds spekulatif dapat memengaruhi target cabang tidak langsung, juga dimungkinkan bagi penyimpanan di luar batas untuk memodifikasi target cabang tidak langsung, seperti penunjuk fungsi atau alamat pengembalian. Hal ini berpotensi menyebabkan eksekusi spekulatif dari alamat yang ditentukan penyerang.

Dalam contoh ini, indeks yang tidak tepercaya diteruskan melalui untrusted_index parameter . Jika untrusted_index kurang dari jumlah pointers elemen array (256 elemen), maka nilai pointer yang disediakan dalam ptr ditulis ke pointers array. Kode ini aman secara arsitektur, tetapi jika CPU salah menduga cabang kondisional, itu dapat mengakibatkan ptr ditulis secara spekulatif di luar batas array yang dialokasikan pointers tumpukan. Ini dapat menyebabkan kerusakan spekulatif dari alamat pengembalian untuk WriteSlot. Jika penyerang dapat mengontrol nilai ptr, mereka mungkin dapat menyebabkan eksekusi spekulatif dari alamat sewenang-wenang ketika WriteSlot kembali di sepanjang jalur spekulatif.

unsigned char WriteSlot(unsigned int untrusted_index, void *ptr) {
    void *pointers[256];
    if (untrusted_index < 256) {
        // SPECULATION BARRIER
        pointers[untrusted_index] = ptr;
    }
}

Demikian pula, jika variabel lokal penunjuk fungsi bernama func dialokasikan pada tumpukan, maka mungkin untuk secara spekulatif memodifikasi alamat yang func mengacu pada ketika kesalahan penyangkalan cabang kondisional terjadi. Ini dapat mengakibatkan eksekusi spekulatif dari alamat arbitrer ketika penunjuk fungsi dipanggil melalui.

unsigned char WriteSlot(unsigned int untrusted_index, void *ptr) {
    void *pointers[256];
    void (*func)() = &callback;
    if (untrusted_index < 256) {
        // SPECULATION BARRIER
        pointers[untrusted_index] = ptr;
    }
    func();
}

Perlu dicatat bahwa kedua contoh ini melibatkan modifikasi spekulatif dari pointer cabang tidak langsung yang dialokasikan tumpukan. Ada kemungkinan bahwa modifikasi spekulatif juga dapat terjadi untuk variabel global, memori yang dialokasikan timbunan, dan bahkan memori baca-saja pada beberapa CPU. Untuk memori yang dialokasikan tumpukan, pengkompilasi Microsoft C++ sudah mengambil langkah-langkah untuk membuatnya lebih sulit untuk memodifikasi target cabang tidak langsung yang dialokasikan tumpukan secara spekulatif, seperti dengan menyusun ulang variabel lokal sehingga buffer ditempatkan berdekatan dengan cookie keamanan sebagai bagian /GS dari fitur keamanan kompilator.

Kebingungan jenis spekulatif

Kategori ini berkaitan dengan pola pengkodian yang dapat memunculkan kebingungan jenis spekulatif. Ini terjadi ketika memori diakses menggunakan jenis yang salah di sepanjang jalur non-arsitektur selama eksekusi spekulatif. Baik misprediksi cabang kondisional dan bypass penyimpanan spekulatif dapat berpotensi menyebabkan kebingungan jenis spekulatif.

Untuk bypass penyimpanan spekulatif, ini dapat terjadi dalam skenario di mana kompilator menggunakan kembali lokasi tumpukan untuk variabel dari beberapa jenis. Ini karena penyimpanan arsitektur dari variabel jenis A dapat dilewati, sehingga memungkinkan beban jenis A untuk dijalankan secara spekulatif sebelum variabel ditetapkan. Jika variabel yang disimpan sebelumnya memiliki jenis yang berbeda, maka ini dapat menciptakan kondisi untuk kebingungan jenis spekulatif.

Untuk misprediksi cabang bersyarat, cuplikan kode berikut akan digunakan untuk menggambarkan berbagai kondisi yang dapat menimbulkan kebingungan jenis spekulatif.

enum TypeName {
    Type1,
    Type2
};

class CBaseType {
public:
    CBaseType(TypeName type) : type(type) {}
    TypeName type;
};

class CType1 : public CBaseType {
public:
    CType1() : CBaseType(Type1) {}
    char field1[256];
    unsigned char field2;
};

class CType2 : public CBaseType {
public:
    CType2() : CBaseType(Type2) {}
    void (*dispatch_routine)();
    unsigned char field2;
};

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ProcessType(CBaseType *obj)
{
    if (obj->type == Type1) {
        // SPECULATION BARRIER
        CType1 *obj1 = static_cast<CType1 *>(obj);

        unsigned char value = obj1->field2;

        return shared_buffer[value * 4096];
    }
    else if (obj->type == Type2) {
        // SPECULATION BARRIER
        CType2 *obj2 = static_cast<CType2 *>(obj);

        obj2->dispatch_routine();

        return obj2->field2;
    }
}

Kebingungan jenis spekulatif yang mengarah ke beban di luar batas

Pola pengkodian ini melibatkan kasus di mana kebingungan jenis spekulatif dapat mengakibatkan akses bidang yang di luar batas atau bingung jenis di mana nilai yang dimuat memberi umpan alamat beban berikutnya. Ini mirip dengan pola pengkodan array di luar batas tetapi dimanifestasikan melalui urutan pengkodan alternatif seperti yang ditunjukkan di atas. Dalam contoh ini, konteks menyerang dapat menyebabkan konteks korban dijalankan ProcessType beberapa kali dengan objek jenis CType1 (type bidang sama dengan Type1). Hal ini akan berpengaruh pada pelatihan cabang bersyarat untuk pernyataan pertama if yang diprediksi tidak diambil. Konteks menyerang kemudian dapat menyebabkan konteks korban dijalankan ProcessType dengan objek jenis CType2. Ini dapat mengakibatkan kebingungan jenis spekulatif jika cabang bersyukur untuk pernyataan pertama if salah menduga dan menjalankan isi if pernyataan, sehingga melemparkan objek jenis CType2 ke CType1. Karena CType2 lebih kecil dari CType1, akses memori ke CType1::field2 akan menghasilkan beban data yang di luar batas spekulatif yang mungkin rahasia. Nilai ini kemudian digunakan dalam beban shared_buffer yang dapat membuat efek samping yang dapat diamati, seperti contoh array di luar batas yang dijelaskan sebelumnya.

Kebingungan jenis spekulatif yang mengarah ke cabang tidak langsung

Pola pengodian ini melibatkan kasus di mana kebingungan jenis spekulatif dapat mengakibatkan cabang tidak langsung yang tidak aman selama eksekusi spekulatif. Dalam contoh ini, konteks menyerang dapat menyebabkan konteks korban dijalankan ProcessType beberapa kali dengan objek jenis CType2 (type bidang sama dengan Type2). Hal ini akan berdampak pada pelatihan cabang bersyarat untuk pernyataan pertama if yang akan diambil dan else if pernyataan tidak diambil. Konteks menyerang kemudian dapat menyebabkan konteks korban dijalankan ProcessType dengan objek jenis CType1. Ini dapat mengakibatkan kebingungan jenis spekulatif jika cabang bersyarat untuk pernyataan pertama if diprediksi diambil dan else if pernyataan memprediksi tidak diambil, sehingga mengeksekusi isi else if dan mentransmisikan objek jenis CType1 ke CType2. CType2::dispatch_routine Karena bidang tumpang tindih dengan char array CType1::field1, ini dapat mengakibatkan cabang tidak langsung spekulatif ke target cabang yang tidak diinginkan. Jika konteks menyerang dapat mengontrol nilai byte dalam CType1::field1 array, mereka mungkin dapat mengontrol alamat target cabang.

Penggunaan spekulatif yang tidak diinisialisasi

Kategori pola pengkodian ini melibatkan skenario di mana eksekusi spekulatif dapat mengakses memori yang tidak diinisialisasi dan menggunakannya untuk memberi makan beban berikutnya atau cabang tidak langsung. Agar pola pengkodian ini dapat dieksploitasi, penyerang harus dapat mengontrol atau secara bermakna memengaruhi konten memori yang digunakan tanpa diinisialisasi oleh konteks tempatnya digunakan.

Penggunaan spekulatif yang tidak diinisialisasi yang mengarah ke beban di luar batas

Penggunaan spekulatif yang tidak diinisialisasi berpotensi menyebabkan beban di luar batas menggunakan nilai yang dikontrol penyerang. Dalam contoh di bawah ini, nilai index ditetapkan trusted_index pada semua jalur arsitektur dan trusted_index diasumsikan kurang dari atau sama dengan buffer_size. Namun, tergantung pada kode yang dihasilkan oleh pengkompilasi, ada kemungkinan bahwa bypass penyimpanan spekulatif dapat terjadi yang memungkinkan beban dari buffer[index] dan ekspresi dependen untuk dijalankan sebelum penugasan ke index. Jika ini terjadi, nilai yang tidak diinisialisasi untuk index akan digunakan sebagai offset yang buffer dapat memungkinkan penyerang membaca informasi sensitif di luar batas dan menyampaikan ini melalui saluran samping melalui beban dependen .shared_buffer

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

void InitializeIndex(unsigned int trusted_index, unsigned int *index) {
    *index = trusted_index;
}

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int trusted_index) {
    unsigned int index;

    InitializeIndex(trusted_index, &index); // not inlined

    // SPECULATION BARRIER
    unsigned char value = buffer[index];
    return shared_buffer[value * 4096];
}

Penggunaan spekulatif yang tidak diinisialisasi yang mengarah ke cabang tidak langsung

Penggunaan spekulatif yang tidak diinisialisasi berpotensi menyebabkan cabang tidak langsung di mana target cabang dikendalikan oleh penyerang. Dalam contoh di bawah ini, routine ditetapkan ke salah satu DefaultMessageRoutine1 atau DefaultMessageRoutine tergantung pada nilai mode. Pada jalur arsitektur, ini akan mengakibatkan selalu diinisialisasi routine di depan cabang tidak langsung. Namun, tergantung pada kode yang dihasilkan oleh kompilator, bypass penyimpanan spekulatif dapat terjadi yang memungkinkan cabang tidak langsung melalui routine untuk dijalankan secara spekulatif menjelang penugasan ke routine. Jika ini terjadi, penyerang mungkin dapat secara spekulatif mengeksekusi dari alamat sewenang-wenang, dengan asumsi penyerang dapat memengaruhi atau mengontrol nilai yang tidak diinisialisasi dari routine.

#define MAX_MESSAGE_ID 16

typedef void (*MESSAGE_ROUTINE)(unsigned char *buffer, unsigned int buffer_size);

const MESSAGE_ROUTINE DispatchTable[MAX_MESSAGE_ID];
extern unsigned int mode;

void InitializeRoutine(MESSAGE_ROUTINE *routine) {
    if (mode == 1) {
        *routine = &DefaultMessageRoutine1;
    }
    else {
        *routine = &DefaultMessageRoutine;
    }
}

void DispatchMessage(unsigned int untrusted_message_id, unsigned char *buffer, unsigned int buffer_size) {
    MESSAGE_ROUTINE routine;

    InitializeRoutine(&routine); // not inlined

    // SPECULATION BARRIER
    routine(buffer, buffer_size);
}

Opsi mitigasi

Kerentanan saluran sisi eksekusi spekulatif dapat dimitigasi dengan membuat perubahan pada kode sumber. Perubahan ini dapat melibatkan mitigasi instans tertentu dari kerentanan, seperti dengan menambahkan hambatan spekulasi, atau dengan membuat perubahan pada desain aplikasi untuk membuat informasi sensitif tidak dapat diakses oleh eksekusi spekulatif.

Hambatan spekulasi melalui instrumentasi manual

Hambatan spekulasi dapat dimasukkan secara manual oleh pengembang untuk mencegah eksekusi spekulatif melanjutkan jalur non-arsitektur. Misalnya, pengembang dapat menyisipkan penghalang spekulasi sebelum pola pengkodian berbahaya di tubuh blok bersyarah, baik di awal blok (setelah cabang kondisional) atau sebelum beban pertama yang menjadi perhatian. Ini akan mencegah kesalahan cabang bersyarat menjalankan kode berbahaya pada jalur non-arsitektur dengan menserialisasikan eksekusi. Urutan penghalang spekulasi berbeda dengan arsitektur perangkat keras seperti yang dijelaskan oleh tabel berikut:

Sistem Intrinsik hambatan spekulasi untuk CVE-2017-5753 Spekulasi hambatan intrinsik untuk CVE-2018-3639
x86/x64 _mm_lfence() _mm_lfence()
ARM saat ini tidak tersedia __dsb(0)
ARM64 saat ini tidak tersedia __dsb(0)

Misalnya, pola kode berikut dapat dimitigasi dengan menggunakan _mm_lfence intrinsik seperti yang ditunjukkan di bawah ini.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        _mm_lfence();
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

Hambatan spekulasi melalui instrumentasi waktu kompilator

Pengkompilasi Microsoft C++ di Visual Studio 2017 (dimulai dengan versi 15.5.5) menyertakan dukungan untuk /Qspectre sakelar yang secara otomatis menyisipkan hambatan spekulasi untuk serangkaian pola pengkodian yang berpotensi rentan terbatas yang terkait dengan CVE-2017-5753. Dokumentasi untuk /Qspectre bendera memberikan informasi lebih lanjut tentang efek dan penggunaannya. Penting untuk dicatat bahwa bendera ini tidak mencakup semua pola pengkodean yang berpotensi rentan dan karena itu pengembang tidak boleh mengandalkannya sebagai mitigasi komprehensif untuk kelas kerentanan ini.

Indeks array masking

Dalam kasus di mana beban out-of-bounds spekulatif dapat terjadi, indeks array dapat sangat terikat pada jalur arsitektur dan non-arsitektur dengan menambahkan logika untuk secara eksplisit mengikat indeks array. Misalnya, jika array dapat dialokasikan ke ukuran yang selaras dengan kekuatan dua, maka masker sederhana dapat diperkenalkan. Ini diilustrasikan dalam sampel di bawah ini di mana diasumsikan yang buffer_size selaras dengan kekuatan dua. Ini memastikan bahwa untrusted_index selalu kurang dari buffer_size, bahkan jika terjadi kesalahan cabang bersyarat dan untrusted_index diteruskan dengan nilai yang lebih besar dari atau sama dengan buffer_size.

Perlu dicatat bahwa masking indeks yang dilakukan di sini dapat tunduk pada bypass penyimpanan spekulatif tergantung pada kode yang dihasilkan oleh pengkompilasi.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        untrusted_index &= (buffer_size - 1);
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

Menghapus informasi sensitif dari memori

Teknik lain yang dapat digunakan untuk mengurangi kerentanan saluran samping eksekusi spekulatif adalah menghapus informasi sensitif dari memori. Pengembang perangkat lunak dapat mencari peluang untuk merefaktor aplikasi mereka sehingga informasi sensitif tidak dapat diakses selama eksekusi spekulatif. Ini dapat dicapai dengan merefaktor desain aplikasi untuk mengisolasi informasi sensitif ke dalam proses terpisah. Misalnya, aplikasi browser web dapat mencoba mengisolasi data yang terkait dengan setiap asal web ke dalam proses terpisah, sehingga mencegah satu proses mengakses data lintas asal melalui eksekusi spekulatif.

Lihat juga

Panduan untuk mengurangi kerentanan saluran samping eksekusi spekulatif
Mengurangi kerentanan perangkat keras saluran samping eksekusi spekulatif