Czy nie zezwalanie programowi trybu użytkownika na dostęp do pamięci miejsca jądra i wykonywanie instrukcji IN i OUT nie podważa celu posiadania trybów procesora?

19

Gdy procesor znajduje się w trybie użytkownika, procesor nie może wykonywać uprzywilejowanych instrukcji i nie może uzyskać dostępu do pamięci miejsca w jądrze.

A gdy procesor jest w trybie jądra, może wykonywać wszystkie instrukcje i mieć dostęp do całej pamięci.

Teraz w Linuksie program trybu użytkownika może uzyskać dostęp do całej pamięci (używając /dev/mem) i może wykonać dwie uprzywilejowane instrukcje INoraz OUT(używając iopl()myślę).

Więc program trybu użytkownika w Linuksie może robić większość rzeczy (myślę, że większość rzeczy), które można zrobić w trybie jądra.

Czy nie zezwalanie, aby program trybu użytkownika miał całą tę moc, jest sprzeczny z celem posiadania trybów procesora?

użytkownik341099
źródło

Odpowiedzi:

23

Więc program trybu użytkownika w Linuksie może robić większość rzeczy (myślę, że większość rzeczy), które można zrobić w trybie jądra.

Cóż, nie wszystkie programy w trybie użytkownika mogą, tylko te z odpowiednimi uprawnieniami. I to zależy od jądra.

/dev/memjest chroniony zwykłymi uprawnieniami dostępu do systemu plików i CAP_SYS_RAWIOmożliwością. iopl()i ioperm()są również ograniczone przez tę samą możliwość.

/dev/memmożna również skompilować z jądra całkowicie ( CONFIG_DEVMEM).

Czy nie zezwalanie, aby program trybu użytkownika miał całą tę moc, jest sprzeczny z celem posiadania trybów procesora?

Być może. Zależy to od tego, co mają być możliwe uprzywilejowane procesy przestrzeni użytkownika. Procesy w przestrzeni użytkownika mogą również zniszczyć cały dysk twardy, jeśli mają dostęp do /dev/sda(lub równoważny), nawet jeśli jest to sprzeczne z celem posiadania sterownika systemu plików do obsługi dostępu do pamięci.

(Jest też fakt, że iopl()działa on przy użyciu trybów uprawnień procesora na i386, więc nie można powiedzieć, że pokonałby ich cel.)

ilkkachu
źródło
2
Nawet ioplnie zezwala na wszystkie uprzywilejowane instrukcje, więc nadal jest użyteczny, aby upewnić się, że invdbłędny program przestrzeni użytkownika nie zostanie przypadkowo uruchomiony , przeskakując przez uszkodzony wskaźnik funkcji wskazujący na wykonywalną pamięć zaczynającą się od 0F 08bajtów. Dodałem odpowiedź z niektórymi przyczynami niezwiązanymi z bezpieczeństwem, dlaczego użyteczne jest, aby procesy w przestrzeni użytkownika podniosły ich uprawnienia.
Peter Cordes,
16

Tylko w ten sam sposób, w jaki modprobe„pokonuje” bezpieczeństwo, ładując nowy kod do jądra.

Z różnych powodów czasem lepiej jest mieć częściowo uprzywilejowany kod (np. Sterowniki graficzne na serwerze X) działający w przestrzeni użytkownika, a nie w wątku jądra.

  • Będąc w stanie to killzrobić łatwiej, chyba że zablokuje HW.
  • Posiadanie strony żądania kodu / danych z plików w systemie plików. (Pamięć jądra nie jest stronicowana)
  • Nadanie jej własnej wirtualnej przestrzeni adresowej, w której błędy na serwerze X mogą po prostu spowodować awarię serwera X bez konieczności usuwania jądra.

Nie robi wiele dla bezpieczeństwa, ale ma wiele zalet związanych z niezawodnością i architekturą oprogramowania.

Pieczenie sterowników graficznych w jądrze może zredukować przełączanie kontekstu między klientami X a serwerem X, jak tylko jeden użytkownik-> jądro-> użytkownik, zamiast konieczności wprowadzania danych do innego procesu przestrzeni użytkowej, ale serwery X były historycznie za duże i zbyt błędne chcieć ich w pełni w jądrze.


Tak, złośliwy kod z tymi uprawnieniami może w razie potrzeby przejąć jądro, /dev/memmodyfikując kod jądra.

Lub na przykład na x86, uruchom cliinstrukcję, aby wyłączyć przerwania na tym rdzeniu po ioplwywołaniu systemowym, aby ustawić poziom uprawnień IO na ring 0.

Ale nawet iopl„tylko” x86 daje dostęp do niektórych instrukcji : in / out (i wersje string in / out) oraz cli / sti. Nie pozwala używać rdmsrani wrmsrodczytywać ani zapisywać „rejestrów specyficznych dla modelu” (np. IA32_LSTARKtóry ustawia adres punktu wejścia jądra dla syscallinstrukcji x86-64 ), ani nie lidtzastępuje tablicy deskryptorów przerwań (co pozwoliłoby ci całkowicie wziąć na maszynie z istniejącego jądra, przynajmniej na tym rdzeniu.)

Nie można nawet odczytać rejestrów kontrolnych (takich jak CR3, który zawiera adres fizyczny katalogu stron najwyższego poziomu, do którego atakujący może się przydać jako przesunięcie w /dev/memcelu zmodyfikowania własnych tabel stron jako alternatywy dla mmapwiększej ilości danych /dev/mem. )

invd(unieważnij wszystkie pamięci podręczne bez zapisywania !! ( przypadek użycia = wczesny BIOS przed skonfigurowaniem pamięci RAM)) to kolejna zabawna, która zawsze wymaga pełnego CPL 0 (aktualny poziom uprawnień), nie tylko IOPL. Nawet wbinvdjest uprzywilejowany, ponieważ jest tak wolny (i nie jest przerywany) i musi opróżniać wszystkie pamięci podręczne na wszystkich rdzeniach. (Patrz Czy istnieje sposób, aby wypłukać całą pamięć podręczną procesora związanych z programem? I wykorzystanie instrukcji WBINVD )

Błędy, które powodują przeskakiwanie na zły adres z działającymi danymi jako kodem, nie mogą więc przypadkowo wykonać żadnej z tych instrukcji na serwerze X w przestrzeni użytkownika.


Obecny poziom uprawnień (w trybie chronionym i długim) to niskie 2 bity cs(selektora segmentu kodu) . mov eax, cs/ and eax, 3działa w dowolnym trybie, aby odczytać poziom uprawnień.

Aby napisać poziom uprawnień, wykonaj a jmp farlub call farustaw CS:RIP(ale wpis GDT / LDT dla segmentu docelowego może go ograniczyć na podstawie starego poziomu uprawnień, dlatego przestrzeń użytkownika nie może tego zrobić, aby się podnieść). Lub używasz intlub, syscallaby przełączyć na pierścień 0 w punkcie wejścia jądra.

Peter Cordes
źródło
Właściwie jestem całkiem pewien, że to po prostu „selektor” kodu w paczce Intela. Był to segment w 8086/8088, prawdopodobnie w 80186, ale w 80286 był określany jako selektor i nie sądzę, żeby od tego czasu oficjalnie zmienili tę terminologię.
CVn