Kurgusal Yürütme Yan Kanalları için C++ Geliştirici Kılavuzu

Bu makale, geliştiricilerin C++ yazılımındaki kurgusal yürütme tarafı kanal donanım güvenlik açıklarını tanımlama ve azaltma konusunda yardımcı olması için rehberlik içerir. Bu güvenlik açıkları güven sınırları genelinde hassas bilgileri açığa çıkarır ve tahmine dayalı, sıra dışı yönergelerin yürütülmesini destekleyen işlemcilerde çalışan yazılımları etkileyebilir. Bu güvenlik açıkları sınıfı ilk olarak Ocak 2018'de açıklanmıştır ve Ek arka plan ve yönergeler Microsoft'un güvenlik danışmanlığında bulunabilir.

Bu makale tarafından sağlanan kılavuz, aşağıdakiler tarafından temsil edilen güvenlik açığı sınıfları ile ilgilidir:

  1. CVE-2017-5753, Spectre varyant 1 olarak da bilinir. Bu donanım güvenlik açığı sınıfı, koşullu dallanma tahmin hatası sonucunda oluşan spekülatif yürütme nedeniyle ortaya çıkabilecek yan kanallarla ilgilidir. Visual Studio 2017'deki Microsoft C++ derleyicisi (sürüm 15.5.5'den başlayarak), CVE-2017-5753 ile ilgili sınırlı sayıda güvenlik açığı olabilecek kodlama deseni için derleme süresi azaltma sağlayan anahtar desteği /Qspectre içerir. Bu /Qspectre anahtar, Visual Studio 2015 Güncelleştirme 3 ile KB 4338871 ile de kullanılabilir. Bayrağın /Qspectre belgeleri, etkileri ve kullanımı hakkında daha fazla bilgi sağlar.

  2. CVE-2018-3639, Tahmini Mağaza Atlama (SSB) olarak da bilinir. Bu donanım güvenlik açığı sınıfı, bir bellek erişimi yanlış tahmini sonucunda bağımlı depolama işleminin önündeki bir yükün spekülatif yürütülmesi nedeniyle ortaya çıkabilecek yan kanallarla ilgilidir.

Kanal bağımlı spekülatif yürütme güvenlik açıklarına dair erişilebilir bir giriş, bu sorunları keşfeden araştırma ekiplerinden biri tarafından yapılan The Case of Spectre and Meltdown başlıklı sunumda bulunabilir.

Kurgusal Yürütme Tarafı Kanalı donanım güvenlik açıkları nelerdir?

Modern CPU'lar, yönergelerin kurgusal ve sıra dışı yürütülmesini kullanarak daha yüksek performans dereceleri sağlar. Örneğin, bu genellikle CPU'nun tahmin edilen dal hedefinde yönergeleri tahmine dayalı olarak yürütmeye başlamasını sağlayan dalların hedefini tahmin ederek (koşullu ve dolaylı) gerçekleştirilir ve böylece gerçek dal hedefi çözümlenene kadar duraklamadan kaçınılır. CPU'nun daha sonra bir yanlış tahmin gerçekleştiğini bulması durumunda, tahmine dayalı olarak hesaplanan tüm makine durumu atılır. Bu, yanlış tahmin edilen spekülasyonun mimari olarak görünür bir etkisi olmamasını sağlar.

Spekülatif yürütme, mimari olarak görünür durumu etkilemese de, CPU tarafından kullanılan çeşitli önbellekler gibi mimari olmayan durumda kalıntı izler bırakabilir. Spekülatif yürütmenin artık izleri, yan kanal güvenlik açıklarına neden olabilir. Bunu daha iyi anlamak için CVE-2017-5753 (Sınır Denetimi Atlama) örneğini sağlayan aşağıdaki kod parçasını göz önünde bulundurun:

// 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];
    }
}

Bu örnekte, ReadByte'a bir arabellek, arabellek boyutu ve arabellekteki bir dizin sağlanır. tarafından untrusted_indexbelirtilen dizin parametresi, yönetim dışı bir işlem gibi daha az ayrıcalıklı bir bağlam tarafından sağlanır. Eğer untrusted_index, buffer_size değerinden küçükse, bu dizindeki karakter buffer'den okunur ve shared_buffer tarafından başvurulan paylaşılan bellek bölgesine dizin oluşturmak için kullanılır.

Mimari açıdan bakıldığında, untrusted_index'nin her zaman buffer_size'den küçük olacağı garanti edilen bu kod dizisi son derece güvenlidir. Ancak, yanılmalı yürütmenin varlığında, CPU'nun koşullu dalı yanlış tahmin etmesi ve untrusted_index değeri buffer_size'den büyük veya eşit olsa bile if deyiminin gövdesini yürütmesi mümkündür. Bunun bir sonucu olarak, CPU sınırlarının ötesinden buffer (gizli bilgi olabilir) tahmini olarak bir bayt okuyabilir ve ardından bu bayt değerini bir sonraki yükleme işleminin adresini hesaplamak için shared_buffer aracılığıyla kullanabilir.

CPU sonunda bu yanlış tahmini algılasa da, artık yan etkiler, sınırların dışından okunan bayt değeri hakkındaki bilgileri ortaya koyan CPU önbelleğinde kalabilir. Bu yan etkiler, içindeki her önbellek satırına shared_buffer ne kadar hızlı erişilebileceğini araştırmak suretiyle sistemde çalışan daha az ayrıcalıklı bir bağlam tarafından algılanabilir. Bunu gerçekleştirmek için izleyebileceğiniz adımlar şunlardır:

  1. değerinden küçük olduğunda fonksiyonunu birden fazla kez çağırın. Saldırı bağlamı, ReadByte çağrısı yapılmasına (örneğin RPC aracılığıyla) öyle bir biçimde neden olabilir ki, dal tahmincisi untrusted_index'in buffer_size'den küçük olduğu durumda 'take' edilmemesi için eğitilir.

  2. içindeki shared_buffertüm önbellek satırlarını temizleyin. Saldıran bağlam, shared_buffer tarafından başvurulan belleğin paylaşılan bölgesindeki tüm önbellek satırlarını temizlemelidir. Bellek bölgesi paylaşıldığından bu basittir ve gibi _mm_clflushiç bilgileri kullanarak gerçekleştirilebilir.

  3. ReadByte, untrusted_indexbuffer_size'den büyük olduğunda çağır. Saldırı bağlamı, kurban bağlamın dalın alınmayacak olduğunu yanlış tahmin edecek şekilde çağırmasına ReadByte neden olur. Bu, işlemcinin untrusted_indexbuffer_size değerinden büyük olduğunda if bloğunun gövdesini tahmine dayalı olarak yürütmesine ve böylece buffer üzerinde sınır dışı bir okuma gerçekleştirmesine neden olur. Sonuç olarak, shared_buffer sınır dışı olarak okunan olası bir gizli dizi değeri kullanılarak dizine alınır ve bu da ilgili önbellek satırının CPU tarafından yüklenmesine neden olur.

  4. Her önbellek satırının en hızlı şekilde erişildiğini görmek için içindeki satırları okuyun. Saldırı bağlamı içindeki shared_buffer her önbellek satırını okuyabilir ve diğerlerinden önemli ölçüde daha hızlı yüklenen önbellek satırını algılayabilir. 3. adımdan getirilmiş olma olasılığı olan önbellek satırıdır. Bu örnekte bayt değeri ile önbellek satırı arasında 1:1 ilişkisi olduğundan, bu, saldırganın sınırların dışında okunan bayt değerini çıkarmasını sağlar.

Yukarıdaki adımlar, CVE-2017-5753 örneğinden yararlanma ile birlikte FLUSH+RELOAD olarak bilinen bir tekniği kullanma örneği sağlar.

Hangi yazılım senaryoları etkilenebilir?

Güvenlik Geliştirme Yaşam Döngüsü (SDL) gibi bir işlem kullanarak güvenli yazılım geliştirmek için genellikle geliştiricilerin kendi uygulamalarındaki güven sınırlarını belirlemesi gerekir. Bir uygulamanın sistemdeki başka bir işlem veya çekirdek modu cihaz sürücüsü söz konusu olduğunda yönetici olmayan bir kullanıcı modu işlemi gibi daha az güvenilen bir bağlam tarafından sağlanan verilerle etkileşim kurabileceği yerlerde güven sınırı vardır. Kurgusal yürütme tarafı kanallarını içeren yeni güvenlik açıkları sınıfı, bir cihazdaki kodu ve verileri yalıtan mevcut yazılım güvenlik modellerindeki güven sınırlarının çoğuyla ilgilidir.

Aşağıdaki tabloda geliştiricilerin ortaya çıkan bu güvenlik açıklarıyla ilgilenmeleri gerekebilecek yazılım güvenlik modellerinin bir özeti yer alır:

Güven sınırı Açıklama
Sanal makine sınırı Başka bir sanal makineden güvenilmeyen veriler alan ayrı sanal makinelerdeki iş yüklerini yalıtan uygulamalar risk altında olabilir.
Çekirdek sınırı Yönetici olmayan bir kullanıcı modu işleminden güvenilmeyen veriler alan çekirdek modu cihaz sürücüsü risk altında olabilir.
Süreç sınırı Uzak Yordam Çağrısı (RPC), paylaşılan bellek veya diğer İşlemler Arası İletişim (IPC) mekanizmaları gibi yerel sistemde çalışan başka bir işlemden güvenilmeyen veriler alan bir uygulama risk altında olabilir.
Yerleşke sınırı Güvenli bir kapanım içinde (Intel SGX gibi) yürütülen ve kapanım dışından güvenilmeyen veriler alan bir uygulama risk altında olabilir.
Dil sınırı Daha üst düzey bir dilde yazılmış güvenilmeyen kodu yorumlayan veya Tam Zamanında (JIT) derleyen ve yürüten bir uygulama risk altında olabilir.

Yukarıdaki güven sınırlarından herhangi birine maruz kalan saldırı yüzeyi olan uygulamalar, kurgusal yürütme tarafı kanal güvenlik açıklarının olası örneklerini belirlemek ve azaltmak için saldırı yüzeyindeki kodu gözden geçirmelidir. Uzak ağ protokolleri gibi uzak saldırı yüzeylerine maruz kalan güven sınırlarının kurgusal yürütme tarafı kanal güvenlik açıklarına karşı risk altında olduğu gösterilmediği belirtilmelidir.

Güvenlik açığı olabilecek kodlama desenleri

Birden çok kodlama deseninin sonucu olarak kurgusal yürütme tarafı kanal güvenlik açıkları ortaya çıkabilir. Bu bölümde, güvenlik açığı olabilecek kodlama desenleri açıklanır ve her biri için örnekler sağlanır, ancak bu temalardaki varyasyonların mevcut olabileceği kabul edilmelidir. Bu nedenle, geliştiricilerin bu desenleri örnek olarak alması ve güvenlik açığı olabilecek tüm kodlama desenlerinin kapsamlı bir listesi olarak almaları tavsiye edilir. Günümüzde yazılımlarda mevcut olabilecek bellek güvenliği açıklarının aynı sınıfları, arabellek taşmaları, sınır dışı dizi erişimleri, başlatılmamış bellek kullanımı, tür karışıklığı vb. dahil ancak bunlarla sınırlı olmamak üzere spekülatif ve sırasız yürütme yollarında da bulunabilir. Saldırganların mimari yollarda bellek güvenliği güvenlik açıklarından yararlanmak için kullanabileceği temel öğeler, kurgusal yollar için de geçerli olabilir.

Genel olarak, koşullu dal yanlış tahmininden kaynaklanan spekülatif yürütme yan kanalları, bir koşullu ifade daha az güvenilir bir bağlam tarafından denetlenen veya etkilenebilen veriler üzerinde çalıştığında ortaya çıkabilir. Örneğin, bu, if, for, while veya switch'de kullanılan koşullu ifadeleri veya üçlü ifadeleri içerebilir. Bu ifadelerin her biri için derleyici, CPU'nun çalışma zamanında dallanma hedefini tahmin edebileceği bir koşullu dallanma oluşturabilir.

Her örnek için, bir geliştiricinin bir engeli azaltma olarak tanıtabileceği "SPEKÜLASYON ENGELİ" ifadesini içeren bir açıklama eklenir. Bu, risk azaltmalar bölümünde daha ayrıntılı olarak ele alınıyor.

Tahmini sınır dışı yük

Kodlama desenlerinin bu kategorisi, spekülatif sınır dışı bellek erişimine yol açan bir koşullu dal yanlış tahminini içerir.

Bir yükü besleyen sınır dışı dizi yükü

Bu kodlama deseni aslında CVE-2017-5753 (Sınır Denetimi Atlama) için tanımlanan güvenlik açığı bulunan kodlama desenidir. Bu makalenin arka plan bölümünde bu desen ayrıntılı olarak açıklanmaktadır.

// 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];
    }
}

Benzer şekilde, dizinin sınırları dışında bir yükleme, yanlış tahmin nedeniyle döngünün sonlandırıcı koşulunu aşmasıyla birlikte oluşabilir. Bu örnekte, x < buffer_size ifadesi buffer_size'den büyük veya buna eşit olduğunda, ifadeyle ilişkili koşullu dal yanlış tahmin edebilir ve x döngüsünün for gövdesini tahmini olarak yürütebilir, sonuç olarak bu, tahmini bir sınır dışı yüklemeye neden olur.

// 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];
    }
}

Dizi sınır aşımı yüklemesi dolaylı bir dallanmaya yol açıyor

Bu kodlama deseni, koşullu dallanmanın yanlış tahmin edilmesinin, fonksiyon işaretçileri dizisine belirtilen sınırların dışına erişime yol açması ve ardından belirtilen sınırların dışında okunan hedef adrese dolaylı bir dala yol açması durumunu içerir. Aşağıdaki kod parçacığı bunu gösteren bir örnek sağlar.

Bu örnekte DispatchMessage'a parametresi aracılığıyla güvenilmeyen bir ileti tanımlayıcısı untrusted_message_id sağlanır. değerinden untrusted_message_idküçükseMAX_MESSAGE_ID, işlev işaretçileri dizisine dizin oluşturmak ve ilgili dal hedefine dal eklemek için kullanılır. Bu kod mimari açıdan güvenlidir, ancak CPU koşullu dalı yanlış tahmin ederse, DispatchTable değeri MAX_MESSAGE_ID değerinden büyük veya buna eşit olduğunda, untrusted_message_id tarafından dizine eklenmesi sonucunda sınır dışı erişime yol açabilir. Bu, dizi sınırlarının ötesinde türetilen bir dal hedef adresinden tahmini yürütmeye neden olabilir ve bu da tahmini olarak yürütülen koda bağlı olarak bilgilerin açığa çıkmasına neden olabilir.

#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);
    }
}

Başka bir yükü besleyen sınır dışı dizi yükünde olduğu gibi, bu durum da yanlış bir ifade nedeniyle sonlandırıcı koşulunu aşan bir döngüyle birlikte ortaya çıkabilir.

Dizi sınır dışı depo dolaylı dalı besliyor

Önceki örnekte, tahmini sınır dışı yükün dolaylı dal hedefini nasıl etkilediği gösterilmiş olsa da, sınır dışı deponun işlev işaretçisi veya dönüş adresi gibi dolaylı dal hedefini değiştirmesi de mümkündür. Bu, saldırgan tarafından belirtilen bir adresten tahmini yürütmeye yol açabilir.

Bu örnekte, güvenilmeyen bir dizin untrusted_index parametresi aracılığıyla geçirilir. Eğer untrusted_index dizinin öğe sayısından (256 öğe) küçükse, ptr içindeki sağlanan işaretçi değeri pointers dizisine yazılır. Bu kod mimari açıdan güvenlidir, ancak CPU koşullu dalı yanlış yorumlarsa yığın tarafından ayrılan ptr dizinin sınırlarının ötesinde tahmine dayalı olarak yazılmasıyla sonuçlanabilirpointers. Bu, WriteSlot için dönüş adresinin varsayımsal bozulmasına neden olabilir. Bir saldırgan ptr değerini denetleyebilirse, WriteSlot tahmini yol boyunca geri döndüğünde seçilen bir adresten tahmini yürütmeye neden olabilir.

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

Benzer şekilde, func adlı bir işlev işaretçisi yerel değişkeni yığında ayrılırsa, koşullu dallanma yanlış tahmini gerçekleştiğinde func'in başvurduğu adresi tahmine dayalı olarak değiştirmek mümkün olabilir. Bu, işlev işaretçisi aracılığıyla çağrıldığında rastgele bir adresten tahmini yürütmeye neden olabilir.

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();
}

Bu örneklerin her ikisinin de yığına ayrılmış dolaylı dal işaretçilerinin tahmini olarak değiştirilmesini içerdiği belirtilmelidir. Genel değişkenler, yığınla ayrılan bellek ve hatta bazı CPU'larda salt okunur bellek için kurgusal değişiklikler de gerçekleşebilir. Yığın üzerinde ayrılmış bellek için, Microsoft C++ derleyicisi, derleyicinin /GS güvenlik özelliği kapsamında yerel değişkenleri sıralayarak arabellekleri bir güvenlik tanımlama bilgisinin yakınına yerleştirir ve bu şekilde yığın üzerinde ayrılmış bellekle ilgili dolaylı dallanma hedeflerini tahmin etmeyi zorlaştırıcı adımlar atmaktadır.

Kurgusal tür karışıklığı

Bu kategori, kurgusal tür karışıklığına neden olabilecek kodlama desenleriyle ilgilidir. Bu, kurgusal yürütme sırasında mimari olmayan bir yol boyunca yanlış bir tür kullanılarak belleğe erişildiğinde oluşur. Hem koşullu dal tahmin hatası hem de tahmini depo atlaması tahmini tür karışıklığına yol açabilir.

Tahmini depo atlama için bu durum, derleyicinin birden çok türdeki değişkenler için bir yığın konumunu yeniden kullanacağı senaryolarda ortaya çıkabilir. Bunun nedeni, türündeki A bir değişkenin mimari deposunun atlanması ve dolayısıyla türündeki A yüklemenin değişken atanmadan önce tahmini olarak yürütülmesine olanak sağlamasıdır. Önceden depolanan değişken farklı bir türdeyse, bu tahmine dayalı tür karışıklığı koşullarını oluşturabilir.

Koşullu dal yanlış tahmini için, spekülatif tür karışıklığının ortaya çıkarabileceği farklı durumları açıklamak amacıyla aşağıdaki kod kısmı kullanılacaktır.

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;
    }
}

Sınır dışı yüke yol açan tahmini tür karışıklığı

Bu kodlama düzeni, tahmini tür karışıklığının, yüklenen değerin sonraki bir yük adresini beslediği sınırlar dışında veya tür karışıklığı alan erişimine neden olabileceği durumu içerir. Bu, sınır dışı dizi kodlama düzenine benzer, ancak yukarıda gösterildiği gibi alternatif bir kodlama dizisiyle gösterilir. Bu örnekte, saldırı bağlamı, kurban bağlamının ProcessType türündeki bir nesneyle defalarca yürütülmesine neden olabilir (type alanı Type1'e eşittir). Bu, ilk if deyimin alınmadığını tahmin etmek için koşullu dalı eğitme etkisine sahip olur. Saldırı bağlamı daha sonra kurban bağlamının CType2 türünde bir nesne ile ProcessType'ı yürütmesine neden olabilir. Bu, ilk if deyiminin koşullu dalı yanlış tahmin edilip if deyiminin gövdesi çalıştırıldığında, bir CType2 türündeki nesnenin CType1 olarak atanmasına neden olarak tahmini tür karışıklığına yol açabilir. CType2 CType1'dan küçük olduğundan, CType1::field2'a bellek erişimi, gizli olabilecek verilerin tahmini olarak sınır dışı yüklenmesine neden olur. Bu değer, daha önce açıklanan sınır dışı dizi örneğindeki gibi gözlemlenebilir yan etkiler yaratma potansiyeline sahip olan shared_buffer'den bir yükleme işlemi için kullanılır.

Spekülatif tür karışıklığı dolaylı dallanmaya yol açıyor.

Bu kodlama düzeni, kurgusal tür karışıklığının kurgusal yürütme sırasında güvenli olmayan bir dolaylı dala neden olabileceği durumu içerir. Bu örnekte, saldırı bağlamı kurban bağlamının ProcessType'i, CType2 türünde bir nesneyle birden çok kez yürütmesine neden olabilir (type alanı Type2'ye eşittir). Bu, ilk if deyimin alınması ve else if deyiminin alınmaması için koşullu dalı eğitme etkisine sahiptir. Saldırı bağlamı daha sonra, bir CType1 türündeki nesneyle ProcessType'yi kurban bağlamında yürütülmesini sağlayabilir. Bu, spekülatif bir tür karışıklığına yol açabilir, eğer ilk if deyiminin koşullu dalı alınacak şekilde ve else if deyimi alınmayacak şekilde tahmin edilir, bu nedenle else if gövdesini yürütür ve CType1 türündeki bir nesneyi CType2'ye dönüştürürse. CType2::dispatch_routine alanı char dizisiyle çakıştığı için, bu, istenmeyen bir dal hedefine spekülatif bir dolaylı dallanmaya neden olabilir. Saldırı bağlamı dizideki CType1::field1 bayt değerlerini denetleyebilirse, dal hedef adresini denetleyebilir.

Öngörüsel başlatılmamış kullanım

Bu kodlama desenleri kategorisi, kurgusal yürütmenin başlatılmamış belleğe erişebileceği ve bunu sonraki bir yükü veya dolaylı dalı beslemek için kullanabileceği senaryoları içerir. Bu kodlama düzenlerinin kötüye kullanılabilmesi için, saldırganın kullanıldığı bağlam tarafından başlatılmadan kullanılan belleğin içeriğini denetleyebilmesi veya anlamlı bir şekilde etkileyebilmesi gerekir.

Sınır dışı yüke yol açan tahmini başlatılmamış kullanım

Spekülatif başlatılmamış kullanım, saldırgan tarafından denetlenen bir değer kullanılarak potansiyel olarak sınırları aşan bir yüklemeye yol açabilir. Aşağıdaki örnekte, index değeri tüm mimari yollara trusted_index olarak atanır ve trusted_index'nin buffer_size'e eşit veya ondan küçük olduğu varsayılır. Ancak, derleyici tarafından üretilen koda bağlı olarak, buffer[index] ile bağımlı ifadelerden oluşan yükün, index atamasından önce yürütülmesine izin veren bir tahmini depolama atlaması oluşabilir. Bu durumda, index için başlatılmamış bir değer, buffer içinde bir offset olarak kullanılır; bu durum, bir saldırganın hassas bilgileri sınırların ötesinde okumasını sağlayabilir ve bu bilgileri, bağımlı yükleme yoluyla shared_buffer üzerinden bir yan kanal vasıtasıyla iletmesine olanak tanır.

// 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];
}

Başlatılmamış spekülatif kullanım sonucunda dolaylı dallanma

Varsayımsal bir başlatılmamış kullanım durumu, dal hedefinin bir saldırgan tarafından kontrol edildiği dolaylı bir dala yol açabilir. Aşağıdaki örnekte, routine'nin değeri mode'e bağlı olarak ya DefaultMessageRoutine1 ya da DefaultMessageRoutine olarak atanır. Mimari yolda, bu durum her zaman dolaylı daldan önce başlatılmasına neden routine olur. Ancak, derleyici tarafından üretilen koda bağlı olarak, spekülatif bir depolama atlaması oluşabilir ve bu, dolaylı dallanmanın, routine atamasından önce routine üzerinden tahmini olarak yürütülmesine imkan tanır. Bu meydana gelirse, başlatılmamış değerin saldırgan tarafından etkilenebileceği veya kontrol edilebileceği varsayılırsa, saldırgan rastgele bir adresten tahmine dayalı yürütme gerçekleştirebilir.

#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);
}

Azaltma seçenekleri

Kaynak kodda değişiklik yapılarak kurgusal yürütme tarafı kanal güvenlik açıkları azaltılabilir. Bu değişiklikler, spekülasyon engeli eklemek veya hassas bilgilerin tahmini yürütmeye erişilemez hale getirilmesi için uygulamanın tasarımında değişiklikler yapmak gibi bir güvenlik açığının belirli örneklerini azaltmayı içerebilir.

Elle yapılan ölçüm veya enstrüman kullanımı yoluyla spekülasyon önleyici.

Kurgusal yürütmenin mimari olmayan bir yol boyunca ilerlemesini önlemek için bir geliştirici tarafından el ile bir spekülasyon engeli eklenebilir. Örneğin, bir geliştirici, koşullu bloğun gövdesinde tehlikeli bir kodlama düzeninden önce, bloğun başına (koşullu daldan sonra) veya endişe duyulan ilk yüklemeden önce bir spekülasyon engeli ekleyebilir. Bu, koşullu dallanma yanlış tahmininin, yürütmeyi sıralı hale getirerek mimari olmayan bir yolda tehlikeli kodu çalıştırmasını engeller. Spekülasyon engeli dizisi, aşağıdaki tabloda açıklandığı gibi donanım mimarisine göre farklılık gösterir:

Mimari CVE-2017-5753 için doğal spekülasyon engeli CVE-2018-3639 için içsel spekülasyon engeli
x86/x64 _mm_lfence() _mm_lfence()
KOL şu anda kullanılamıyor __dsb(0)
ARM64 şu anda kullanılamıyor __dsb(0)

Örneğin, aşağıdaki kod deseni, aşağıda gösterildiği gibi _mm_lfence yerleşik kullanılarak azaltılabilir.

// 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];
    }
}

Derleyici zamanı enstrümantasyonu aracılığıyla spekülasyon bariyeri

Visual Studio 2017'deki Microsoft C++ derleyicisi (sürüm 15.5.5'ten itibaren), CVE-2017-5753 ile ilgili olası güvenlik açıklarına sahip sınırlı sayıda kodlama deseni için otomatik olarak bir spekülasyon bariyeri ekleyen /Qspectre anahtarı için destek içerir. Bayrağın /Qspectre belgeleri, etkileri ve kullanımı hakkında daha fazla bilgi sağlar. Bu bayrağın tüm güvenlik açığı olabilecek kodlama desenlerini kapsamadığını ve bu tür geliştiricilerin bu güvenlik açıkları sınıfı için kapsamlı bir azaltma olarak buna güvenmemesi gerektiğini unutmayın.

Dizi dizinlerini maskeleme

Tahmini sınır dışı yükün oluşabileceği durumlarda dizi dizini, dizi dizinini açıkça bağlamaya yönelik mantık ekleyerek hem mimari hem de mimari olmayan yolda kesin olarak sınırlanabilir. Örneğin, bir dizi 2'nin kuvvetine uygun bir boyutta hizalanabiliyorsa, basit bir maske kullanılabilir. Bu, buffer_size'ın iki'nin katlarına hizalandığı varsayılan aşağıdaki örnekte gösterilmiştir. Bu, bir koşullu dal yanlış tahmini meydana gelse ve untrusted_index değeri buffer_size'ten büyük veya buna eşit olarak iletilse bile, untrusted_index'ın her zaman buffer_size'den küçük olmasını sağlar.

Burada gerçekleştirilen dizin maskelemenin, derleyici tarafından oluşturulan koda bağlı olarak tahmini depo atlamasına tabi olabileceği belirtilmelidir.

// 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];
    }
}

Bellekten hassas bilgileri kaldırma

Kurgusal yürütme tarafı kanal güvenlik açıklarını azaltmak için kullanılabilecek bir diğer teknik de hassas bilgileri bellekten kaldırmaktır. Yazılım geliştiricileri, kurgusal yürütme sırasında hassas bilgilere erişilmeyecek şekilde uygulamalarını yeniden düzenleme fırsatı arayabilir. Bu, hassas bilgileri ayrı işlemler halinde yalıtmak için bir uygulamanın tasarımını yeniden düzenleyerek gerçekleştirilebilir. Örneğin, bir web tarayıcısı uygulaması her web kaynağıyla ilişkili verileri ayrı işlemlere ayırmayı deneyerek bir işlemin tahmini yürütme yoluyla çıkış noktaları arası verilere erişmesini engelleyebilir.

Ayrıca bkz.

Kurgusal yürütme yan kanal güvenlik açıklarını azaltma kılavuzu
Kurgusal yürütme tarafı kanal donanım güvenlik açıklarını azaltma