Błędy NULL pointer dereference

Jakiś już czas temu, dosyć głośno było o błędach typu “NULL pointer dereference” w jądrze Linux’a. Jakkolwiek wiele mówiło się o samych błędach to ciężko było znaleźć wyjaśnienie na czym dokładnie problem polega.

Są to bardzo poważne błędy w wyniku których można uzyskać uprawnienia administratora na danej maszynie. Aby zrozumieć ich naturę dobrze jest znać choć trochę podstawy budowy komputerów i systemów operacyjnych. Spróbuję jednak pokrótce i najprościej jak się da wyjaśnić o co w tym wszystkim chodzi.

Cały problem sprowadza się do tego jak system operacyjny wykorzystuje wirtualną przestrzeń adresową. Wirtualna przestrzeń adresowa jest zbiorem adresów, które jednak nie przekładają się bezpośrednio na pamięć fizyczną, tylko są odpowiednio mapowane na adresy fizyczne wedle potrzeby. Na maszynie 32-bitowe przestrzeń ta ma 32 bity, ale już na moim AMD64 tylko 48 bitów (a fizyczne adresy mają 40-bitów, 64-bitowe są “słowa” procesora). W Linux’ie na architekturze x86 wykorzystywany jest tak zwany płaski model pamięci. Oznacza to, że wszystkie procesy widzą całą dostępną przestrzeń adresową, od pierwszego dostępnego adresu (0x00000000) do ostatniego (0xffffffff.).

Pierwszy 1GiB jest zarezerwowany jako przestrzeń jądra. Z założenia wszystko co się tam znajduje jest procesem jądra, a więc działa z maksymalnym, najwyższym priorytetem. Każda próba odwołania się do tej części pamięci przez zwykły proces kończy się zabiciem go (jak zresztą każda próba odwołania się do adresu pamięci nie należącego do programu).

I tutaj właśnie przechodzimy do sedna sprawy. Podczas programowania, używając wskaźników, kiedy chcemy zaznaczyć, że dany wskaźnik nie wskazuje już na żaden element ustawiamy jego wartość na NULL. Jednak w praktyce NULL też ma wartość, a jest nią zero. Czyli ustawiając wskaźnik na NULL tak naprawdę ustawiamy go aby wskazywał na adres 0x00000000, który należy do przestrzeni jądra.
Używając funkcji mmap() możemy umieścić pod tym adresem jakieś dane. Najchętniej kod który po wykonaniu wywoła dla nas powłokę. Jednak tak jak powiedziałem wcześniej, odwoływanie się do nie swojej pamięci kończy się wyrokiem śmierci ze skutkiem natychmiastowym (dlatego właśnie programy które starają się odwoływać do elementów wskaźnika wskazującego na NULL kończą się wyrzuceniem “Segmentation fault”). A gdyby tak przekonać jądro, żeby wywołało dla nas ten kod? Przecież adres 0x00000000 należy do jądra, a więc ma ono prawo wykonać to co się tam znajduje. W ten sposób kod zostaje wykonany z maksymalnymi możliwymi prawami, czyli otrzymamy powłokę z prawami root’a. Oczywiście funkcje jądra mają obowiązek sprawdzać czy nie odwołują się przypadkiem do tego adresu. Jednak czasem zdarza się, że robią to źle. Takie właśnie nieprawidłowe funkcje są tym czego poszukują hackerzy/crackerzy.

Po ostatnich błędach dodano do jądra funkcję która zabrania mapowania wszelkich adresów poniżej określonego. Daje to bardzo dużo, bo co z tego, że w jakiejś funkcji jądra znajdzie się taki błąd, jeśli nie możemy nic umieścić pod tym adresem? Pytanie jednak czy i tego zabezpieczenia nie może obejść (a raczej jak i kiedy się to komuś uda ;)).

Bardzo dobry przykład, wytworzony w celach szkoleniowo-prezentacyjnych, wraz z objaśnieniem można znaleźć pod tym linkiem.

Dokładniej o tym problemie można poczytać w styczniowym numerze magazynu hakin9, który dostępny jest za darmo na stronie lub bezpośrednio tutaj.

Comment are closed.