Tagowanie pamięci

W czwartym kwartale 2018 roku ARM przedstawił nową wersję architektury procesorów pod zbiorczą nazwą ARMv8.5-A. Jedno z wprowadzonych usprawnień to Memory Tagging Extension (MTE): tagowanie pamięci ze wsparciem sprzętowym. Jaki jest cel wprowadzenia MTE?

Większość błędów bezpieczeństwa w kernelu linux, bibliotekach czy aplikacjach napisanych w C/C++ związanych jest z niepoprawnym dostępem do pamięci. Najczęstsze z nich to przepełnienie bufora na stosie lub stercie (stack or heap buffer overflow), dostęp do zwolnionej pamięci (use-after-free) i użycie niezainicjowanej pamięci (uninitizalized memory). Przypomnijmy, na czym polegają wspomniane błędy i jakie mogą powodować konsekwencje.

Przepełnienie bufora to sytuacja, w której program w trakcie swojego wykonywania odwołuje się do pamięci poza wyznaczonym obszarem (na stosie lub stercie). Znakomita większość podatności bezpieczeństwa to liniowe przepełnienia bufora lub bezpośredni dostęp poza obszar bufora (out-of-bounds access):
– Heartbleed
– Total Meltdown
– GHOSTBug
– Venom

Ostatnie błędy znalezione w Chrome::
– CVE-2018-6153
– CVE-2018-6120

Błędy odnalezione w kernelu linuxowym:
– CVE-2018-14633
– CVE-2018-12233

Błędy typu use-after-free, gdy program odwołuje się do fragmentu pamięci, który został poprzednio zwolniony:
– CVE-2018-16065
– CVE-2018-17182
– CVE-2018-10879

Błędy niezainicjalizowanej pamięci, które mogą prowadzić do ujawnienia fragmentu pamięci:
– CVE-2017-5103
– CVE-2017-5102
– Red Hut Bugzilla

Narzędzia do odnajdywania błędów dostępu do pamięci

Na rynku dostępnych jest kilka open-sourcowych narzędzi do przeprowadzenia skutecznej analizy pod kątem błędnego dostępu do pamięci. Najstarszym i najbardziej znanym jest Valgrind i jego moduł Memcheck, który został upubliczniony w lutym 2002 roku. Drugi, AdressSanitizer (ASAN), został zaprezentowany ze wsparciem w kompilatorach LLVM 3.1 i GCC 4.8 w 2011 przez Google. ASAN jest w stanie odnaleźć błędy typu stack i heap buffer overflow, use-after-free, use-after-return, czy use-after-scope. Kolejnym narzędziem z tej rodziny jest Memory Sanitizer (MSan), który odnajduje błędy związane z niezainicjalizowaną pamięcią. Dla audytowania kernela linuxowego zostały wprowadzone odpowiednio kmemcheck i kasan.

Użycie wspomnianych sanitizerów ma jednak kilka zauważalnych wad: narzędzia te utrzymują kopie fragmentów pamięci, czerwone strefy (red zones) i kwarantanny dookoła fragmentów stosu, sterty i obiektów globalnych. Taka forma testowania znacząco zwiększa zużycie pamięci i obciążenie procesora, dlatego narzędzia te są używane jedynie w fazie testów oraz jako wsparcie podczas przeprowadzania audytu kodu źródłowego pod kątem bezpieczeństwa, fuzzowania, czy ogólnie security hardeningu, a nigdy nie są używane w finalnych produktach.

Tagowanie pamięci

Innym podejściem do zapobiegania błędom dostępu do pamięci jest tagowanie pamięci (znane również jako kolorowanie pamięci). Zgodnie z tym podejściem, weryfikowany fragment pamięci zostaje wyrównany do rozmiaru TG (tag-granularity – granulacja tagu) i pokolorwany tagiem o rozmiarze TS (tag size – rozmiar tagu). Bity tagu w rozmiarze TS są umieszczane w nieużywanej części wskaźnika. W architekturze Aarch64 stanowi to do ośmiu najbardziej znaczących bitów 64-bitowego wskaźnika (dzięki TBI, top-byte-ignore). W trakcie alokacji pamięci, wylosowany tag zostaje oznaczony na całym obszarze zaalokawanej pamięci i powiązany z odpowiadającym mu wskaźnikiem. Podczas operacji ładowania i zapisu do wcześniej zaalokowanego fragmentu pamięci, tagi dla pamięci i wskaźnika są walidowane. Jeśli tagi różnią się zgłaszany jest wyjątek. Taki zabieg pozwala na odnalezienie większości błędów, takich jak stak and heap buffer overflow i use-after-free.

Przeanalizujmy alokację pamięci w rozmiarze 20 bajtów, gdy TS (tag-size) jest równy 4 bity, a TG (tag-granularity) 16 bajtów (obraz 1).

Należy zaalokować 20 bajtów: alokator pamięci wyrównuje ten rozmiar do 32 bajtów ze względu na 16-bajtowe TG. Następnie alokator losuje tag (zaznaczony na zielono) i zapisuje w 4 (TS) najbardziej znaczących bitach wskaźnika oraz taguje pamięć, na którą wskazuje. Podczas dereferencji wskaźnika p na pozycji 32, wartość taga nie będzie zgodna pomiędzy wskaźnikiem p, a pamięcią z offsetu 32 i taki dostęp zostanie wyłapany (obraz 2). Zaznaczmy, że z powodu 16-bajtowego rozmiaru TG, dereferencja spod pozycji np. 20 nie zostanie wykryta, ponieważ tag wskaźnika i pamięci będą takie same.

Podobny zabieg zostanie przeprowadzony podczas dealokacji pamięci. W trakcie zwalniania fragmentu sterty, pamięć zostanie ponownie otagowana (analogicznie jak w przykładzie 3, pokolorowana na czerwono). Jeśli pozostanie wskaźnik (zielony) wskazujący na tę pamięć (czerwona po zwolnieniu) i nastąpi próba dereferencji, taki dostęp zostanie wykryty (obraz 3).

Tagowanie pamięci otrzymało wsparcie w kompilatorze Clang/LLVM i zostało nazwane HWSAN (hardware-assisted address sanitizer). Działa ono na architekturze Aarch64 dzięki TBI z rozmiarem tagów równym 4 bity i wyrównaniem do 16-bajtów. Walidacja tagów odbywa się jednak programowo: cały czas pozostaje spory narzut na zużycie procesora i rozmiar pliku wykonywalnego (do 2 razy). Pozytywnie za to spadło zużycie pamięci, które względem standardowych rozwiązań jest bardzo niskie, około 6 proc.

Aby rozwiązać problemy związane z niepoprawnym dostępem do pamięci i niską wydajnością dostępnych narzędzi, ARM zdecydował się na wprowadzenie sprzętowego wsparcia dla tagowania pamięci, Memory Tagging Extension. Implementacja będzie pozwalała na tagowanie pamięci z rozmiarem tagu 4 bitów i granulacją tagu w rozmiarze 16 bajtów. Jednocześnie zestaw instrukcji został powiększony o 15 nowych:

Co dalej?

Pozostaje chwilę poczekać. Wsparcie dla tagowania pamięci dla procesorów ARM zostało wprowadzone do GCC 9 i LLVM, jednak należy wprowadzić jeszcze sporo optymalizacji. Oddzielnym problemem jest dostępność nowych procesorów ARMv8.5A: okres od prezentacji architektury do momentu produkcji gotowych układów przez producentów, takich jak Qualcomm czy Samsung to około 2 lata. Miejmy nadzieję, że układy SoC z nową architekturą szybko pojawią się na rynku: zaprezentowane rozszerzenie Memory Tagging Extension znacznie zmniejszy ilość potencjalnych błędów związanych z dostępem do pamięci w aplikacjach pisanych w C/C++.


Referencje
– Arm A-Profile Architecture Developments 2018: Armv8.5-A
– “Memory Tagging and how it improves C/C++ memory safety” by Kostya Serebryany: pdf video
– Wikipedia

Powiązane wpisy

Dodaj komentarz

0 komentarzy

Wyślij komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *