Trije zlobni mušketirji
V računalništvu se redko zgodi, da kakšna odkrita ranljivost pesti vse modele naprav, ki so izšli v zadnjih dveh desetletjih. Še redkeje se zgodi, da spremeni razmišljanje, zgradbo in način, kako računalnike izdelujemo in kako delujejo. V začetku januarja razkrite ranljivosti v modernih procesorjih so povzročile prav to. Novi procesorji ne bodo nikoli več takšni, kot so bili. Stari procesorji pa bodo počasnejši.
Shema cevovoda. Moderni procesorji imajo daljše in kompleksnejše.
Računalniki so zelo neumne naprave, ki v resnici ne znajo početi drugega, kot premetavati ničle in enice. Uporabni so zato, ker to počno zelo hitro. Srce vsakega računalnika predstavlja centralni procesor (CPU), ki v današnjih modelih deluje s hitrostjo 2–4 GHz. Premislimo, kaj to pomeni v praksi – posamezen cikel traja manj kot nanosekundo. To ni hitro le za človeške razmere, to je hitro tudi za elektroniko. Procesor lahko podatke obdeluje tako hitro le, če jih ima na voljo. Nanosekundni pomnilnik bi bil nepredstavljivo drag, dostopni čas na klasičnem disku pa se meri v milisekundah (milijonkrat več torej). Elegantni rešitvi tega problema, ki se imenujeta špekulativno izvajanje ukazov in pomnilniška hierarhija, sta zakuhali največjo ranljivost procesorjev v zgodovini.
Ranljivi so vsi Intelovi procesorji po letu 1995 in moderni AMDjevi, ARMjevi in Nvdiini čipi.
Hierarhija pomnilnika
Ob procesorju so registri, ki so dostopni tako hitro kakor tiktaka procesor. So najmanjša enota v pomnilniški hierarhiji, saj običajno vsebujejo le vhodne in izhodne podatke trenutnih operacij. Nekoliko večji, a počasnejši je prvonivojski (L1) predpomnilnik. Obstajata še dva nivoja predpomnilnika (L2 in L3). Vsak naslednji je večji in cenejši, a počasnejši. Sledi glavni pomnilnik (RAM), najpočasnejši in največji pa je trajni prostor na disku. Čeprav se zdijo vsi dostopni časi s človeške perspektive hitri, se razlikujejo za več velikostnih razredov (od nanosekund do milisekund). Če bi to preračunali v človeške enote, bi bili podatki v registru to, kar vemo na pamet, za dostop do informacij v pomnilniku pa bi se morali peljati v uro oddaljeno knjižnico.
Procesorji so pametni in podatke, do katerih so nazadnje ali pogosto dostopali, shranjujejo v predpomnilnik. Tako skrajšajo odzivni čas pri vnovičnem dostopu, a po drugi strani to odpira nekaj možnosti za napade po stranskem kanalu (side channel attack). Tako imenujemo napade, ki izkoriščajo fizično izvedbo računalnika in ne kakšne programske ranljivosti. Če bi jim uspelo prisluškovati omrežnemu prometu z merjenjem elektromagnetnega valovanja okrog kabla UTP, bi bil to napad po stranskem kanalu.
Popravki upočasnijo procesorje do 20 odstotkov.
Špekulativno izvajanje
Procesorji so tako hitri, da včasih prehitijo kar sami sebe. Moderne procesorje sestavljajo cevovodi, kjer se v vsaki stopnji izvaja neki ukaz, koda pa se skoznje pomika kot skozi cevni reaktor. Ker različne operacije trajajo različno dolgo, procesorji ukazov ne izvajajo strogo v zaporedju, kot je zapisano v kodi. Medtem ko procesor čaka na kakšne podatke iz centralnega pomnilnika, lahko obdela vrsto drugih ukazov (out-of-order execution). Kadar v kodi naleti na razvejitev ali pogoj, za katerega rezultata še ne pozna, vezje predpostavi najverjetnejši izid (branch prediction) in začne izvajati ukaze. Če se kasneje, ko se pogoj dejansko preveri, izkaže, da je procesor uganil pravilno, smo prihranili veliko časa. Če se je zmotil, se izračuni zavržejo, rezultat pa je enak, kot če bi procesor preprosto čakal na rezultat pogoja. Temu se pravi špekulativno izvajanje (speculative execution) in izdatno pospeši izvajanje kode.
Pomnilniška hierarhija v modernih računalnikih je kompromis med hitrostjo in velikostjo (ceno).
Navidezni pomnilnik in pravice
V modernih operacijskih sistemih se uporabniški programi že dolgo časa ne pogovarjajo več s strojno opremo neposredno. Poenostavljeno povedano, se vsak program počuti, kot da ima ves računalnik in s tem pomnilnik na voljo le zase. Program vidi navidezne naslove, operacijski sistem (oziroma v normalnih razmerah TLB (translation lookaside buffer), ki je v predpomnilniku na procesorju) pa skrbi za pretvarjanje med dejanskimi fizičnimi naslovi, kjer so podatki shranjeni v pomnilniku, in navideznimi naslovi, ki jih kliče program. Prav tako skrbi, da ima program dostop le do dela fizičnega pomnilnika, ki pripada njemu.
Del pomnilnika, do katerega ima dostop le jedro (kernel memory), se prav tako preslika v navidezni pomnilnik, ki je na voljo programu. Toda dostop do njega ima le, kadar se izvaja z ustreznimi privilegiji, denimo pri sistemskih klicih. Privilegiji se preverjajo ob vsakem klicu. Isti pomnilnik jedra v fizičnem pomnilniku se lahko preslika v navidezni pomnilnik vsakega programa posebej.
Ranljivost Meltdown
In že smo pri ranljivostih, ki so bili v procesorjih odkriti v januarju. Lažje razumljiva, resnejša, a tudi lažje odpravljiva je ranljivost Meltdown, ki zadeva le Intelove procesorje in nekaj ARMjevih. Prizadeti so vsi Intelovi procesorji po letu 1995, razen Itaniumov in Atomov izpred leta 2013, ter ARM Cortex-A15, A57, A72 in A73.
Najprej pojasnimo s prispodobo. Predpostavimo, da gremo v službo z avtomobilom, če dežuje, drugače pa s kolesom. Procesor bi se tega opravila lotil takole. Najprej bi vprašal zunanji pomnilnik, kakšno je vreme. Ker mora na odgovor počakati in ker je zadnji teden deževalo, bi odprl garažo, pripeljal avto pred vhod in ga zagnal. Ko bi končno dobil odgovor, da sije sonce, bi avto zapeljal nazaj v garažo in jo zaprl. Toda ker bi bil motor topel, avto pa malce drugače poravnan, bi lahko ugotovili, da sije sonce zgolj s pregledom garaže, ne da bi stopili ven.
Štirikratno odkritje
Ranljivosti Spectre in Meltdown je skoraj hkrati odkrilo več raziskovalnih skupin. V drugi polovici leta 2017 so za prvo ali drugo neodvisno izvedeli v Google Project Zero (Jann Horn), v Cyberus Technology (Werner Hass in Thomas Prescher), na Tehniški univerzi Gradec (Daniel Gruss, Moritz Lipp, Stefan Mangard, Michael Schwarz) in Paul Kocher v sodelovanju z Univerzo Pensilvanija (Daniel Genkin), Rambusom (Mike Hamburg) in Univerzo v Adelajdi (Yuval Yarom).
KPTI ščiti pred ranljivostjo Meltdown. Shema: Matt Klein
Do neke mere je štirikratno odkritje ranljivosti naključje, po drugi strani pa so raziskovalci sledili istim namigom in delnim odkritjem, ki so obstajali že prej. Prvi je napad odkril Jann Horn iz Googla že junija in o njem obvestil Intel, Kocher se je v pogovoru s Hamburgom septembra domislil preveriti isto ranljivost, v Cyberusu in v Gradcu pa so ranljivosti odkrili neodvisno in oboji v razmiku nekaj dni decembra obvestili Intel.
To štirikratno neodvisno odkritje pa poraja neprijetno vprašanje. Verjetno je na svetu še kdo, ki ima sposobnosti in opremo za odkritje in zlorabo te ranljivosti. Ker je obstajala več kot dve desetletji, je čisto mogoče, da se je že dolgo izrabljala. To je še en argument, zakaj skrivno kopičenje ranljivosti in njihovo skrivanje, ki se ga gredo obveščevalne službe po svetu, negativno vpliva na varnost. Zelo malo verjetno je, da so edine, ki jih poznajo. S skrivanjem preprečijo izdelavo popravkov, zato lahko tako one kot sposobni nepridipravi v miru rovarijo po naših računalnikih.
V praksi Meltdown deluje takole. Naprej definiramo pomožno polje (probe_array). Napadalec želi dostopiti do pomnilniškega naslova A (kernel_address), ki pripada jedru, in prebrati njegovo vrednost. Procesor izvede ta ukaz, hkrati pa pošlje poizvedbo, ali ima program sploh pravice za dostop do tja (nima jih). Tu bi se morala zgodba končati, a se ne, ker Intelov procesor nadaljuje, preden dobi informacijo o pravicah. Nato koda želi prebrati naslov B v pomnilniku, do katerega proces ima dostop. Ključno je, da je koda napisana tako, da je naslov B odvisen od vrednosti na naslovu A (probe_array[final_kernel_memory]). Procesor to izvede in naslov B shrani v predpomnilnik. Šele sedaj dobi procesor informacijo, da dostop do A ni dovoljen, zato vse operacije razveljavi, program pa ne dobi nobene informacije. Toda vsebina iz naslova B je še vedno v predpomnilniku. Zdaj program prebere vseh 256 možnosti za naslov B (celotne polje probe_array), saj dostop do B ima, in preveri, kateri naslov se je prebral bistveno hitreje. Iz tega dobi informacijo, kaj je pisalo na naslovu A (kernel_address). Tako je mogoče prečesati celoten pomnilnik.
Srž problema je vrstni red izvajanja ukazov. Ker procesor ne preveri, ali ima program pravico dostopa do naslova, preden ga prebere in s tem vpliva na stanje predpomnilnika, je mogoča zloraba. Popravek se imenuje KPTI (kernel page table isolation) in pomnilnika jedra ne preslika več v isti navidezni pomnilnik kakor program, temveč v ločen prostor. Klici vanj so nekoliko počasnejši, zato popravki za Meltdown upočasnijo procesor.
Koda za Meltdown
1. uint8_t* probe_array = new uint8_t[256 * 4096];
2. uint8_t kernel_memory = *(uint8_t*)(kernel_address);
3. uint64_t final_kernel_memory = kernel_memory * 4096;
4. uint8_t dummy = probe_array[final_kernel_memory];
Ranljivost Spectre
Bistveno subtilnejša je ranljivost Spectre, ki je prisotna v procesorjih vseh večjih izdelovalcev. Medtem ko Meltdown nastane zaradi hrošča v Intelovi izvedbi procesorjev, za katerega ni pametnega razloga, je Spectre posledica logike in konstrukcije procesorjev, zaradi česar je zaščita bistveno zagonetnejša. Napad Spectre ima dve različici, ki sta si podobni. Z obema program, ki se izvaja v uporabniškem načinu, pretenta procesor za dostop do pomnilnika, ki pripada drugemu programu (in ne jedru).
Pri ranljivosti Spectre 1 (bounds check bypass) ne gre za eskalacijo privilegijev, temveč moramo procesor pretentati v napačno napoved, kateri ukaz naj začne izvajati. Recimo, da ima napadalec nadzor nad spremenljivko x, array1_size pa ni v predpomnilniku, medtem ko array1 je. Če procesor napačno predpostavi, da prvi pogoj v kodi velja, se bo izvedel nadaljnji ukaz, ki bo vplival na stanje predpomnilnika. Procesor bo kasneje pravilno ugotovil, da pogoj ni izpolnjen in da program nima dostopa, in bo zavrgel vse rezultate, a predpomnilnik bo čutil spremembo.
Pametne rešitve tega problema ni. Da je iz stanja predpomnilnika mogoče sklepati o tem, kateri pomnilniški naslovi so bili nazadnje uporabljeni, ni novo. Povsem novo pa je odkritje, kako to izkoristiti za dostop do pomnilnika v uporabniškem prostoru, do kamor program nima dostopa. Edina rešitev je statična analiza kode, odkrivanje odlomkov, ki vplivajo na izid špekulativnega izvajanja ukazov, in vstavitev ukazov za serializacijo (torej čakanje, da se izvedejo vsi ukazi do točke razvejitve). To vpliva na hitrost.
Koda za Spectre 1
if (x < array1_size) {
y = array2[array1[x] * 256];
}
Ranljivost Spectre 2 (branch target injection) je podobna, le da tu napadalec zastrupi procesorjevo enoto za predvidevanje razvejitev in ga s tem ukani v izvajanje ukazov, ki jih sicer ne bi nikoli predvidel. Tudi v tem primeru potem iz stanja predpomnilnika rekonstruira vsebina pomnilnika. Rešitev za Spectre 2 že obstaja, in sicer so ločene razvili Google, AMD in Intel, vključuje pa tudi nadgradnjo mikrokode procesorja. Izkoriščanje ranljivost Spectre je bistveno težje kakor Meltdown (tudi popolna razlaga bi bila precej daljša), a po drugi strani tudi ni enostavne zaščite zanj.
Odziv
O obstoju ranljivosti je bila širša javnost seznanjena 3. januarja. Google, ki je bil eden izmed odkriteljev ranljivosti, je načrtoval javno objavo za 9. januarja, a so špekulacije v internetu, ki jih je povzročil tudi nedokumentiran in nekomentiran popravek za Linuxovo jedro, izdelovalce prisilile v hitrejše razkritje. Sprva je kazalo, da je ranljivost omejena na Intelove procesorje in da bo AMD imel fantastično leto, a to ni res.
Popravki za Meltdown so na ravni operacijskih sistemov. Microsoft jih je izdal 3. januarja, a hitro ugotovil, da so nekateri protivirusni programi nezdružljivi z novo obravnavo pomnilnika jedra in povzročajo sesuvanje sistema. Zato je Microsoft začel popravek nameščati le v računalnike, ki imajo potrjeno združljiv (to se preverja prek ključa v registru operacijskega sistema) protivirusni program. Kdor takega protivirusnega programa nima, mora v registru sam omogočiti namestitev popravka. Kasneje se je izkazalo, da se računalniki s starejšimi AMDjevimi procesorji po namestitvi popravka ne zaženejo več. Microsoft je prekinil distribucijo popravka zanje in jo nadaljeval šele s popravljeno različico kasneje. Popravek za Linux je že v novi različici jedra. Apple je prav tako že posodobil svoje operacijske sisteme.
Zloraba ranljivosti ne pušča sprememb v pomnilniku ali na disku, zato jo je nemogoče zaznati.
Za Spectre je stanje težavnejše. Intel je izdal popravljeno mikrokodo za nekatere procesorje, a je ta pri več modelih (Broadwell, Haswell, Ivy Bridge, Sandy Bridge, Skylake, Kaby Lake) povzročala nepričakovane vnovične zagone, kar še rešujejo. Od leta 1995 je izšlo veliko procesorjev in Intel trdi, da je januarja zakrpal 95 odstotkov vseh mlajših od pet let. Kaj bo s starejšimi, bomo videli. AMD je prav tako napovedal popravke za nekatere procesorje Ryzen in EPYC za zaščito pred Spectre 2.
Ker so za napad Spectre 1 najbolj občutljivi brskalniki, so vsi večji izdelovalci že izdali popravljene različice, ki vsebuje varovalke proti napadu. Večina popravkov je zgolj najnujnejši obliž, zato v nadaljevanju pričakujemo nove popravke na vseh ravneh (mikrokoda, BIOS/UEFI, operacijski sistem, programska oprema). Namestite jih.
Posledice
Daljnosežna posledica bo večja ali manjša sprememba v arhitekturi procesorjev. Meltdown je posledica napačnega oblikovanja procesorja, Spectre pa je posledica pravilnega in želenega obnašanja procesorja. Zaradi cevovoda, zunajserijskega izvajanja ukazov in predvidevanja ob razvejitvah so današnji procesorji bistveno hitrejši, kot bi bili sicer.
Kratkoročne posledice so upočasnitve zaradi sprememb, ki so jih prinesli popravki. Prve katastrofične napovedi o 50-odstotni upočasnitvi se k sreči niso udejanjile, a razlike so merljive in v številnih primerih občutne.
Največ preglavic imajo ponudniki oblačnih storitev, podatkovnih centrov in gostovanja. Ti nimajo druge izbire kakor namestiti popravke, saj morajo zagotoviti, da si uporabniki istih fizičnih strojev, vsak v svojem navideznem računalniku, ne gledajo v pomnilnik. Ti sistemi so oblikovani glede na pričakovane obremenitve in maksimalno učinkovitost, zato se vsako konsistentno zmanjšanje zmogljivosti pozna.
Za domače uporabnike je stanje boljše, če niso morda staknili popravka, ki jim je onesposobil računalnik. Windows 10 na novejših procesorjih je do 10 odstotkov počasnejši, na starejših (Haswell) pa tudi do 20 odstotkov. Na Windows 7 in 8 se razlike poznajo še bolj. Zlasti občutne upočasnitve zaznajo sintetični testi ter pri uporabi v okoljih, kjer je veliko dostopov I/O oz. dostopov v jedro (omrežje, diski). Poleg nižje hitrosti opazimo tudi višjo obremenitev procesorja.
Nadaljnje branje
• Horn, J. Reading privileged memory with a side-channel. Google Projet Zero, 2018.
• Lipp, M. et al. Meltdown. ArXiv, 2018.
• Kocher, P. et al. Spectre Attacks: Exploiting Speculative Execution. ArXiv, 2018.
• Gruss, D. et al. KASLR is Dead: Long Live KASLR. International Symposium on Engineering Secure Software and Systems, 2017.