Meltdown og Spectre, forklart
Selv om jeg i disse dager er mest kjent for nettverk på applikasjonsnivå og distribuerte systemer, brukte jeg den første delen av karrieren min på operativsystemer og hypervisorer. Jeg opprettholder en dyp fascinasjon med lavt nivå detaljer om hvordan moderne prosessorer og systemer programvare arbeid. Når Den siste Meltdown og Spectre sårbarheter ble annonsert, jeg gravde inn i tilgjengelig informasjon og var ivrig etter å lære mer.
sårbarhetene er forbløffende; Jeg vil hevde at de er en av de viktigste funnene i datavitenskap de siste 10-20 årene. Tiltakene er også vanskelig å forstå og nøyaktig informasjon om dem er vanskelig å finne. Dette er ikke overraskende gitt deres kritiske natur. Å redusere sårbarhetene har krevd måneder med hemmelig arbeid av alle DE store CPU -, operativsystemet og skyleverandørene. Det faktum at problemene ble holdt under wraps for 6 måneder når bokstavelig talt hundrevis av mennesker var sannsynlig å jobbe med dem er fantastisk.Selv om Mye har blitt skrevet om Meltdown og Spectre siden kunngjøringen, har jeg ikke sett en god mid – level introduksjon til sårbarheter og begrensninger. I dette innlegget skal jeg forsøke å rette opp det ved å gi en mild introduksjon til maskinvare-og programvarebakgrunnen som kreves for å forstå sårbarhetene, en diskusjon av sårbarhetene selv, samt en diskusjon av dagens begrensninger.Viktig merknad: fordi Jeg ikke har jobbet direkte med begrensningene, og ikke jobber Hos Intel, Microsoft, Google, Amazon, Red Hat, etc. noen av detaljene som jeg kommer til å gi kan ikke være helt nøyaktig. Jeg har satt sammen dette innlegget basert på min kunnskap om hvordan disse systemene fungerer, offentlig tilgjengelig dokumentasjon,og patcher / diskusjon lagt TIL LKML og xen-devel. Jeg vil gjerne bli korrigert hvis noe av dette innlegget er unøyaktig, selv om jeg tviler på at det vil skje helst snart gitt hvor mye av dette emnet er fortsatt dekket AV NDA.
i denne delen vil jeg gi litt bakgrunn som kreves for å forstå sårbarhetene. Seksjonen glatter over en stor mengde detaljer og er rettet mot lesere med begrenset forståelse av maskinvare og systemprogramvare.
Virtuelt minne
Virtuelt minne er en teknikk som brukes av alle operativsystemer siden 1970-tallet. Det gir et lag av abstraksjon mellom minneadresseoppsettet som de fleste programvare ser og de fysiske enhetene som støtter det minnet (RAM, disker, etc.). På et høyt nivå tillater det at applikasjoner bruker mer minne enn maskinen faktisk har; dette gir en kraftig abstraksjon som gjør mange programmeringsoppgaver enklere.
figur 1 viser en forenklet datamaskin med 400 byte minne lagt ut i»sider»på 100 byte (ekte datamaskiner bruker krefter på to, typisk 4096). Datamaskinen har to prosesser, hver med 200 byte minne over 2 sider hver. Prosessene kan kjøre samme kode ved hjelp av faste adresser i 0-199 byte-området, men de støttes av diskret fysisk minne slik at de ikke påvirker hverandre. Selv om moderne operativsystemer og datamaskiner bruker virtuelt minne på en vesentlig mer komplisert måte enn det som presenteres i dette eksemplet, holder den grunnleggende premissen som presenteres ovenfor i alle tilfeller. Operativsystemer abstraherer adressene som programmet ser fra de fysiske ressursene som støtter dem.Å Oversette virtuelle til fysiske adresser Er en så vanlig operasjon i moderne datamaskiner at HVIS OPERATIVSYSTEMET måtte være involvert i alle tilfeller, ville datamaskinen være utrolig treg. Moderne CPU-maskinvare gir en enhet kalt En Translation Lookaside Buffer (TLB) som cacher nylig brukte mappings. Dette gjør At Cpuer kan utføre adresseoversettelse direkte i maskinvare mesteparten av tiden.
Figur 2 viser adresseoversettelsesflyten:
- et program henter en virtuell adresse.
- CPUEN forsøker å oversette DEN ved HJELP AV TLB. Hvis adressen er funnet, brukes oversettelsen.
- HVIS adressen ikke blir funnet, KONSULTERER CPU et sett med «sidetabeller» for å bestemme tilordningen. Sidetabeller er et sett med fysiske minnesider levert av operativsystemet på et sted maskinvaren kan finne dem (FOR EKSEMPEL CR3-registeret på x86-maskinvaren). Sidetabeller tilordner virtuelle adresser til fysiske adresser, og inneholder også metadata som tillatelser.
- hvis sidetabellen inneholder en tilordning, returneres den, bufres i TLB og brukes til oppslag. Hvis sidetabellen ikke inneholder en tilordning, heves EN» sidefeil » TIL OPERATIVSYSTEMET. En sidefeil er en spesiell type avbrudd som gjør AT OPERATIVSYSTEMET kan ta kontroll og bestemme hva som skal gjøres når det mangler eller er ugyldig kartlegging. FOR EKSEMPEL KAN OPERATIVSYSTEMET avslutte programmet. Det kan også tildele noe fysisk minne og kartlegge det i prosessen. Hvis en sidefeilbehandler fortsetter å utføre, vil DEN nye tilordningen bli brukt av TLB.
figur 3 viser en litt mer realistisk visning av hva virtuelt minne ser ut i en moderne datamaskin (pre-meltdown — mer om dette nedenfor). I dette oppsettet har vi følgende funksjoner:
- Kernel minne vises i rødt. Den finnes i fysisk adresseområde 0-99. Kjerneminne er spesielt minne som bare operativsystemet skal kunne få tilgang til. Brukerprogrammer skal ikke kunne få tilgang til det.
- brukerminne vises i grått.
- Ikke-Allokert fysisk minne vises i blått.
i dette eksemplet begynner vi å se noen av de nyttige funksjonene i virtuelt minne. Primært:
- brukerminne i hver prosess er i det virtuelle området 0-99, men støttet av forskjellig fysisk minne.Kjerneminnet i hver prosess er i det virtuelle området 100-199, men støttet av det samme fysiske minnet.
som jeg kort nevnte i forrige avsnitt, har hver side tilhørende tillatelsesbiter. Selv om kjerneminnet er kartlagt i hver brukerprosess, når prosessen kjører i brukermodus, kan den ikke få tilgang til kjerneminnet. Hvis en prosess forsøker å gjøre det, vil det utløse en sidefeil på hvilket tidspunkt operativsystemet vil avslutte det. Men når prosessen kjører i kjernemodus (for eksempel under et systemanrop), vil prosessoren tillate tilgangen.På dette punktet vil jeg merke at denne typen dual mapping (hver prosess som har kjernen kartlagt direkte) har vært standard praksis i operativsystemdesign i over tretti år av ytelsesårsaker (systemkall er svært vanlige, og det vil ta lang tid å omforme kjernen eller brukerplassen ved hver overgang).
CPU cache topologi
DEN neste bit av bakgrunnsinformasjon som kreves for å forstå sårbarheter ER CPU og cache topologi av moderne prosessorer. Figur 4 viser en generisk topologi som er felles for de fleste moderne Cpuer. Den består av følgende komponenter:
- den grunnleggende enheten for utførelse er «CPU thread» eller «hardware thread» eller » hyper-thread.»Hver CPU-tråd inneholder et sett med registre og muligheten til å utføre en strøm av maskinkode, omtrent som en programvaretråd.
- CPU-tråder finnes i EN » CPU-kjerne.»De fleste moderne Cpuer inneholder to tråder per kjerne.Moderne Cpuer inneholder generelt flere nivåer av cache-minne. Cachenivåene nærmere CPU-tråden er mindre, raskere og dyrere. Jo lenger UNNA CPU og nærmere hovedminnet cachen er, jo større, langsommere og billigere er det.Typisk MODERNE CPU-design bruker En L1 / L2-cache per kjerne. Dette betyr at hver CPU-tråd på kjernen bruker de samme cachene.
- Flere CPU-kjerner finnes i EN » CPU-pakke.»Moderne Cpuer kan inneholde opptil 30 kjerner (60 tråder) eller mer per pakke.
- ALLE CPU-kjernene i pakken deler vanligvis En l3-cache.
- CPU-pakker passer inn i » stikkontakter.»De fleste forbruker datamaskiner er single socket mens mange datasenter servere har flere stikkontakter.
Spekulativ utførelse
den siste delen av bakgrunnsinformasjonen som kreves for å forstå sårbarhetene, er en moderne CPU-teknikk kjent som » spekulativ utførelse.»Figur 5 viser et generisk diagram over utførelsesmotoren inne i en moderne CPU . den primære takeaway er at moderne Cpuer er utrolig kompliserte og ikke bare utfører maskininstruksjoner i rekkefølge. HVER CPU-tråd har en komplisert rørledningsmotor som er i stand til å utføre instruksjoner ute av drift. Årsaken til dette har å gjøre med caching. Som jeg diskuterte i forrige avsnitt, bruker HVER CPU flere nivåer av caching. Hver cache miss legger en betydelig mengde forsinkelse tid til programutførelse. For å redusere dette, er prosessorer i stand til å utføre fremover og ute av drift mens de venter på minnebelastninger. Dette kalles spekulativ utførelse. Følgende kodebit demonstrerer dette.
if (x < array1_size) {
y = array2 * 256];
}
i forrige tekstutdrag, tenk deg at array1_size
ikke er tilgjengelig i cache, men adressen til array1
er. CPUEN kan gjette (spekulere) at x
er mindre enn array1_size
og fortsett og utfør beregningene i if-setningen. Når array1_size
leses fra minnet, KAN CPUEN avgjøre om den gjettet riktig. Hvis det gjorde det, kan det fortsette å ha spart en haug med tid. Hvis det ikke gjorde det, kan det kaste bort spekulative beregninger og starte på nytt. Dette er ikke verre enn om det hadde ventet i utgangspunktet.
en annen type spekulativ utførelse er kjent som indirekte grenprediksjon. Dette er ekstremt vanlig i moderne programmer på grunn av virtuell forsendelse.
class Base {
public:
virtual void Foo() = 0;
};class Derived : public Base {
public:
void Foo() override { … }
};Base* obj = new Derived;
obj->Foo();
(kilden til den forrige kodebiten er dette innlegget)
måten den forrige kodebiten er implementert i maskinkode, er å laste «v-table» eller «virtual dispatch table» fra minneplasseringen som obj
peker på og deretter kaller det. Fordi denne operasjonen er så vanlig, har moderne Cpuer ulike interne cacher og vil ofte gjette (spekulere) hvor den indirekte grenen vil gå og fortsette utførelsen på det tidspunktet. Igjen, hvis CPU gjetter riktig, kan den fortsette å ha lagret en masse tid. Hvis det ikke gjorde det, kan det kaste bort spekulative beregninger og starte på nytt.
Meltdown sårbarhet
Etter å ha dekket all bakgrunnsinformasjon, kan vi dykke inn i sårbarhetene.
Rogue data cache load
det første sikkerhetsproblemet, Kjent som Meltdown, er overraskende enkelt å forklare og nesten trivielt å utnytte. Utnyttelseskoden ser omtrent ut som følgende:
1. uint8_t* probe_array = new uint8_t;
2. // ... Make sure probe_array is not cached
3. uint8_t kernel_memory = *(uint8_t*)(kernel_address);
4. uint64_t final_kernel_memory = kernel_memory * 4096;
5. uint8_t dummy = probe_array;
6. // ... catch page fault
7. // ... determine which of 256 slots in probe_array is cached
la oss ta hvert trinn over, beskrive hva det gjør, og hvordan det fører til å kunne lese minnet til hele datamaskinen fra et brukerprogram.
- i første linje tildeles en «probe array». Dette er minne i vår prosess som brukes som en sidekanal for å hente data fra kjernen. Hvordan dette gjøres vil bli tydelig snart.
- etter tildelingen sørger angriperen for at ingen av minnet i sondearrayen er bufret. Det finnes ulike måter å oppnå dette på, den enkleste som inkluderer CPU-spesifikke instruksjoner for å fjerne en minneplassering fra cache.
- angriperen fortsetter deretter å lese en byte fra kjernens adresseområde. Husk fra vår tidligere diskusjon om virtuelt minne og sidetabeller at alle moderne kjerner vanligvis kartlegger hele kjernens virtuelle adresserom i brukerprosessen. Operativsystemer er avhengige av at hver sidetabelloppføring har tillatelsesinnstillinger, og at brukermodusprogrammer ikke har tilgang til kjerneminnet. Enhver slik tilgang vil resultere i en sidefeil. Det er faktisk det som til slutt vil skje i trinn 3.
- men moderne prosessorer utfører også spekulativ utførelse og vil utføre før feilinstruksjonen. Dermed kan trinn 3-5 utføre I CPU-rørledningen før feilen heves. I dette trinnet multipliseres byten til kjerneminnet (som varierer fra 0-255) med sidestørrelsen til systemet, som vanligvis er 4096.
- i dette trinnet brukes den multipliserte byten av kjerneminnet til å lese fra sondearrayen til en dummy-verdi. Multiplikasjonen av byten med 4096 er å unngå EN CPU-funksjon kalt «prefetcher» fra å lese mer data enn vi vil ha inn i cachen.
- VED dette trinnet har CPUEN innsett sin feil og rullet tilbake til trinn 3. Resultatene av de spekulerte instruksjonene er imidlertid fortsatt synlige i cache. Angriperen bruker operativsystemfunksjonalitet til å fange feilinstruksjonen og fortsette kjøring (f.eks. håndtering AV SIGFAULT).
- i trinn 7 går angriperen gjennom og ser hvor lang tid det tar å lese hver av de 256 mulige byte i sondearrayen som kunne ha blitt indeksert av kjerneminnet. CPUEN vil ha lastet en av stedene i cache, og denne plasseringen vil lastes vesentlig raskere enn alle de andre stedene (som må leses fra hovedminnet). Denne plasseringen er verdien av byten i kjerneminnet.Ved hjelp av teknikken ovenfor, og det faktum at det er vanlig praksis for moderne operativsystemer å kartlegge alt fysisk minne i kjernens virtuelle adresserom, kan en angriper lese hele datamaskinens fysiske minne.
Nå lurer du kanskje på: «du sa at sidetabeller har tillatelsesbiter. Hvordan kan det være at brukermoduskoden kunne spekulativt få tilgang til kjerneminnet?»Årsaken er at dette er En feil I Intel-prosessorer . Etter min mening er det ingen god grunn, ytelse eller på annen måte, for at dette skal være mulig. Husk at all tilgang til virtuelt minne må skje VIA TLB. Det er lett mulig under spekulativ kjøring å kontrollere at en bufret tilordning har tillatelser som er kompatible med gjeldende kjørerettighetsnivå. Intel-maskinvare gjør det ganske enkelt ikke. Andre prosessorleverandører utfører en tillatelseskontroll og blokkerer spekulativ utførelse. Således, så vidt Vi vet, Er Meltdown Et Intel – eneste sårbarhet.
Edit: det ser ut til at minst EN ARM-prosessor også er utsatt for Nedsmelting som angitt her og her.
Meltdown mitigations
Meltdown Er lett å forstå, trivielt å utnytte, og heldigvis har også en relativt grei begrensning (i hvert fall konseptuelt — kjerneutviklere er kanskje ikke enige om at det er greit å implementere).
Kernel page table isolation (KPTI)
Husk at i delen om virtuelt minne beskrev jeg at alle moderne operativsystemer bruker en teknikk der kjerneminnet er kartlagt i hver brukermodus prosess virtuelt minneadresseplass. Dette er for både ytelse og enkelhet grunner. Det betyr at når et program gjør et systemanrop, er kjernen klar til å brukes uten videre arbeid. Løsningen for Meltdown er å ikke lenger utføre denne doble kartleggingen.