Meltdown i Spectre, wyjaśnione

chociaż obecnie jestem znany głównie z sieci na poziomie aplikacji i systemów rozproszonych, pierwszą część kariery spędziłem pracując nad systemami operacyjnymi i hipernadzorcami. Jestem głęboko zafascynowany niskopoziomowymi szczegółami działania nowoczesnych procesorów i oprogramowania systemowego. Kiedy ogłoszono niedawne luki w zabezpieczeniach Meltdown i Spectre, zagłębiłem się w dostępne informacje i chciałem dowiedzieć się więcej.

luki są zdumiewające; Uważam, że są one jednym z najważniejszych odkryć w informatyce w ciągu ostatnich 10-20 lat. Łagodzenia są również trudne do zrozumienia i dokładne informacje na ich temat jest trudne do znalezienia. Nie jest to zaskakujące, biorąc pod uwagę ich krytyczny charakter. Łagodzenie luk w zabezpieczeniach wymagało miesięcy tajnej pracy wszystkich głównych dostawców procesorów, systemów operacyjnych i chmury. Fakt, że problemy były trzymane w tajemnicy przez 6 miesięcy, kiedy dosłownie setki ludzi prawdopodobnie nad nimi pracowało, jest niesamowity.

chociaż dużo napisano o Meltdown i Spectre od czasu ich ogłoszenia, nie widziałem dobrego wprowadzenia średniego poziomu do luk i złagodzeń. W tym poście postaram się to poprawić poprzez łagodne wprowadzenie do środowiska sprzętowego i oprogramowania wymaganego do zrozumienia luk w zabezpieczeniach, omówienie samych luk, a także omówienie obecnych ograniczeń.

Ważna uwaga: Ponieważ nie pracowałem bezpośrednio nad poprawkami i nie pracuję w Intel, Microsoft, Google,Amazon, Red Hat itp. niektóre szczegóły, które zamierzam podać, mogą nie być całkowicie dokładne. Poskładałem ten post w oparciu o moją wiedzę o tym, jak działają te systemy, publicznie dostępną dokumentację i łatki/dyskusję zamieszczoną w LKML i xen-devel. Chciałbym zostać poprawiony, jeśli którykolwiek z tego postu jest niedokładny, choć wątpię, że stanie się to w najbliższym czasie, biorąc pod uwagę, jak wiele z tego tematu jest nadal poruszane przez NDA.

w tej sekcji przedstawię tło wymagane do zrozumienia luk w zabezpieczeniach. Sekcja ta zawiera dużą ilość szczegółów i jest skierowana do czytelników z ograniczoną wiedzą na temat sprzętu komputerowego i oprogramowania systemowego.

Pamięć wirtualna

Pamięć wirtualna jest techniką stosowaną przez wszystkie systemy operacyjne od lat 70. zapewnia warstwę abstrakcji między układem adresów pamięci, który widzi większość programów, a fizycznymi urządzeniami wspierającymi tę pamięć (RAM, dyski itp.). Na wysokim poziomie pozwala aplikacjom na wykorzystanie większej ilości pamięci niż maszyna w rzeczywistości; zapewnia to potężną abstrakcję, która ułatwia wiele zadań programistycznych.

Rysunek 1: Pamięć wirtualna

rysunek 1 przedstawia uproszczony komputer z 400 bajtami pamięci ułożonymi w „stronach” po 100 bajtów (prawdziwe komputery używają potęg dwóch, zazwyczaj 4096). Komputer ma dwa procesy, każdy z 200 bajtami pamięci na 2 stronach każdy. Procesy mogą uruchamiać ten sam kod przy użyciu stałych adresów z zakresu bajtów 0-199, jednak są one wspierane przez dyskretną pamięć fizyczną tak, że nie wpływają na siebie nawzajem. Chociaż nowoczesne systemy operacyjne i komputery wykorzystują pamięć wirtualną w znacznie bardziej skomplikowany sposób niż to, co przedstawiono w tym przykładzie, podstawowe założenie przedstawione powyżej jest we wszystkich przypadkach. Systemy operacyjne abstrakują adresy, które aplikacja widzi z zasobów fizycznych, które je wspierają.

tłumaczenie adresów wirtualnych na fizyczne jest tak powszechną operacją w nowoczesnych komputerach, że gdyby system operacyjny miał być zaangażowany we wszystkich przypadkach, komputer byłby niesamowicie wolny. Nowoczesny sprzęt CPU zapewnia urządzenie o nazwie Translation Lookaside Buffer (TLB), które buforuje ostatnio używane mapowania. Pozwala to procesorom na wykonywanie tłumaczenia adresów bezpośrednio w sprzęcie przez większość czasu.

Rysunek 2: Tłumaczenie pamięci wirtualnej

Rysunek 2 pokazuje przepływ tłumaczenia adresów:

  1. program pobiera adres wirtualny.
  2. procesor próbuje go przetłumaczyć za pomocą TLB. Jeśli adres zostanie znaleziony, używane jest tłumaczenie.
  3. Jeśli adres nie zostanie znaleziony, procesor sprawdza zestaw „tabel stron”w celu określenia mapowania. Tabele stron są zbiorem stron pamięci fizycznej dostarczanych przez system operacyjny w miejscu, w którym sprzęt może je znaleźć (na przykład rejestr CR3 na sprzęcie x86). Tabele stron mapują adresy wirtualne na adresy fizyczne, a także zawierają metadane, takie jak uprawnienia.
  4. jeśli tabela stron zawiera mapowanie, jest ona zwracana, buforowana w TLB i używana do wyszukiwania. Jeśli tabela stron nie zawiera mapowania, „Błąd strony” jest podnoszony do systemu operacyjnego. Błąd strony jest specjalnym rodzajem przerwania, które pozwala systemowi operacyjnemu przejąć kontrolę i określić, co zrobić, gdy brakuje lub jest nieprawidłowe mapowanie. Na przykład System Operacyjny może zakończyć działanie programu. Może również przydzielić trochę pamięci fizycznej i zmapować ją do procesu. Jeśli Obsługa błędów strony kontynuuje wykonywanie, nowe mapowanie będzie używane przez TLB.

Rysunek 3: mapowania pamięci wirtualnej użytkownika/jądra

Rysunek 3 pokazuje nieco bardziej realistyczny obraz tego, jak wygląda pamięć wirtualna w nowoczesnym komputerze (Pre-Meltdown — więcej na ten temat poniżej). W tej konfiguracji mamy następujące funkcje:

  • pamięć jądra jest pokazana na Czerwono. Jest ona zawarta w fizycznym zakresie adresów 0-99. Pamięć jądra jest specjalną pamięcią, do której dostęp powinien mieć tylko system operacyjny. Programy użytkownika nie powinny mieć do niego dostępu.
  • pamięć użytkownika jest wyświetlana na szaro.
  • Nieprzydzielona pamięć fizyczna jest pokazana na niebiesko.

w tym przykładzie zaczynamy widzieć kilka przydatnych funkcji pamięci wirtualnej. Przede wszystkim:

  • pamięć użytkownika w każdym procesie mieści się w zakresie wirtualnym 0-99, ale jest wspierana przez inną pamięć fizyczną.
  • pamięć jądra w każdym procesie znajduje się w wirtualnym zakresie 100-199, ale jest wspierana przez tę samą pamięć fizyczną.

jak krótko wspomniałem w poprzedniej sekcji, każda strona ma powiązane bity uprawnień. Mimo że pamięć jądra jest mapowana do każdego procesu użytkownika, gdy proces działa w trybie użytkownika, nie może uzyskać dostępu do pamięci jądra. Jeśli proces spróbuje to zrobić, spowoduje to błąd strony, w którym system operacyjny go zakończy. Jednakże, gdy proces jest uruchomiony w trybie jądra (na przykład podczas wywołania systemowego), procesor zezwoli na dostęp.

w tym miejscu zauważę, że ten rodzaj podwójnego mapowania (każdy proces mający bezpośrednio odwzorowane jądro) jest standardową praktyką w projektowaniu systemu operacyjnego od ponad trzydziestu lat ze względu na wydajność (wywołania systemowe są bardzo powszechne i potrzeba dużo czasu, aby zremapować jądro lub przestrzeń użytkownika przy każdym przejściu).

topologia pamięci podręcznej procesora

rysunek 4: topologia wątku PROCESORA, rdzenia, pakietu i pamięci podręcznej.

kolejną informacją niezbędną do zrozumienia luk w zabezpieczeniach jest topologia procesora i pamięci podręcznej nowoczesnych procesorów. Rysunek 4 przedstawia ogólną topologię, która jest wspólna dla większości nowoczesnych procesorów. Składa się z następujących komponentów:

  • podstawową jednostką wykonania jest „wątek CPU” lub „wątek sprzętowy” lub „hyper-thread.”Każdy wątek CPU zawiera zestaw rejestrów i możliwość wykonywania strumienia kodu maszynowego, podobnie jak wątek oprogramowania.
  • wątki procesora są zawarte w „rdzeniu CPU.”Większość nowoczesnych procesorów zawiera po dwa wątki na rdzeń.
  • nowoczesne procesory zazwyczaj zawierają wiele poziomów pamięci podręcznej. Poziomy pamięci podręcznej bliżej wątku PROCESORA są mniejsze, szybsze i droższe. Im dalej od procesora i bliżej pamięci głównej pamięć podręczna jest większa, wolniejsza i tańsza.
  • typowa nowoczesna konstrukcja procesora wykorzystuje pamięć podręczną L1 / L2 na rdzeń. Oznacza to, że każdy wątek procesora na rdzeniu korzysta z tych samych pamięci podręcznych.
  • wiele rdzeni procesora znajduje się w pakiecie „CPU.”Nowoczesne procesory mogą zawierać ponad 30 rdzeni (60 wątków) lub więcej na pakiet.
  • wszystkie rdzenie procesora w pakiecie zazwyczaj współdzielą pamięć podręczną L3.
  • Pakiety CPU pasują do „gniazd.”Większość komputerów konsumenckich to pojedyncze gniazdo, podczas gdy wiele serwerów centrów danych ma wiele gniazd.

rysunek 5: Modern CPU execution engine (Source: Google images)

ostatnim elementem informacji wymaganych do zrozumienia luk w zabezpieczeniach jest nowoczesna technika CPU znana jako „speculative execution.”Rysunek 5 przedstawia ogólny schemat silnika wykonawczego wewnątrz nowoczesnego procesora.

podstawowym jest to, że nowoczesne procesory są niezwykle skomplikowane i nie wykonują po prostu instrukcji maszynowych w kolejności. Każdy wątek procesora ma skomplikowany silnik pipeliningowy, który jest w stanie wykonywać instrukcje poza kolejnością. Powodem tego jest buforowanie. Jak omówiłem w poprzedniej sekcji, każdy procesor korzysta z wielu poziomów buforowania. Każde pominięcie pamięci podręcznej dodaje znaczną ilość czasu opóźnienia do wykonania programu. Aby to złagodzić, procesory są w stanie wykonywać z wyprzedzeniem i poza kolejnością, czekając na obciążenia pamięci. Jest to znane jako egzekucja spekulacyjna. Ilustruje to poniższy fragment kodu.

if (x < array1_size) {
y = array2 * 256];
}

w poprzednim fragmencie wyobraź sobie, żearray1_size nie jest dostępny w pamięci podręcznej, ale adresarray1 jest. Procesor może zgadywać (spekulować), że x jest mniejszy niż array1_size I wykonać obliczenia wewnątrz instrukcji if. Gdy array1_size zostanie odczytany z pamięci, procesor może określić, czy odgadł poprawnie. Jeśli tak, może nadal zaoszczędzić sporo czasu. Jeśli nie, może odrzucić spekulacyjne obliczenia i zacząć od nowa. To nie jest gorsze niż gdyby czekało w pierwszej kolejności.

inny rodzaj realizacji spekulacyjnej jest znany jako przewidywanie gałęzi pośrednich. Jest to niezwykle powszechne w nowoczesnych programach ze względu na wirtualną wysyłkę.

class Base {
public:
virtual void Foo() = 0;
};class Derived : public Base {
public:
void Foo() override { … }
};Base* obj = new Derived;
obj->Foo();

(źródłem poprzedniego fragmentu jest ten post)

sposób, w jaki poprzedni fragment jest zaimplementowany w kodzie maszynowym, polega na załadowaniu „v-table” lub „virtual dispatch table” z lokalizacji pamięci, do której obj wskazuje, a następnie wywołuje go. Ponieważ ta operacja jest tak powszechna, nowoczesne procesory mają różne wewnętrzne pamięci podręczne i często zgadują (spekulują), gdzie pójdzie gałąź pośrednia i kontynuują wykonywanie w tym momencie. Ponownie, Jeśli procesor zgadnie poprawnie, może nadal zaoszczędzić kilka czasu. Jeśli nie, może odrzucić spekulacyjne obliczenia i zacząć od nowa.

luka w zabezpieczeniach Meltdown

po omówieniu wszystkich podstawowych informacji, możemy zanurkować w luki.

nieuczciwe obciążenie pamięci podręcznej danych

pierwsza Luka, znana jako Meltdown, jest zaskakująco prosta do wyjaśnienia i prawie trywialna do wykorzystania. Kod exploita wygląda mniej więcej następująco:

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

wykonajmy każdy krok powyżej, opiszmy, co robi i jak prowadzi do odczytu pamięci całego komputera z programu użytkownika.

  1. w pierwszym wierszu przydzielana jest „tablica sond”. Jest to pamięć w naszym procesie, która jest używana jako boczny kanał do pobierania danych z jądra. Jak to się robi, wkrótce stanie się jasne.
  2. Po alokacji atakujący upewnia się, że żadna pamięć w tablicy sondy nie jest buforowana. Istnieje wiele sposobów osiągnięcia tego celu, z których najprostszy obejmuje instrukcje specyficzne dla PROCESORA, aby wyczyścić lokalizację pamięci z pamięci podręcznej.
  3. atakujący zaczyna odczytywać bajt z przestrzeni adresowej jądra. Pamiętaj z naszej poprzedniej dyskusji na temat pamięci wirtualnej i tabel stron, że wszystkie nowoczesne jądra zazwyczaj mapują całą wirtualną przestrzeń adresową jądra w proces użytkownika. Systemy operacyjne polegają na tym, że każdy wpis tabeli stron ma ustawienia uprawnień, a Programy trybu użytkownika nie mają dostępu do pamięci jądra. Każdy taki dostęp spowoduje błąd strony. To jest rzeczywiście to, co w końcu stanie się w kroku 3.
  4. jednak nowoczesne procesory również wykonują spekulacyjne wykonanie i wykonają je przed instrukcją błędu. Tak więc kroki 3-5 mogą zostać wykonane w potoku procesora, zanim usterka zostanie podniesiona. W tym kroku bajt pamięci jądra (który waha się od 0 do 255) jest mnożony przez rozmiar strony systemu, który zazwyczaj wynosi 4096.
  5. w tym kroku pomnożony bajt pamięci jądra jest następnie używany do odczytu z tablicy sond do wartości atrapy. Mnożenie bajtu przez 4096 ma na celu uniknięcie funkcji procesora o nazwie „prefetcher” od odczytu większej ilości danych niż chcemy do pamięci podręcznej.
  6. w tym kroku procesor zdał sobie sprawę ze swojego błędu i wrócił do kroku 3. Jednak wyniki spekulowanych instrukcji są nadal widoczne w pamięci podręcznej. Atakujący używa funkcji systemu operacyjnego, aby złapać instrukcję powodującą błąd i kontynuować wykonywanie (np. obsługę SIGFAULT).
  7. w kroku 7 atakujący dokonuje iteracji i widzi, ile czasu zajmuje odczytanie każdego z 256 możliwych bajtów w tablicy sond, które mogły być indeksowane przez pamięć jądra. Procesor załaduje jedną z lokalizacji do pamięci podręcznej i ta lokalizacja ładuje się znacznie szybciej niż wszystkie inne lokalizacje (które muszą być odczytane z pamięci głównej). Ta lokalizacja jest wartością bajtu w pamięci jądra.

wykorzystując powyższą technikę oraz fakt, że w nowoczesnych systemach operacyjnych mapowanie całej pamięci fizycznej do wirtualnej przestrzeni adresowej jądra jest standardową praktyką, atakujący może odczytać całą pamięć fizyczną komputera.

teraz możesz się zastanawiać: „powiedziałeś, że tabele stron mają bity uprawnień. Jak to możliwe, że kod trybu użytkownika był w stanie spekulatywnie uzyskać dostęp do pamięci jądra?”Powodem jest to, że jest to błąd w procesorach Intela. Moim zdaniem, nie ma dobrego powodu, wydajność lub w inny sposób, aby było to możliwe. Przypomnijmy, że cały dostęp do pamięci wirtualnej musi odbywać się przez TLB. Podczas wykonywania spekulacyjnego można łatwo sprawdzić, czy mapowanie buforowane ma uprawnienia zgodne z bieżącym poziomem uprawnień. Sprzęt Intela po prostu tego nie robi. Inni dostawcy procesorów wykonują sprawdzanie uprawnień i blokują spekulacyjne wykonanie. Tak więc, o ile wiemy, Meltdown jest luką tylko dla Intela.

Edit: wydaje się, że co najmniej jeden procesor ARM jest również podatny na stopienie, jak wskazano tutaj i tutaj.

łagodzenia Meltdown

Meltdown jest łatwe do zrozumienia, banalne do wykorzystania i na szczęście ma również stosunkowo proste łagodzenie (przynajmniej koncepcyjnie — Programiści jądra mogą nie zgodzić się, że jest łatwe do wdrożenia).

Kernel page table isolation (KPTI)

Przypomnijmy, że w sekcji poświęconej pamięci wirtualnej opisałem, że wszystkie nowoczesne systemy operacyjne wykorzystują technikę, w której pamięć jądra jest mapowana do każdej przestrzeni adresowej pamięci wirtualnej w trybie użytkownika. Jest to zarówno ze względu na wydajność, jak i prostotę. Oznacza to, że kiedy program wykonuje wywołanie systemowe, jądro jest gotowe do użycia bez dalszej pracy. Poprawką dla Meltdown jest zaprzestanie wykonywania tego podwójnego mapowania.

Rysunek 6: strona Jądra izolacja tabeli

rysunek 6 przedstawia technikę zwaną izolacją tabeli stron jądra (kpti). Zasadniczo sprowadza się to do nie mapowania pamięci jądra w program, gdy jest uruchomiony w przestrzeni użytkownika. Jeśli nie ma mapowania, wykonanie spekulacyjne nie jest już możliwe i natychmiast zawiedzie.

oprócz tego, że wirtualny Menedżer pamięci (VMM) systemu operacyjnego jest bardziej skomplikowany, bez pomocy sprzętowej technika ta znacznie spowalnia również obciążenia, które powodują dużą liczbę przejść trybu użytkownika do trybu jądra, ze względu na fakt, że tabele stron muszą być modyfikowane przy każdym przejściu i TLB musi być opróżniany (biorąc pod uwagę, że TLB może trzymać się starych mapowań).

nowsze procesory x86 mają funkcję znaną jako asid (Address space ID) lub PCID (process context ID), która może być użyta do uczynienia tego zadania znacznie tańszym (ARM i inne mikroarchitektury mają tę funkcję od lat). PCID pozwala powiązać identyfikator z wpisem TLB, a następnie opróżnić tylko wpisy TLB z tym identyfikatorem. Korzystanie z PCID sprawia, że KPTI jest tańsze, ale nadal nie jest darmowe.

Podsumowując, Meltdown to niezwykle poważna i łatwa do wykorzystania luka w zabezpieczeniach. Na szczęście ma to stosunkowo proste ograniczenie, które zostało już wdrożone przez wszystkich głównych dostawców systemów operacyjnych, z zastrzeżeniem, że niektóre obciążenia będą działać wolniej, dopóki przyszły sprzęt nie zostanie wyraźnie zaprojektowany do opisanego rozdzielania przestrzeni adresowej.

Spectre

Spectre posiada pewne właściwości Meltdown i składa się z dwóch wariantów. W przeciwieństwie do Meltdown, Spectre jest znacznie trudniejsze do wykorzystania, ale dotyczy prawie wszystkich nowoczesnych procesorów wyprodukowanych w ciągu ostatnich dwudziestu lat. Zasadniczo Spectre jest atakiem przeciwko nowoczesnemu procesorowi i systemowi operacyjnemu w porównaniu z konkretną luką w zabezpieczeniach.

bounds check bypass (wariant widmowy 1)

pierwszy wariant widmowy jest znany jako „bounds check bypass.”Jest to pokazane w poniższym fragmencie kodu (który jest tym samym fragmentem kodu, którego użyłem do wprowadzenia wykonania spekulacyjnego powyżej).

if (x < array1_size) {
y = array2 * 256];
}

w poprzednim przykładzie przyjmij następującą sekwencję zdarzeń:

  1. atakujący kontrolujex.
  2. array1_size nie jest buforowany.
  3. array1 jest buforowany.
  4. procesor domyśla się, żex jest mniejszy niżarray1_size. (Procesory wykorzystują różne zastrzeżone algorytmy i heurystyki, aby określić, czy spekulować, dlatego szczegóły ataku dla widma różnią się między producentami procesorów i modelami.)
  5. procesor wykonuje treść instrukcji if podczas oczekiwania na załadowaniearray1_size, wpływając na pamięć podręczną w podobny sposób jak Meltdown.
  6. atakujący może następnie określić rzeczywistą wartośćarray1 za pomocą jednej z różnych metod. (Więcej szczegółów na temat ataków Cache inference można znaleźć w artykule z badań.)

Spectre jest znacznie trudniejsze do wykorzystania niż Meltdown, ponieważ ta luka nie zależy od eskalacji uprawnień. Atakujący musi przekonać jądro do uruchomienia kodu i nieprawidłowo spekulować. Zazwyczaj atakujący musi zatruć silnik spekulacji i oszukać go do zgadywania nieprawidłowo. To powiedziawszy, naukowcy wykazali kilka exploitów proof-of-concept.

chcę powtórzyć, jak naprawdę niesamowite jest odkrycie tego exploita. Osobiście nie uważam tego za wadę projektową procesora, taką jak Meltdown per se. Uważam to za fundamentalne odkrycie na temat tego, jak nowoczesny sprzęt i oprogramowanie współpracują ze sobą. Fakt, że pamięci podręczne procesora mogą być używane pośrednio do poznawania wzorców dostępu, jest znany od pewnego czasu. Fakt, że pamięci podręczne procesora mogą być używane jako boczny kanał do zrzutu pamięci komputera, jest zdumiewający, zarówno koncepcyjnie, jak iw swoich implikacjach.

Branch target injection (Spectre variant 2)

Przypomnijmy, że pośrednie rozgałęzienia są bardzo powszechne we współczesnych programach. Wariant 2 Spectre wykorzystuje pośrednią predykcję gałęzi, aby zatruć procesor do spekulatywnego wykonania w lokalizacji pamięci, której w przeciwnym razie nigdy by nie wykonał. Jeśli wykonanie tych instrukcji może pozostawić stan w pamięci podręcznej, który można wykryć za pomocą ataków Cache inference, atakujący może zrzucić całą pamięć jądra. Podobnie jak widmo wariantu 1, widmo wariantu 2 jest znacznie trudniejsze do wykorzystania niż Meltdown, jednak naukowcy zademonstrowali działające exploity proof-of-concept wariantu 2.

łagodzenie widm

łagodzenie widm jest znacznie ciekawsze niż łagodzenie Meltdown. W rzeczywistości academic Spectre paper pisze, że obecnie nie są znane żadne złagodzenia. Wydaje się, że za kulisami i równolegle do prac akademickich, Intel (i prawdopodobnie inni producenci procesorów) oraz najwięksi dostawcy systemów operacyjnych i chmur pracowali wściekle od miesięcy nad opracowaniem rozwiązań łagodzących. W tej sekcji omówię różne złagodzenia, które zostały opracowane i wdrożone. Jest to sekcja, na której jestem najbardziej zamglony, ponieważ niezwykle trudno jest uzyskać dokładne informacje, więc zbieram rzeczy z różnych źródeł.

analiza statyczna i ogrodzenie (wariant 1 łagodzenie)

jedynym znanym wariantem 1 (bounds check bypass) łagodzenie jest statyczna analiza kodu w celu określenia sekwencji kodu, które mogą być kontrolowane przez atakującego w celu zakłócenia spekulacji. Sekwencje kodów podatnych na ataki mogą mieć włożoną instrukcję serializującą ,taką jaklfence, która zatrzymuje wykonywanie spekulacyjne, dopóki nie zostaną wykonane wszystkie instrukcje aż do ogrodzenia. Należy zachować ostrożność podczas wstawiania instrukcji ogrodzenia, ponieważ zbyt wiele może mieć poważny wpływ na wydajność.

Retpoline (Variant 2 mitigation)

pierwszy wariant widmowy 2 (branch target injection) mitigation został opracowany przez Google i jest znany jako „retpoline.”Nie jest dla mnie jasne, czy został on opracowany w izolacji przez Google, czy przez Google we współpracy z Intelem. Spekulowałbym, że został eksperymentalnie opracowany przez Google, a następnie zweryfikowany przez inżynierów sprzętowych Intela, ale nie jestem pewien. Szczegóły dotyczące podejścia „retpoline” można znaleźć w artykule Google na ten temat. Podsumuję je tutaj (przeglądam niektóre szczegóły, w tym niedociągnięcia, które są omówione w artykule).

Retpoline polega na tym, że wywołanie i powrót funkcji oraz związane z tym manipulacje stosem są tak powszechne w programach komputerowych, że procesory są mocno zoptymalizowane do ich wykonywania. (Jeśli nie jesteś zaznajomiony z tym, jak działa stos w odniesieniu do wywołania i powrotu z funkcji ten post jest dobrym podkład.) W skrócie, gdy” wywołanie ” jest wykonywane, adres zwrotny jest pchany na stos. „ret” usuwa adres zwrotny i kontynuuje wykonywanie. Sprzęt do wykonywania spekulacyjnego zapamięta wypchnięty adres zwrotny i spekulacyjnie kontynuuje wykonywanie w tym momencie.

konstrukcja retpoliny zastępuje pośredni skok do miejsca pamięci zapisanego w rejestrzer11:

jmp *%r11

z:

call set_up_target; (1)
capture_spec: (4)
pause;
jmp capture_spec;
set_up_target:
mov %r11, (%rsp); (2)
ret; (3)

zobaczmy, co robi poprzedni kod złożenia krok po kroku i jak łagodzi zastrzyk docelowy gałęzi.

  1. w tym kroku kod wywołuje lokalizację pamięci, która jest znana w czasie kompilacji, więc jest to mocno zakodowane przesunięcie, a nie pośrednie. To umieszcza adres zwrotny capture_spec na stosie.
  2. adres zwrotny z połączenia jest nadpisywany rzeczywistym celem skoku.
  3. powrót jest wykonywany na rzeczywistym celu.
  4. gdy procesor zostanie uruchomiony, powróci do nieskończonej pętli! Pamiętaj, że procesor będzie spekulować do przodu, dopóki Ładowanie pamięci nie zostanie zakończone. W tym przypadku spekulacje zostały zmanipulowane w celu przechwycenia w nieskończoną pętlę, która nie ma skutków ubocznych, które są zauważalne dla atakującego. Kiedy procesor w końcu wykona rzeczywisty zwrot, przerywa wykonywanie spekulacyjne, co nie przyniosło żadnego efektu.

moim zdaniem jest to naprawdę pomysłowe rozwiązanie. Chwała inżynierom, którzy ją opracowali. Minusem tego łagodzenia jest to, że wymaga przekompilowania całego oprogramowania, tak aby pośrednie gałęzie były konwertowane na gałęzie retpolinowe. W przypadku usług w chmurze, takich jak Google, które posiadają cały stos, rekompilacja nie jest wielka. Dla innych może to być bardzo duża sprawa lub niemożliwe.

IBRS, STIBP i IBPB (Variant 2 mitigation)

wydaje się, że równolegle z rozwojem retpoline, Intel (i AMD do pewnego stopnia) pracują wściekle nad zmianami sprzętowymi, aby złagodzić ataki typu branch target injection. Trzy nowe funkcje sprzętowe dostarczane jako aktualizacje mikrokodu procesora to:

  • Indirect Branch Restricted Speculation (IBRS)
  • Single Thread Indirect Branch Predictors (STIBP)
  • Indirect Branch Predictor Barrier (IBPB)

ograniczone informacje na temat nowych funkcji mikrokodu są dostępne w firmie Intel tutaj. Byłem w stanie z grubsza poskładać to, co robią te nowe funkcje, czytając powyższą dokumentację i patrząc na łatki jądra Linuksa i hipernadzorcy Xen. Z mojej analizy wynika, że każda funkcja jest potencjalnie używana w następujący sposób:

  • IBRS przepuszcza bufor predykcji gałęzi pomiędzy poziomami uprawnień (użytkownik do jądra) i wyłącza predykcję gałęzi na wątku PROCESORA rodzeństwa. Przypomnijmy, że każdy rdzeń PROCESORA ma zwykle dwa wątki procesora. Wygląda na to, że na nowoczesnych procesorach sprzęt do przewidywania gałęzi jest współdzielony między wątkami. Oznacza to, że nie tylko kod trybu użytkownika może zatruć predyktor gałęzi przed wprowadzeniem kodu jądra, ale również kod uruchomiony na wątku PROCESORA rodzeństwa. Włączenie IBRS w trybie jądra zasadniczo uniemożliwia wcześniejsze wykonanie w trybie użytkownika i jakiekolwiek wykonanie na wątku PROCESORA rodzeństwa od wpływu na przewidywanie gałęzi.
  • STIBP wydaje się być podzbiorem IBR, który po prostu wyłącza przewidywanie gałęzi na wątku PROCESORA rodzeństwa. O ile mogę powiedzieć, głównym przypadkiem użycia tej funkcji jest zapobieganie zatruwaniu przez siostrzany wątek procesora predyktora gałęzi podczas uruchamiania dwóch różnych procesów trybu użytkownika (lub maszyn wirtualnych) na tym samym rdzeniu procesora w tym samym czasie. To szczerze nie jest całkowicie jasne dla mnie teraz, kiedy STIBP powinien być używany.
  • IBPB wydaje się opróżniać bufor predykcji gałęzi dla kodu działającego na tym samym poziomie uprawnień. Może to być użyte podczas przełączania się pomiędzy dwoma programami w trybie użytkownika lub dwoma maszynami wirtualnymi, aby upewnić się, że poprzedni kod nie koliduje z kodem, który ma zostać uruchomiony (chociaż bez STIBP uważam, że kod działający na wątku PROCESORA rodzeństwa może nadal zatruć predyktor gałęzi).

w chwili pisania tego tekstu, główne ograniczenia, które widzę zaimplementowane dla luki docelowego wtrysku gałęzi wydają się być zarówno retpoline, jak i IBRS. Prawdopodobnie jest to najszybszy sposób ochrony jądra przed programami trybu użytkownika lub hipernadzorcą przed gośćmi maszyny wirtualnej. W przyszłości spodziewałbym się, że zarówno STIBP, jak i IBPB zostaną wdrożone w zależności od poziomu paranoi różnych programów trybu użytkownika zakłócających się nawzajem.

koszt IBR również wydaje się bardzo różnić między architekturami procesorów, przy czym nowsze procesory Intel Skylake są stosunkowo tanie w porównaniu do starszych procesorów. W Lyft zaobserwowaliśmy około 20% spowolnienie niektórych ciężkich obciążeń wywołanych przez system w wystąpieniach AWS C4 po wdrożeniu środków łagodzących. Spekulowałbym, że Amazon wprowadził IBR i potencjalnie także retpolinę, ale nie jestem pewien. Wygląda na to, że Google mogło wdrożyć retpoline tylko w swojej chmurze.

z czasem spodziewałbym się, że procesory w końcu przejdą do modelu ibrs „always on”, w którym sprzęt domyślnie wyczyści separację predyktorów gałęzi między wątkami procesora i poprawnie wypłukuje stan przy zmianach poziomu uprawnień. Jedynym powodem, dla którego nie byłoby to dzisiaj zrobione, jest widoczny koszt modernizacji tej funkcjonalności na już wydanych mikroarchitekturach poprzez aktualizacje mikrokodu.

wnioski

bardzo rzadko wyniki badań zasadniczo zmieniają sposób budowy i działania komputerów. Meltdown i Spectre właśnie to zrobili. Ustalenia te znacząco zmienią projekt sprzętu i oprogramowania w ciągu najbliższych 7-10 lat (następny cykl sprzętowy procesora), ponieważ projektanci uwzględniają nową rzeczywistość możliwości wycieku danych przez kanały boczne pamięci podręcznej.

w międzyczasie wyniki Meltdown i Spectre oraz związane z nimi środki łagodzące będą miały istotne konsekwencje dla użytkowników komputerów w nadchodzących latach. W niedalekiej perspektywie ograniczenia będą miały wpływ na wydajność, która może być znaczna w zależności od obciążenia pracą i konkretnego sprzętu. Może to wymagać zmian operacyjnych dla niektórych infrastruktur (na przykład w Lyft agresywnie przenosimy niektóre obciążenia do instancji AWS C5 ze względu na fakt, że IBRS wydają się działać znacznie szybciej na procesorach Skylake, a nowy hipernadzorca Nitro dostarcza przerwania bezpośrednio do Gości korzystających z SR-IOV i APICv, usuwając wiele wyjść z maszyn wirtualnych dla ciężkich obciążeń IO). Użytkownicy komputerów stacjonarnych również nie są odporni, ze względu na ataki przeglądarek proof-of-concept przy użyciu JavaScript, które dostawcy systemów operacyjnych i przeglądarek pracują nad złagodzeniem. Ponadto, ze względu na złożoność luk w zabezpieczeniach, jest prawie pewne, że badacze bezpieczeństwa znajdą nowe exploity nieobjęte obecnymi ograniczeniami, które będą musiały zostać poprawione.

chociaż uwielbiam pracować w Lyft i czuję, że praca, którą wykonujemy w przestrzeni infrastruktury systemów mikroserwisowych, jest jedną z najbardziej wpływowych prac wykonywanych obecnie w branży, takie wydarzenia sprawiają, że tęsknię za pracą nad systemami operacyjnymi i hipernadzorcami. Jestem bardzo zazdrosny o heroiczną pracę, która została wykonana w ciągu ostatnich sześciu miesięcy przez ogromną liczbę ludzi w badaniu i łagodzeniu słabości. Chciałbym być tego częścią!

Czytaj dalej

  • Meltdown and Spectre academic papers:https://spectreattack.com/
  • wpis na blogu Google Project Zero:https://googleprojectzero.blogspot.com/2018/01/reading-privileged-memory-with-side.html
  • ograniczenia sprzętowe Intel Spectre:https://software.intel.com/sites/default/files/managed/c5/63/336996-Speculative-Execution-Side-Channel-Mitigations.pdf
  • retpoline blog post:
  • dobre podsumowanie znanych informacji:https://github.com/marcan/speculation-bugs/blob/master/README.md



Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.