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:
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í.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_buffer
odkazuje .
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 buffer
hranic . 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ů:
Vyvolání
ReadByte
vícekrát s menší nežbuffer_size
untrusted_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ídalReadByte
, protožeuntrusted_index
je menší nežbuffer_size
.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 kteroushared_buffer
odkazuje . 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_clflush
je .Vyvolat
ReadByte
suntrusted_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 suntrusted_index
větší nežbuffer_size
, což vede ke čteníbuffer
mimo 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.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ý řádekshared_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 if
příkazech , for
, while
, switch
nebo 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