Co to jest retpolina i jak działa?

244

Aby zminimalizować ryzyko ujawnienia pamięci jądra lub pamięci międzyprocesowej ( atak Spectre ), jądro Linux 1 zostanie skompilowane z nową opcją , -mindirect-branch=thunk-externwprowadzoną w gcccelu wykonywania wywołań pośrednich za pośrednictwem tak zwanej retpoliny .

To wydaje się być nowo wynalezionym terminem, ponieważ wyszukiwarka Google pojawia się tylko bardzo niedawno (ogólnie w 2018 r.).

Co to jest retpolina i jak zapobiega ostatnim atakom związanym z ujawnianiem informacji o jądrze?


1 Nie jest on jednak specyficzny dla Linuksa - podobny lub identyczny konstrukt wydaje się być wykorzystywany jako część strategii ograniczania ryzyka w innych systemach operacyjnych.

BeeOnRope
źródło
6
Ciekawy artykuł pomocy technicznej od Google.
sgbj
2
och, więc wymawia się / ˌtræmpəˈlin / (American) lub / ˈtræmpəˌliːn / (British)
Walter Tross
2
Możesz wspomnieć, że jest to jądro Linuksa , choć gccwskazuje to w ten sposób! Nie rozpoznałem lkml.org/lkml/2018/1/3/780 jak na stronie z listą mailingową jądra Linuksa, nawet kiedy tam zajrzałem (i dostałem migawkę, ponieważ była offline).
PJTraill
@PJTraill - dodał tag jądra Linux
RichVel
@PJTraill - dobra uwaga, zaktualizowałem tekst pytania. Zauważ, że pierwszy raz zobaczyłem go w jądrze Linuksa ze względu na jego stosunkowo otwarty proces rozwoju, ale bez wątpienia te same lub podobne techniki są stosowane jako ograniczenie w całym spektrum systemów operacyjnych z otwartym i zamkniętym źródłem. Nie uważam tego za specyficzne dla Linuksa, ale z pewnością jest to łącze.
BeeOnRope,

Odpowiedzi:

158

Artykuł wspomniany przez sgbj w komentarzach napisanych przez Paula Turnera z Google wyjaśnia znacznie bardziej szczegółowo, ale dam mu szansę:

O ile potrafię poskładać to razem z ograniczonymi informacjami w tej chwili, retpolina jest trampoliną powrotną, która wykorzystuje nieskończoną pętlę, która nigdy nie jest wykonywana, aby zapobiec spekulowaniu procesora na celu pośredniego skoku.

Podstawowe podejście można znaleźć w gałęzi jądra Andi Kleen zajmującej się tym problemem:

Wprowadza nowe __x86.indirect_thunkwywołanie, które ładuje cel wywołania, którego adres pamięci (który zadzwonię ADDR) jest przechowywany na górze stosu i wykonuje skok za pomocą RETinstrukcji. Sam thunk jest następnie wywoływany za pomocą makra NOSPEC_JMP / CALL , który został użyty do zastąpienia wielu (jeśli nie wszystkich) pośrednich wywołań i skoków. Makro po prostu umieszcza cel wywołania na stosie i ustawia adres zwrotny, jeśli to konieczne (zwróć uwagę na nieliniowy przepływ sterowania):

.macro NOSPEC_CALL target
    jmp     1221f            /* jumps to the end of the macro */
1222:
    push    \target          /* pushes ADDR to the stack */
    jmp __x86.indirect_thunk /* executes the indirect jump */
1221:
    call    1222b            /* pushes the return address to the stack */
.endm

Umieszczenie callna końcu jest konieczne, aby po zakończeniu wywołania pośredniego przepływ sterujący był kontynuowany za użyciem NOSPEC_CALLmakra, dzięki czemu można go używać zamiast zwykłegocall

Sam thunk wygląda następująco:

    call retpoline_call_target
2:
    lfence /* stop speculation */
    jmp 2b
retpoline_call_target:
    lea 8(%rsp), %rsp 
    ret

Przepływ kontroli może być tutaj nieco mylący, więc wyjaśnię:

  • call wypycha bieżący wskaźnik instrukcji (etykieta 2) na stos.
  • leadodaje 8 do wskaźnika stosu , skutecznie odrzucając ostatnio wypisane cztero słowo, które jest ostatnim adresem zwrotnym (do etykiety 2). Następnie górna część stosu ponownie wskazuje na prawdziwy adres zwrotny ADDR.
  • retprzeskakuje *ADDRi resetuje wskaźnik stosu na początek stosu wywołań.

W końcu całe to zachowanie jest praktycznie równoważne skokowi bezpośrednio *ADDR. Jedną z korzyści, jakie otrzymujemy, jest to, że predyktor gałęzi używany dla instrukcji return (Return Stack Buffer, RSB), podczas wykonywania callinstrukcji, zakłada, że ​​odpowiednia retinstrukcja przejdzie do etykiety 2.

Część po etykiecie 2 tak naprawdę nigdy nie zostanie wykonana, jest to po prostu nieskończona pętla, która teoretycznie wypełniłaby potok JMPinstrukcji instrukcjami. Za pomocą LFENCE, PAUSElub bardziej ogólnie instrukcja powodując rurociąg instrukcja będzie stoisko zatrzymuje CPU od marnować energię i czas na wykonanie tego spekulacyjnego. Wynika to z tego, że w przypadku normalnego powrotu wywołania retpoline_call_target LFENCEnastępna instrukcja do wykonania. Tego też przewidzi predyktor gałęzi na podstawie oryginalnego adresu zwrotnego (etykieta 2)

Cytat z podręcznika architektury Intela:

Instrukcje następujące po LFENCE mogą zostać pobrane z pamięci przed LFENCE, ale nie zostaną wykonane, dopóki LFENCE się nie zakończy.

Zauważ jednak, że specyfikacja nigdy nie wspomina, że ​​LFENCE i PAUZA powodują zatrzymanie potoku, więc czytam trochę między wierszami tutaj.

Wróćmy do pierwotnego pytania: Ujawnienie informacji o pamięci jądra jest możliwe dzięki połączeniu dwóch pomysłów:

  • Mimo że wykonywanie spekulacyjne powinno być wolne od skutków ubocznych, gdy spekulacja była błędna, wykonywanie spekulacyjne nadal wpływa na hierarchię pamięci podręcznej . Oznacza to, że gdy ładowanie pamięci jest wykonywane spekulacyjnie, mogło nadal powodować eksmisję linii pamięci podręcznej. Tę zmianę w hierarchii pamięci podręcznej można zidentyfikować, dokładnie mierząc czas dostępu do pamięci, który jest mapowany na ten sam zestaw pamięci podręcznej.
    Możesz nawet wyciec niektóre fragmenty dowolnej pamięci, gdy adres źródłowy odczytanej pamięci został odczytany z pamięci jądra.

  • Pośredni predyktor rozgałęzienia procesorów Intel wykorzystuje tylko 12 najniższych bitów instrukcji źródłowej, dlatego łatwo jest otruć wszystkie możliwe historie prognoz 2 ^ 12 adresami pamięci kontrolowanymi przez użytkownika. Mogą one wówczas, gdy przewidywany skok pośredni w jądrze, zostać spekulacyjnie wykonany z uprawnieniami jądra. Korzystając z bocznego kanału synchronizacji pamięci podręcznej, możesz w ten sposób przeciekać dowolną pamięć jądra.

AKTUALIZACJA: Na liście mailingowej jądra trwa dyskusja, która prowadzi mnie do przekonania, że ​​retpoliny nie łagodzą w pełni problemów prognozowania gałęzi, tak jak gdy bufor zwrotny stosu (RSB) jest pusty, nowsze architektury Intel (Skylake +) wycofują się do podatnego na oddział bufora docelowego (BTB):

Retpolina jako strategia łagodzenia zamienia pośrednie gałęzie na zwroty, aby uniknąć korzystania z prognoz pochodzących z BTB, ponieważ mogą zostać zatrute przez atakującego. Problem ze Skylake + polega na tym, że niedomiar RSB wraca do używania prognozy BTB, która pozwala atakującemu przejąć kontrolę nad spekulacjami.

Tobias Ribizel
źródło
Nie sądzę, aby instrukcja LFENCE była ważna, zamiast tego implementacja Google używa instrukcji PAUSE. support.google.com/faqs/answer/7625886 Pamiętaj, że w cytowanej dokumentacji jest napisane: „nie wykonam”, „nie zrobię”, nie zostanie wykonany spekulacyjnie ”.
Ross Ridge
1
Z tej strony z najczęściej zadawanymi pytaniami Google: „Instrukcje pauzy w powyższych pętlach spekulacyjnych nie są wymagane do poprawności. Ale to oznacza, że ​​nieproduktywne wykonywanie spekulacyjne zajmuje mniej procesora”. Więc to nie potwierdza twojego wniosku, że LFENCE jest tutaj kluczem.
Ross Ridge
@RossRidge Zgadzam się częściowo, dla mnie wygląda to na dwie możliwe implementacje nieskończonej pętli, które podpowiadają procesorowi, aby nie spekulacyjnie wykonywał kod po PAUSE / LFENCE. Jeśli jednak LFENCE został wykonany spekulacyjnie i nie został wycofany, ponieważ spekulacja była prawidłowa, byłoby to sprzeczne z twierdzeniem, że zostanie ono wykonane dopiero po zakończeniu ładowania pamięci. (W przeciwnym razie cały zestaw instrukcji, które zostały wykonane spekulacyjnie, musiałby zostać
wycofany
1
Ma to tę zaletę, push/ retże nie asymetrii na zwrot adresowej predyktora stosie. Jest jeden nieprzewidywalny (przejście do lfenceprzed użyciem faktycznego adresu zwrotnego), ale zrównoważył to callmodyfikator + . rspret
Peter Cordes,
1
ups, przewaga nad push / ret(w moim ostatnim komentarzu). Odp: Twoja edycja: Niedopełnienie RSB powinno być niemożliwe, ponieważ retpolina zawiera call. Gdyby uprzedzenie jądra spowodowało zmianę kontekstu, wznowilibyśmy wykonanie z RSB przygotowanym z callharmonogramu. Ale może procedura obsługi przerwań mogłaby zakończyć się wystarczającą ilością rets, aby opróżnić RSB.
Peter Cordes,
46

Retpoline jest przeznaczony do ochrony przed wstrzyknięciem docelowej gałęzi ( CVE 2017-5715 ) wykorzystania. Jest to atak polegający na użyciu pośredniej instrukcji rozgałęzienia w jądrze w celu wymuszenia spekulatywnego wykonania dowolnego fragmentu kodu. Wybrany kod jest „gadżetem”, który jest w pewien sposób przydatny dla atakującego. Na przykład można wybrać kod, który spowoduje wyciek danych jądra poprzez wpływ na pamięć podręczną. Retpolina zapobiega temu exploitowi, po prostu zastępując wszystkie instrukcje gałęzi pośredniej instrukcją return.

Myślę, że kluczem w retpolinie jest tylko część „ret”, która zastępuje gałąź pośrednią instrukcją return, dzięki czemu procesor używa predyktora stosu zwrotnego zamiast przewidywalnego gałęzi. Jeśli zamiast tego zostanie użyta prosta instrukcja wypychania i powrotu, kod, który zostałby wykonany spekulacyjnie, byłby kodem, który funkcja w końcu i tak wróci do normy, a nie jakiś gadżet przydatny dla atakującego. Główną zaletą części trampoliny wydaje się być utrzymanie stosu zwrotnego, więc gdy funkcja faktycznie powraca do swojego obiektu wywołującego, jest to poprawnie przewidywane.

Podstawowa idea zastrzyku celu gałęzi jest prosta. Wykorzystuje to, że CPU nie rejestruje pełnego adresu źródła i miejsca docelowego gałęzi w swoich buforach docelowych gałęzi. Atakujący może więc wypełnić bufor za pomocą skoków we własnej przestrzeni adresowej, które spowodują trafienia predykcyjne, gdy konkretny skok pośredni zostanie wykonany w przestrzeni adresowej jądra.

Pamiętaj, że retpolina nie zapobiega bezpośredniemu ujawnianiu informacji o jądrze, a jedynie zapobiega wykorzystywaniu instrukcji pośredniej gałęzi do spekulacyjnego wykonania gadżetu, który ujawniałby informacje. Jeśli atakujący może znaleźć inne sposoby spekulacyjnego wykonania gadżetu, retpolina nie zapobiega atakowi.

Artykuł Spectre Attacks: Exploiting Speculative Execution autorstwa Paula Kochera, Daniela Genkina, Daniela Grussa, Wernera Haasa, Mike'a Hamburga, Moritza Lippa, Stefana Mangarda, Thomasa Preschera, Michaela Schwarza i Yuvala Yaroma daje następujący przegląd tego, jak można wykorzystać gałęzie pośrednie :

Wykorzystywanie pośrednich oddziałów. Korzystając z programowania zwrotnego (ROP), w tej metodzie atakujący wybiera gadżetz przestrzeni adresowej ofiary i wpływa na ofiarę w celu spekulacyjnego wykonania gadżetu. W przeciwieństwie do ROP atakujący nie polega na usterce w kodzie ofiary. Zamiast tego atakujący trenuje rozgałęziony bufor docelowy (BTB), aby błędnie przewidzieć gałąź z instrukcji gałęzi pośredniej na adres gadżetu, co powoduje spekulacyjne wykonanie gadżetu. Chociaż spekulacyjnie wykonane instrukcje są porzucane, ich wpływ na pamięć podręczną nie jest przywracany. Te efekty mogą być używane przez gadżet do wycieku poufnych informacji. Pokazujemy, jak przy starannym doborze gadżetu tę metodę można wykorzystać do odczytania dowolnej pamięci ofiary.

Aby pomylić BTB, atakujący znajduje wirtualny adres gadżetu w przestrzeni adresowej ofiary, a następnie wykonuje pośrednie rozgałęzienia na ten adres. To szkolenie odbywa się z przestrzeni adresowej atakującego i nie ma znaczenia, co znajduje się pod adresem gadżetu w przestrzeni adresowej atakującego; wszystko, co jest wymagane, to aby oddział używany do szkolenia oddziałów korzystał z tego samego docelowego adresu wirtualnego. (W rzeczywistości, o ile atakujący obsługuje wyjątki, atak może działać, nawet jeśli nie ma kodu zamapowanego na wirtualnym adresie gadżetu w przestrzeni adresowej atakującego). Nie jest również konieczne pełne dopasowanie adresu źródłowego oddziału używanego do szkolenia i adres docelowego oddziału. Zatem atakujący ma znaczną elastyczność w ustawianiu treningu.

Wpis na blogu zatytułowany Czytanie pamięci uprzywilejowanej z bocznym kanałem przez zespół Project Zero w Google stanowi kolejny przykład wykorzystania zastrzyku docelowego dla gałęzi do stworzenia działającego exploita.

Ross Ridge
źródło
9

To pytanie zostało zadane jakiś czas temu i zasługuje na nowszą odpowiedź.

Streszczenie :

Sekwencje „retpoliny” są konstrukcją programową, która umożliwia izolowanie pośrednich gałęzi przed spekulatywnym wykonywaniem. Można to zastosować w celu ochrony wrażliwych plików binarnych (takich jak system operacyjny lub implementacje hiperwizora) przed atakami wstrzyknięć docelowych oddziałów na ich pośrednie gałęzie.

Słowo „ ret poline ” to kontaminacja słów „powrót” i „trampolina”, podobnie jak Improvement „ rel poline ” powstał z połączenia „względnej” i „trampoliną”. Jest to konstrukcja trampoliny skonstruowana przy użyciu operacji powrotu, która również symbolicznie gwarantuje, że wszelkie powiązane spekulacyjne wykonania będą „odbijać się” w nieskończoność.

Aby zminimalizować ryzyko ujawnienia pamięci jądra lub pamięci międzyprocesowej (atak Spectre), jądro Linuksa [1] zostanie skompilowane z nową opcją, -mindirect-branch=thunk-externwprowadzoną do gcc w celu wykonywania wywołań pośrednich poprzez tak zwaną retpolinę.

[1] Nie jest on jednak specyficzny dla Linuksa - podobny lub identyczny konstrukt wydaje się być wykorzystywany jako część strategii łagodzących w innych systemach operacyjnych.

Użycie tej opcji kompilatora chroni tylko przed Spectre V2 w procesorach, których dotyczy problem, i wymagana jest aktualizacja mikrokodu dla CVE-2017-5715. Będzie „ działał ” na dowolnym kodzie (nie tylko na jądrze), ale warto zaatakować tylko kod zawierający „sekrety”.

To wydaje się być nowo wynalezionym terminem, ponieważ wyszukiwarka Google pojawia się tylko bardzo niedawno (ogólnie w 2018 r.).

Kompilator LLVM miał -mretpolineprzełącznik ponieważ przed 4 stycznia 2018 r . Ta data jest datą pierwszego opublikowania luki w zabezpieczeniach . GCC udostępniło swoje łatki 7 stycznia 2018 r.

Data CVE sugeruje, że luka została „ odkryta ” w 2017 r., Ale dotyczy niektórych procesorów wyprodukowanych w ciągu ostatnich dwóch dekad (prawdopodobnie została odkryta dawno temu).

Co to jest retpolina i jak zapobiega ostatnim atakom związanym z ujawnianiem informacji o jądrze?

Najpierw kilka definicji:

  • Trampolina - czasami nazywana wektorami skoków pośrednich, trampoliny to miejsca w pamięci, w których znajdują się adresy wskazujące na procedury obsługi przerwań, procedury we / wy itp. Wykonanie wskakuje do trampoliny, a następnie natychmiast wyskakuje lub odbija się, stąd nazwa trampolina. GCC tradycyjnie wspiera zagnieżdżone funkcje, tworząc wykonywalną trampolinę w czasie wykonywania, gdy pobierany jest adres zagnieżdżonej funkcji. Jest to mały fragment kodu, który zwykle znajduje się na stosie, w ramce stosu funkcji zawierającej. Trampolina ładuje statyczny rejestr łańcucha, a następnie przeskakuje na prawdziwy adres zagnieżdżonej funkcji.

  • Thunk - Thunk to podprogram używany do wstrzykiwania dodatkowych obliczeń do innego podprogramu. Klocki są używane przede wszystkim do opóźniania obliczeń, aż do uzyskania wyniku, lub do wstawiania operacji na początku lub na końcu innego podprogramu

  • Zapamiętywanie - Zapamiętana funkcja „zapamiętuje” wyniki odpowiadające niektórym zestawom określonych danych wejściowych. Kolejne wywołania z zapamiętanymi danymi wejściowymi zwracają zapamiętany wynik, zamiast go ponownie obliczać, eliminując w ten sposób pierwotny koszt wywołania z podanymi parametrami ze wszystkich oprócz pierwszego wywołania funkcji z tymi parametrami.

Z grubsza mówiąc , retpolina jest trampoliną ze zwrotem jako „ thunk” , aby „ zepsućzapamiętywanie w pośrednim predyktorze gałęzi.

Źródło : Retpolina zawiera instrukcję PAUSE dla Intela, ale dla AMD konieczna jest instrukcja LFENCE, ponieważ na tym procesorze instrukcja PAUSE nie jest instrukcją serializacji, więc pętla pauzy / jmp zużyje nadmierną moc, ponieważ spekuluje się na oczekiwanie na powrót źle przewidzieć właściwy cel.

Arstechnica ma proste wyjaśnienie problemu:

„Każdy procesor ma zachowanie architektoniczne (udokumentowane zachowanie, które opisuje sposób działania instrukcji i od którego programiści muszą pisać swoje programy) oraz zachowanie mikroarchitektoniczne (sposób, w jaki zachowuje się rzeczywista implementacja architektury). Mogą się one różnić w subtelny sposób. Na przykład architektonicznie program ładujący wartość z określonego adresu w pamięci będzie czekał, aż adres zostanie znany, zanim spróbuje wykonać ładowanie. Jednak mikroarchitektycznie procesor może spróbować spekulacyjnie zgadnąć adres, aby mógł się uruchomić ładowanie wartości z pamięci (która jest powolna), nawet zanim jest absolutnie pewne, którego adresu powinien użyć.

Jeśli procesor zgadnie źle, zignoruje wartość odgadnięcia i ponownie załaduje, tym razem z poprawnym adresem. Zachowanie zdefiniowane architektonicznie zostaje zatem zachowane. Ale to błędne przypuszczenie zakłóci inne części procesora - w szczególności zawartość pamięci podręcznej. Te zakłócenia mikroarchitektoniczne można wykryć i zmierzyć, mierząc czas potrzebny do uzyskania dostępu do danych, które powinny (lub nie powinny) znajdować się w pamięci podręcznej, umożliwiając złośliwemu programowi wyciąganie wniosków na temat wartości przechowywanych w pamięci. ”.

Z dokumentu Intela: „ Retpoline: A Branch Target Injection Mitigation ” ( .PDF ):

„Sekwencja retpoliny zapobiega wykorzystywaniu przez procesor spekulacyjny„ pośredniego predyktora gałęzi ”(jeden sposób przewidywania przepływu programu) do spekulacji na adres kontrolowany przez exploita (spełniający element 4 z pięciu elementów wstrzyknięcia celu gałęzi (wariant Spectre 2 ) wykorzystaj kompozycję wymienioną powyżej). ”.

Uwaga, element 4 brzmi: „Exploit musi skutecznie wpływać na tę pośrednią gałąź, aby spekulacyjnie źle przewidzieć i wykonać gadżet. Ten gadżet, wybrany przez exploita, wycieka tajne dane przez kanał boczny, zazwyczaj przez synchronizację pamięci podręcznej.”.

Obrabować
źródło