Sdílet prostřednictvím


Pokyny pro vývojáře C++ k útokům souvisejícím se spekulativním spouštěním postranních kanálů

Tento článek obsahuje pokyny, které vývojářům pomůžou identifikovat a zmírnit ohrožení zabezpečení hardwaru kanálu na straně spekulativního spuštění v softwaru C++. Tato ohrožení zabezpečení můžou odhalit citlivé informace přes hranice důvěryhodnosti a mohou ovlivnit software, který běží na procesorech, které podporují spekulativní provádění instrukcí mimo pořadí. Tuto třídu ohrožení zabezpečení jsme poprvé popsali v lednu 2018 a další základní informace a pokyny najdete v poradci Microsoftu pro zabezpečení.

Pokyny uvedené v tomto článku se týkají tříd ohrožení zabezpečení reprezentovaných:

  1. CVE-2017-5753, označovaná také jako Spectre varianta 1. Tato třída ohrožení zabezpečení hardwaru souvisí s kanály na straně, které mohou vzniknout kvůli spekulativnímu spuštění, ke kterému dochází v důsledku chybného diktování podmíněné větve. Kompilátor Microsoft C++ v sadě Visual Studio 2017 (počínaje verzí 15.5.5) zahrnuje podporu /Qspectre přepínače, která poskytuje omezení času kompilace pro omezenou sadu potenciálně ohrožených vzorů kódování souvisejících s CVE-2017-5753. Přepínač /Qspectre je k dispozici také v sadě Visual Studio 2015 Update 3 až KB 4338871. Dokumentace k příznaku /Qspectre obsahuje další informace o jeho vlivu a využití.

  2. CVE-2018-3639, označované také jako spekulativní obejití úložiště (SSB) Tato třída ohrožení zabezpečení hardwaru souvisí s kanály na straně, které mohou vzniknout kvůli spekulativnímu spuštění zatížení před závislým úložištěm v důsledku chybného diktování přístupu k paměti.

V prezentaci s názvem Case of Spectre a Meltdown v jednom z výzkumných týmů, které tyto problémy objevily, najdete přístupný úvod ke spekulativnímu spuštění kanálu.

Co jsou ohrožení zabezpečení hardwaru na straně spekulativního spuštění kanálu?

Moderní procesory poskytují vyšší stupeň výkonu tím, že využívají spekulativní a zastaralé provádění instrukcí. To se například často dosahuje predikcí cíle větví (podmíněných a nepřímých), které procesoru umožní zahájit spekulativní provádění instrukcí v předpovídaném cíli větve, čímž se zabrání zastavení, dokud se skutečný cíl větve nevyřeší. V případě, že procesor později zjistí, že došlo k nesprávnému předdiktování, je veškerý stav počítače vypočítaný spekulativním způsobem zahozen. Tím se zajistí, že neexistují žádné architektonické viditelné účinky nesprávně nadiktovaných spekulací.

I když spekulativní spouštění nemá vliv na architekturálně viditelný stav, může ponechat reziduální trasování v nekonkreativním stavu, jako jsou různé mezipaměti používané procesorem. Jedná se o reziduální stopy spekulativního spuštění, které můžou vést k ohrožením zabezpečení na straně kanálu. Abyste tomu lépe porozuměli, zvažte následující fragment kódu, který poskytuje příklad CVE-2017-5753 (Obejití kontroly hranic):

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

V tomto příkladu ReadByte se do této vyrovnávací paměti zadává vyrovnávací paměť, velikost vyrovnávací paměti a index. Parametr indexu, jak je specifikován untrusted_index, je poskytován méně privilegovaným kontextem, například procesem, který není správcem. Pokud untrusted_index je menší než buffer_size, znak v tomto indexu se načte buffer a používá se k indexování do sdílené oblasti paměti, na kterou shared_bufferodkazuje .

Z hlediska architektury je tato sekvence kódu dokonale bezpečná, protože zaručuje, že untrusted_index vždy bude menší než buffer_size. Nicméně, v přítomnosti spekulativního spuštění je možné, že procesor přepíše podmíněnou větev a spustí tělo příkazu if, i když untrusted_index je větší než nebo rovno buffer_size. V důsledku toho může procesor spekulativní čtení bajtu z hranice buffer (které by mohlo být tajným kódem) a pak by tuto bajtovou hodnotu mohl použít k výpočtu adresy následného zatížení shared_buffer.

Zatímco procesor nakonec zjistí tuto chybnou předpoložku, reziduální vedlejší účinky mohou být ponechány v mezipaměti procesoru, které odhalí informace o bajtové hodnotě, která byla načtena z bufferhranic . Tyto vedlejší účinky můžou být zjištěny méně privilegovaným kontextem spuštěným v systému tím, že zjišťují, jak rychle se ke každému řádku mezipaměti přistupuje shared_buffer . Kroky, které je možné provést k dosažení těchto kroků:

  1. Vyvolání ReadByte vícekrát s menší než buffer_sizeuntrusted_index. Kontext útoku může způsobit vyvolání kontextu oběti (např. prostřednictvím RPC), aby se prediktor větve trénoval tak, aby se nepřebídal ReadByte , protože untrusted_index je menší než buffer_size.

  2. Vyprázdnění všech řádků mezipaměti v shared_buffer. Kontext útoku musí vyprázdnit všechny řádky mezipaměti ve sdílené oblasti paměti, na kterou shared_bufferodkazuje . Vzhledem k tomu, že oblast paměti je sdílena, je to jednoduché a lze ji provést pomocí vnitřních objektů, jako _mm_clflushje .

  3. Vyvolat ReadByte s untrusted_index tím, že je větší než buffer_size. Kontext útoku způsobí, že se kontext oběti vyvolá ReadByte tak, že nesprávně předpovídá, že větev nebude přijata. To způsobí, že procesor spekulativně spustí tělo bloku if s untrusted_index větší než buffer_size, což vede ke čtení buffermimo hranice . shared_buffer Proto se indexuje pomocí potenciálně tajné hodnoty, která byla načtena mimo hranice, což způsobí načtení příslušného řádku mezipaměti procesorem.

  4. Přečtěte si každý řádek shared_buffer mezipaměti, abyste zjistili, ke kterému z nich se dostanete co nejrychleji. Kontext útoku může číst každý řádek shared_buffer mezipaměti a zjišťovat řádek mezipaměti, který se načítá výrazně rychleji než ostatní. Jedná se o čáru mezipaměti, která pravděpodobně byla přenesena krokem 3. Vzhledem k tomu, že v tomto příkladu existuje vztah 1:1 mezi bajtovou hodnotou a řádkem mezipaměti, umožňuje útočníkovi odvodit skutečnou hodnotu bajtu, která byla načtena mimo hranice.

Výše uvedené kroky poskytují příklad použití techniky označované jako FLUSH+RELOAD ve spojení s zneužitím instance CVE-2017-5753.

Jaké softwarové scénáře můžou mít vliv?

Vývoj zabezpečeného softwaru pomocí procesu, jako je SDL (Security Development Lifecycle ), obvykle vyžaduje, aby vývojáři identifikovali hranice důvěryhodnosti, které existují ve své aplikaci. Hranice důvěryhodnosti existuje na místech, kde může aplikace pracovat s daty poskytovanými méně důvěryhodným kontextem, například jiným procesem v systému nebo procesem uživatelského režimu bez správy v případě ovladače zařízení v režimu jádra. Nová třída ohrožení zabezpečení zahrnující spekulativní kanály na straně spuštění je relevantní pro mnoho hranic důvěryhodnosti v existujících modelech zabezpečení softwaru, které izolují kód a data na zařízení.

Následující tabulka obsahuje souhrn modelů zabezpečení softwaru, kterých se mohou vývojáři zabývat těmito chybami zabezpečení:

Hranice důvěryhodnosti Popis
Hranice virtuálního počítače Aplikace, které izolují úlohy v samostatných virtuálních počítačích, které přijímají nedůvěryhodná data z jiného virtuálního počítače, mohou být ohroženy.
Hranice jádra Ovladač zařízení v režimu jádra, který přijímá nedůvěryhodná data z procesu nesprávě uživatelského režimu, může být ohrožen.
Hranice procesu Aplikace, která přijímá nedůvěryhodná data z jiného procesu spuštěného v místním systému, například prostřednictvím vzdáleného volání procedur (RPC), sdílené paměti nebo jiných mechanismů komunikace mezi procesy (IPC) může být ohrožena.
Hranice enklávy Aplikace, která se spouští v rámci zabezpečené enklávy (například Intel SGX), která přijímá nedůvěryhodná data mimo enklávu, může být ohrožena.
Hranice jazyka Aplikace, která interpretuje nebo kompiluje JIT (Just-In-Time) a spouští nedůvěryhodný kód napsaný v jazyce vyšší úrovně, může být ohrožen.

Aplikace, které mají prostor pro útoky vystavené kterékoli z výše uvedených hranic důvěryhodnosti, by měly zkontrolovat kód na ploše útoku a identifikovat a zmírnit možné výskyty ohrožení zabezpečení kanálu na straně spekulativního spuštění. Je třeba poznamenat, že hranice důvěryhodnosti vystavené vzdáleným útokům, jako jsou vzdálené síťové protokoly, nebyly ukázáno, že jsou ohroženy spekulativními chybami kanálu na straně spuštění.

Potenciálně zranitelné vzory kódování

Spekulativní ohrožení zabezpečení kanálu na straně spuštění může vzniknout v důsledku několika vzorů kódování. Tato část popisuje potenciálně zranitelné vzory kódování a poskytuje příklady pro každou z nich, ale měla by být rozpoznána, že varianty těchto motivů mohou existovat. Proto se vývojářům doporučuje, aby tyto vzory brali jako příklady, a ne jako vyčerpávající seznam všech potenciálně ohrožených vzorů kódování. Stejné třídy ohrožení zabezpečení paměti, které mohou existovat v softwaru dnes, mohou existovat také u spekulativních a mimoobjednávých cest provádění, včetně mimosměrných přetečení vyrovnávací paměti, přístupů k polím mimo hranice, použití neinicializované paměti, záměny typů atd. Stejné primitivy, které útočníci můžou použít ke zneužití ohrožení zabezpečení paměti v rámci cest architektury, se také můžou vztahovat na spekulativní cesty.

Obecně platí, že spekulativní kanály na straně spouštění související s chybným slovníkem podmíněné větve mohou nastat, když podmíněný výraz pracuje s daty, která mohou být řízena nebo ovlivněna méně důvěryhodným kontextem. Může to například zahrnovat podmíněné výrazy používané v ifpříkazech , for, while, switchnebo ternárních výrazů. Pro každý z těchto příkazů může kompilátor vygenerovat podmíněnou větev, kterou procesor pak může předpovědět cíl větve za běhu.

Pro každý příklad se vloží komentář s frází "SPEKULACE BARIÉRA", kde by vývojář mohl zavést bariéru jako zmírnění rizik. Toto je podrobněji popsáno v části věnované zmírnění rizik.

Spekulativní zatížení mimo hranice

Tato kategorie vzorů kódování zahrnuje chybné diktování podmíněné větve, která vede ke spekulativnímu přístupu k paměti mimo hranice.

Zatížení v mezích pole, které je zatíženo zatížením

Tento vzor kódování je původně popsaný ohrožený vzor kódování pro CVE-2017-5753 (Bounds Check Bypass). Základní část tohoto článku podrobně vysvětluje tento vzor.

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

Podobně může dojít k odchozímu zatížení pole ve spojení se smyčkou, která překračuje jeho ukončovací podmínku kvůli chybnému diktování. V tomto příkladu může podmíněná větev přidružená k x < buffer_size výrazu nesprávně nadiktovat a spekulativním spuštěním textu for smyčky, pokud x je větší nebo rovna buffer_size, což vede k spekulativnímu zatížení mimo hranice.

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

Maticové mimohraniční zatížení dodávající nepřímou větev

Tento vzor kódování zahrnuje případ, kdy může chybně diktování podmíněné větve vést k odchozímu přístupu k poli ukazatelů funkce, což pak vede k nepřímé větvi na cílovou adresu, která byla přečtená mimo hranice. Následující fragment kódu poskytuje příklad, který to demonstruje.

V tomto příkladu je pro DispatchMessage prostřednictvím parametru untrusted_message_id zadaný nedůvěryhodný identifikátor zprávy. Pokud untrusted_message_id je menší než MAX_MESSAGE_ID, použije se k indexování do pole ukazatelů funkce a větve do odpovídajícího cíle větve. Tento kód je bezpečný na architektuře, ale pokud procesor nesprávně zapisuje podmíněnou větev, může vést DispatchTable k indexování, untrusted_message_id pokud je jeho hodnota větší nebo rovna MAX_MESSAGE_ID, což vede k přístupu mimo hranice. To může vést ke spekulativnímu spuštění z cílové adresy větve, která je odvozena nad hranice pole, což může vést ke zpřístupnění informací v závislosti na kódu, který se spouští spekulativním způsobem.

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

Stejně jako u zatížení mimo hranice pole, které je zatíženo jiným zatížením, může tato podmínka vzniknout také ve spojení se smyčkou, která překračuje jeho ukončovací podmínku kvůli nesprávnému diktování.

Maticové mimohraniční úložiště dodávající nepřímou větev

I když předchozí příklad ukázal, jak může spekulativní zatížení mimo hranice ovlivnit cíl nepřímé větve, je také možné, aby mimohraniční úložiště upravilo cíl nepřímé větve, jako je ukazatel funkce nebo návratová adresa. To může potenciálně vést ke spekulativnímu spuštění z adresy zadané útočníkem.

V tomto příkladu se prostřednictvím parametru untrusted_index předává nedůvěryhodný index. Pokud untrusted_index je menší než počet pointers prvků pole (256 prvků), zadaná hodnota ukazatele je ptr zapsána pointers do pole. Tento kód je bezpečný na architektuře, ale pokud procesor chybně zapíše podmíněnou větev, může to vést ptr k spekulativnímu zápisu nad hranicemi pole přiděleného pointers zásobníkem. To by mohlo vést ke spekulativnímu poškození zpáteční adresy pro WriteSlot. Pokud útočník může řídit hodnotu ptr, může být schopen způsobit spekulativní spuštění z libovolné adresy, když WriteSlot se vrátí po spekulativní cestě.

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

Podobně platí, že pokud byla v zásobníku přidělena místní proměnná func ukazatele na funkci, může být možné spekulativně upravit adresu, která func odkazuje, když dojde k chybnému diktování podmíněné větve. Výsledkem může být spekulativní spuštění z libovolné adresy, když je ukazatel funkce volána.

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

Je třeba poznamenat, že oba tyto příklady zahrnují spekulativní úpravy nepřímých ukazatelů větví přidělených zásobníkem. Je možné, že k spekulativní úpravě může dojít také pro globální proměnné, paměť přidělenou haldou a dokonce i paměť jen pro čtení v některých procesorech. Pro paměť přidělenou zásobníkem už kompilátor C++ provádí kroky, které ztěžují spekulativní úpravu cílů nepřímých větví přidělených zásobníkem, například změnou pořadí místních proměnných tak, aby vyrovnávací paměti byly umístěny vedle souboru cookie zabezpečení jako součást funkce zabezpečení kompilátoru /GS .

Nejasnosti spekulativního typu

Tato kategorie se zabývá vzory kódování, které můžou vést ke spekulativnímu zmatení typu. K tomu dochází při přístupu k paměti pomocí nesprávného typu podél cesty, která není architektura během spekulativního spuštění. Chybně diktování podmíněné větve i spekulativní obejití úložiště můžou potenciálně vést ke nejasnostem spekulativního typu.

U spekulativního obejití úložiště by k tomu mohlo dojít ve scénářích, kdy kompilátor opakovaně používá umístění zásobníku pro proměnné více typů. Důvodem je to, že úložiště architektury proměnné typu A může být vynecháno, což umožňuje spekulativnímu spuštění zatížení typu A před přiřazením proměnné. Pokud je dříve uložená proměnná jiného typu, může to vytvořit podmínky pro spekulativní záměny typu.

V případě chybného diktování podmíněné větve se použije následující fragment kódu k popisu různých podmínek, ke kterým může dojít ke nejasnostem spekulativního typu.

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

Spekulativní záměna typu, která vede k odchozímu zatížení

Tento vzor kódování zahrnuje případ, kdy spekulativní záměna typu může vést k nevázaných nebo zaměňovaných polí, kde načtená hodnota podává následnou adresu načtení. To se podobá vzoru kódování mimo hranice pole, ale manifestuje se prostřednictvím alternativní sekvence kódování, jak je znázorněno výše. V tomto příkladu může napadený kontext způsobit, že se kontext oběti spustí ProcessType několikrát s objektem typu CType1 (type pole se rovná Type1). To bude mít vliv na trénování podmíněné větve pro první if příkaz, aby se nepředpovídal. Útočný kontext pak může způsobit spuštění ProcessType kontextu oběti s objektem typu CType2. To může vést ke nejasnostem spekulativního if typu, pokud podmíněná větev prvního if příkazu nesprávně nadiktuje a spustí tělo příkazu, čímž přetypuje objekt typu CType2 na CType1. Vzhledem k tomu CType2 , že je menší než CType1, bude mít přístup k CType1::field2 paměti za následek spekulativní odchozí zatížení dat, která mohou být tajná. Tato hodnota se pak použije při zatížení, ze shared_buffer kterého může vzniknout pozorovatelné vedlejší účinky, stejně jako u výše popsaného příkladu pole mimo hranice.

Spekulativní záměna typu vedoucí k nepřímé větvi

Tento model kódování zahrnuje případ, kdy spekulativní záměna typu může během spekulativního spuštění vést k nebezpečné nepřímé větvi. V tomto příkladu může napadený kontext způsobit, že se kontext oběti spustí ProcessType několikrát s objektem typu CType2 (type pole se rovná Type2). To bude mít vliv na trénování podmíněné větve pro první if příkaz, který se má provést, a else if příkaz, který se má přijmout. Útočný kontext pak může způsobit spuštění ProcessType kontextu oběti s objektem typu CType1. To může vést ke nejasnostem spekulativního typu v případě, že podmíněná větev prvního if příkazu předpovídá, a else if příkaz předpovídá, že se nepřevezmou, a tím se spustí tělo a přetypování objektu typu CType1 na CType2.else if CType2::dispatch_routine Vzhledem k tomu, že se pole překrývají s char polemCType1::field1, může to vést ke spekulativní nepřímé větvi do nezamýšleného cíle větve. Pokud může napadený kontext řídit hodnoty bajtů v CType1::field1 poli, můžou mít kontrolu nad cílovou adresou větve.

Spekulativní neinicializované použití

Tato kategorie vzorů kódování zahrnuje scénáře, kdy spekulativní spouštění může přistupovat k neinicializované paměti a používat ji k následnému načtení nebo nepřímé větvi. Aby bylo možné tyto vzory kódování zneužít, musí být útočník schopný řídit nebo smysluplně ovlivnit obsah paměti, která se používá bez inicializace kontextu, ve které se používá.

Spekulativní neinicializované použití vedoucí k odchozímu zatížení

Spekulativní neinicializované použití může potenciálně vést k odchozímu zatížení pomocí řízené hodnoty útočníka. V následujícím příkladu je hodnota index přiřazena trusted_index pro všechny architektonické cesty a trusted_index předpokládá se, že je menší nebo rovna buffer_size. V závislosti na kódu vytvořeném kompilátorem však může dojít ke spekulativnímu obejití úložiště, které umožňuje načtení ze buffer[index] závislých výrazů provést před přiřazením index. V takovém případě se jako posun použije neinicializovaná hodnota index , do buffer které by útočník mohl číst citlivé informace mimo hranice a předávat je prostřednictvím bočního kanálu prostřednictvím závislé zátěže 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];
}

Spekulativní neinicializované použití vedoucí k nepřímé větvi

Spekulativní neinicializované použití může potenciálně vést k nepřímé větvi, ve které je cíl větve řízen útočníkem. V následujícím routine příkladu je přiřazen buď DefaultMessageRoutine1 nebo DefaultMessageRoutine v závislosti na hodnotě .mode Na cestě architektury to způsobí, že se routine vždy inicializuje před nepřímou větví. V závislosti na kódu vytvořeném kompilátorem však může dojít k spekulativnímu obejití úložiště, které umožňuje spekulativnímu spuštění nepřímé větve routine před přiřazením routine. Pokud k tomu dojde, útočník může být schopen spekulativního spuštění z libovolné adresy za předpokladu, že útočník může ovlivnit nebo řídit neinicializovanou hodnotu 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);
}

Možnosti omezení rizik

Spekulativní ohrožení zabezpečení kanálu na straně spuštění je možné zmírnit provedením změn ve zdrojovém kódu. Tyto změny můžou zahrnovat zmírnění konkrétních instancí ohrožení zabezpečení, například přidáním spekulační bariéry nebo provedením změn návrhu aplikace, aby byly citlivé informace nepřístupné spekulativnímu spuštění.

Spekulační bariéra prostřednictvím ruční instrumentace

Spekulační bariéru může vývojář vložit ručně, aby zabránil spekulativnímu spuštění v cestě mimo architekturu. Vývojář může například vložit spekulaci před nebezpečný vzor kódování v těle podmíněného bloku, a to buď na začátku bloku (po podmíněné větvi), nebo před prvním zatížením, které je důležité. Tím zabráníte nesprávnému předdiktování podmíněné větve spuštění nebezpečného kódu na cestě mimo architekturu serializací spuštění. Posloupnost spekulací bariér se liší podle hardwarové architektury, jak je popsáno v následující tabulce:

Architektura Spekulovaná bariéra vnitřní pro CVE-2017-5753 Spekulovaná bariéra vnitřní pro CVE-2018-3639
x86/x64 _mm_lfence() _mm_lfence()
ARM momentálně není k dispozici __dsb(0)
ARM64 momentálně není k dispozici __dsb(0)

Následující vzor kódu je například možné zmírnit pomocí _mm_lfence vnitřního kódu, jak je znázorněno níže.

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

Spekulační bariéra prostřednictvím instrumentace kompilátoru

Kompilátor Microsoft C++ v sadě Visual Studio 2017 (počínaje verzí 15.5.5) zahrnuje podporu /Qspectre přepínače, která automaticky vloží spekulaci pro omezenou sadu potenciálně ohrožených vzorů kódování souvisejících s CVE-2017-5753. Dokumentace k příznaku /Qspectre obsahuje další informace o jeho vlivu a využití. Je důležité si uvědomit, že tento příznak nepokrývá všechny potenciálně ohrožené vzory kódování, a proto by se na něj vývojáři neměli spoléhat jako na komplexní zmírnění této třídy ohrožení zabezpečení.

Maskování indexů pole

V případech, kdy může dojít k spekulativnímu zatížení mimo hranice, může být index pole silně ohraničený jak na cestě architektury, tak i mimo architekturu přidáním logiky pro explicitní vazbu indexu pole. Pokud je například možné přidělit pole velikosti, která je zarovnaná k mocnině dvou, lze zavést jednoduchou masku. To je znázorněno v ukázce níže, kde se předpokládá, že buffer_size je zarovnaná k mocnině dvou. Tím se zajistí, že untrusted_index je vždy menší než buffer_size, i když dojde k chybnému diktování podmíněné větve a untrusted_index byla předána s hodnotou větší než nebo rovno buffer_size.

Je třeba poznamenat, že maskování indexu prováděné zde může být předmětem spekulativního obejití úložiště v závislosti na kódu, který je generován kompilátorem.

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

Odebírání citlivých informací z paměti

Další technikou, kterou lze použít ke zmírnění ohrožení zabezpečení kanálu na straně spekulativního spuštění, je odebrání citlivých informací z paměti. Vývojáři softwaru můžou hledat příležitosti k refaktoringu aplikace tak, aby citlivé informace nebyly během spekulativního spouštění přístupné. Toho lze dosáhnout refaktorováním návrhu aplikace tak, aby se citlivé informace izolovaly do samostatných procesů. Aplikace webového prohlížeče se například může pokusit izolovat data přidružená k jednotlivým webovým zdrojům do samostatných procesů, čímž zabrání tomu, aby jeden proces mohl přistupovat k datům mezi zdroji prostřednictvím spekulativního spuštění.

Viz také

Pokyny ke zmírnění spekulativního spuštění ohrožení zabezpečení na straně kanálu
Zmírnění ohrožení zabezpečení hardwaru na straně spekulativního spuštění kanálu