Takové procesory jsme už měli: 8086, 286, 386 (bez cache). V podstatě jakýkoliv CPU, který provádí operace v pevně daném počtu cyklů bez ohledu na to, jestli už danou operaci nebo adresu v paměti už viděl nebo ne...
Přítomnost cache ovlivňuje dobu provádění instrukcí i dobu přístupu k paměti na základě dříve vykonávaného kódu, takže umožňuje existenci časového bočního kanálu (side channel). Jedná se o 386 s cache na MB, 486 a Pentium.
Spekulativní provádění instrukcí tomu pak nasazuje vyšší laťku, protože umožňuje procesoru vykonávat program "do předu" a za specifických podmínek lze do cache nahrát nebo nenahrát i data, ke kterým by daný program neměl mít vůbec žádný přístup. Časováním přístupu do cache se dá pak vyčíst jestli operace proběhla, tudíž jestli byl hledaný bit nastavený na 0 nebo 1 (Spectre). Možností zneužití přibývá se složitostí implementace procesorů a množstvím vychytávek pro zrychlení různých operací. Pentium Pro, P6 a výš...
Cache není nutně problém samo o sobě. Problém je, pokud jde obsah dostupné cache ovlivnit daty, která by neměla být přístupná. Což je případ mj. Spectre.
Samotný fakt, že lze nahrát do cache „nepřístupná“ data ještě teoreticky nemusí být problém. Problém je, že na základě „nepřístupných“ dat lze do cache nahrát data, ke kterým přistupovat lze, a tedy na nich lze měřit dobu přístupu, z čehož lze usuzovat přítomnost v cache. Problém by to mohl být i v případě jiného ovlivnění viditelné části cache, například skrze cache eviction.
Uznávám, že jsem svůj popis Spectre útoku zjednodušil až moc, ale nechtělo se mi to rozepisovat do posledního detailu. Kdo má zájem, může si to nastudovat nebo to už zná.
Na druhou stranu, cache představuje jakýsi 'otisk prstu', který za sebou zanechává každá zpracovaná instrukce. Pokud si někdo dá tu práci, aby přesně zmapoval co sledovaný program dělá, tak může z obsahu cache vyextrahovat nějaké informace o předchozím běhu programu.
Názorně je to vysvětleno v těchto článcích z roku 2005 (tedy více než desetiletí před útoky na spekulativní vykonávání, BTB, atd.). Tehdy přišly na trh nové procesory Pentium 4 vybavené hyperthreadingem. Ten umožňoval dvěma threadům (klidně z různých procesů) sdílet nejen výkonné jednotky, ale taky L1 cache. Tam jsou nejžhavější informace o tom, co se právě teď dělo...
https://news.netcraft.com/archives/2005/05/20/researcher_attack_could_expose_ssl_certs_on_shared_servers.html
- http://www.daemonology.net/papers/htt.pdf
- https://cr.yp.to/antiforgery/cachetiming-20050414.pdf
Toto je pokračování z roku 2019:
https://eprint.iacr.org/2018/1060.pdf
Špehování cache se dá výrazně omezit velmi promyšleným způsobem implementace kryptografických funkcí tak, aby se z přistupovaných adres nedaly zpětně odvodit např. bity z hesla, privátního klíče, apod. Např. tím, že kód vždy přečte celé pole, i když potřebuje jen jeden údaj. Nebo výpočet pokračuje i přesto, pokud je už známo, že třeba klíč není platný. Nebo pokud před samotným výpočtem se načtou (prefetch) úplně všechna nutná data z paměti do cache (klíče, S-boxy, apod.), aby se kód vždy vykonával z cache a nedalo se manipulací předchozího stavu cache odhadovat na co se sahalo. Následně po konci výpočtu opět všechno (i opakovaně kvůli přemazání LRU historie) načíst do cache, aby se zahladily stopy. Možností je hodně, ale není to jednoduché. Např. je nutno zajistit, že to bude bezpečné na všech podporovaných platformách a druzích a verzích překladače. Taky je nutné ověřit, že překladač např. při optimalizaci nevyhodil 'zbytečné' čtení datových struktur, které se přímo v cyklu nepoužívají. Gcc je známé velmi agresivními optimalizacemi, které mohou způsobovat i bezpečnostní díry, např. kompletní odstranění volání memset() na vymazání hesla v řetězci na zásobníku na konci funkce. Vždyť ty nově zapisované hodnoty nikdo nečte (kromě útočníka), tak proč ztrácet čas s jejich zápisem...? Je toho hodně, takže nakonec nezbude než si přečíst přímo vygenerovaný assembler a ověřit si ručně, že tam je všechno co tam má být.
Nejen cache se dá šikovnou manipulací a měřením zneužít. V podstatě jakékoliv stavové informace v procesoru, které se během zpracování mění nebo dokonce na nich závisí vykonávání příštích instrukcí (cache, BTB/prediktor skoků, store buffers, různé fronty a spousta dalších implementačně závislých věcí).
10. 4. 2021, 03:54 editováno autorem komentáře