Bezpieczeństwo pamięci - Memory safety

Bezpieczeństwo pamięci to stan ochrony przed różnymi błędami oprogramowania i lukami w zabezpieczeniach podczas korzystania z dostępu do pamięci , takimi jak przepełnienia bufora i zwisające wskaźniki . Na przykład mówi się, że Java jest bezpieczna w pamięci, ponieważ wykrywanie błędów w czasie wykonywania sprawdza granice tablicy i wyłuskiwanie wskaźników. W przeciwieństwie do tego, C i C++ umożliwiają arbitralną arytmetykę wskaźników ze wskaźnikami zaimplementowanymi jako bezpośrednie adresy pamięci bez możliwości sprawdzania granic , a zatem są potencjalnie niebezpieczne dla pamięci .

Historia

Błędy pamięci zostały po raz pierwszy rozważone w kontekście systemów zarządzania zasobami i podziału czasu , aby uniknąć problemów, takich jak bomby widełkowe . Do czasu robaka Morris , który wykorzystywał przepełnienie bufora w fingerd, rozwój był w większości teoretyczny . Od tego czasu dziedzina bezpieczeństwa komputerowego rozwinęła się szybko, nasilając się wraz z mnóstwem nowych ataków, takich jak atak typu „ powrót do libc” i techniki obronne, takie jak niewykonywalny stos i randomizacja układu przestrzeni adresowej . Randomizacja zapobiega większości ataków polegających na przepełnieniu bufora i wymaga od atakującego użycia rozpylania sterty lub innych metod zależnych od aplikacji w celu uzyskania adresów, chociaż jej wdrażanie przebiega powoli. Jednak wdrożenia technologii są zazwyczaj ograniczone do losowych bibliotek i lokalizacji stosu.

Podejścia

DieHard, jego przeprojektowany DieHarder i Allinea Distributed Debugging Tool to specjalne alokatory sterty, które alokują obiekty na własnej losowej stronie pamięci wirtualnej, umożliwiając zatrzymanie i debugowanie nieprawidłowych odczytów i zapisów dokładnie według instrukcji, która je powoduje. Ochrona opiera się na ochronie pamięci sprzętowej, a zatem obciążenie zwykle nie jest znaczne, chociaż może znacznie wzrosnąć, jeśli program intensywnie wykorzystuje alokację. Randomizacja zapewnia jedynie probabilistyczną ochronę przed błędami pamięci, ale często można ją łatwo zaimplementować w istniejącym oprogramowaniu poprzez ponowne połączenie pliku binarnego.

Narzędzie memcheck firmy Valgrind wykorzystuje symulator zestawu instrukcji i uruchamia skompilowany program na maszynie wirtualnej sprawdzającej pamięć, zapewniając gwarantowane wykrywanie podzbioru błędów pamięci w czasie wykonywania. Jednak zazwyczaj spowalnia program 40-krotnie, a ponadto musi być wyraźnie poinformowany o niestandardowych alokatorach pamięci.

Dzięki dostępowi do kodu źródłowego istnieją biblioteki, które gromadzą i śledzą prawidłowe wartości wskaźników („metadane”) i sprawdzają poprawność każdego dostępu do wskaźnika z metadanymi, takie jak moduł odśmiecania pamięci firmy Boehm . Ogólnie rzecz biorąc, bezpieczeństwo pamięci można bezpiecznie zapewnić za pomocą śledzenia wyrzucania elementów bezużytecznych i wstawiania kontroli w czasie wykonywania przy każdym dostępie do pamięci; to podejście ma narzut, ale mniej niż Valgrind. Wszystkie języki zbierające śmieci stosują to podejście. W przypadku C i C++ istnieje wiele narzędzi, które wykonują transformację kodu w czasie kompilacji w celu sprawdzenia bezpieczeństwa pamięci w czasie wykonywania, takie jak CheckPointer i AddressSanitizer, które nakładają średni współczynnik spowolnienia wynoszący 2.

Inne podejście wykorzystuje statyczną analizę programu i zautomatyzowane dowodzenie twierdzeń, aby zapewnić, że program jest wolny od błędów pamięci. Na przykład język programowania Rust implementuje moduł sprawdzania wypożyczeń, aby zapewnić bezpieczeństwo pamięci. Narzędzia takie jak Coverity oferują statyczną analizę pamięci dla C. Inteligentne wskaźniki C++ są ograniczoną formą tego podejścia.

Rodzaje błędów pamięci

Może wystąpić wiele różnych rodzajów błędów pamięci:

  • Błędy dostępu : nieprawidłowy odczyt/zapis wskaźnika
  • Zmienne niezainicjowane — używana jest zmienna, której nie przypisano wartości. Może zawierać niepożądaną lub, w niektórych językach, uszkodzoną wartość.
    • Wyłuskanie wskaźnika zerowego - wyłuskanie nieprawidłowego wskaźnika lub wskaźnika do pamięci, która nie została przydzielona
    • Dzikie wskaźniki powstają, gdy wskaźnik jest używany przed inicjalizacją do jakiegoś znanego stanu. Wykazują to samo nieobliczalne zachowanie, co zwisające wskaźniki, chociaż jest mniej prawdopodobne, że pozostaną niewykryte.
  • Wyciek pamięci — gdy użycie pamięci nie jest śledzone lub jest śledzone nieprawidłowo
    • Wyczerpywanie stosu — występuje, gdy programowi zabraknie miejsca na stosie, zwykle z powodu zbyt głębokiej rekursji . Strona strażnik zwykle zatrzymuje program zapobiegania korupcji pamięci, ale działa z dużymi ramek stosu można pominąć stronę.
    • Wyczerpanie sterty — program próbuje przydzielić więcej pamięci niż dostępna ilość. W niektórych językach warunek ten należy sprawdzić ręcznie po każdej alokacji.
    • Podwójne darmowe - wielokrotne wezwania do darmowego mogą przedwcześnie zwolnić nowy obiekt pod tym samym adresem. Jeśli dokładny adres nie został ponownie wykorzystany, mogą wystąpić inne uszkodzenia, zwłaszcza w alokatorach korzystających z bezpłatnych list .
    • Nieprawidłowe wolne przekazanie nieprawidłowego adresu do zwolnienia może spowodować uszkodzenie sterty .
    • Mismatched free — gdy używanych jest wiele alokatorów, próba zwolnienia pamięci za pomocą funkcji cofnięcia alokacji innego alokatora
    • Niechciany aliasing - gdy ta sama lokalizacja pamięci jest przydzielana i modyfikowana dwukrotnie w niepowiązanych celach.

Bibliografia