Udostępnij przez


Obsługa buforu

Jednym z najczęstszych błędów w dowolnym sterowniku jest obsługa buforów, które są nieprawidłowe lub zbyt małe. Te błędy mogą zezwalać na przepełnienie buforu lub powodować awarie systemu, co może naruszyć bezpieczeństwo systemu. W tym artykule omówiono niektóre typowe problemy z obsługą buforu i sposoby ich unikania. Identyfikuje również przykładowy kod zestawu WDK, który demonstruje odpowiednie techniki obsługi buforu.

Typy bufora i nieprawidłowe adresy

Z perspektywy kierowcy, bufory występują w jednej z dwóch odmian:

  • Bufory stronicowane, które mogą, ale nie muszą być rezydentne w pamięci.

  • Bufory niestronicowane, które muszą znajdować się w pamięci.

Nieprawidłowy adres pamięci nie jest ani stronicowany, ani bezstronicowy. Ponieważ system operacyjny działa w celu rozwiązania błędu strony spowodowanego nieprawidłową obsługą buforu, należy wykonać następujące czynności:

  • Oddziela nieprawidłowy adres do jednego ze "standardowych" zakresów adresów (adresów jądra stronicowanych, adresów jądra niestronicowanych lub adresów użytkownika).

  • Zgłasza odpowiedni typ błędu. System zawsze obsługuje błędy buforu poprzez mechanizm sprawdzania błędów, takich jak PAGE_FAULT_IN_NONPAGED_AREA, albo przez wyjątek, taki jak STATUS_ACCESS_VIOLATION. Jeśli błąd jest sprawdzaniem błędów, system zatrzyma operację. W przypadku wyjątku system wywołuje programy obsługi wyjątków oparte na stosie. Jeśli żaden z procedur obsługi wyjątków nie obsługuje wyjątku, system wywołuje sprawdzanie błędów.

Niezależnie od tego, każda ścieżka dostępu, którą program aplikacyjny może wywołać, a która prowadzi do sprawdzenia błędów przez sterownik, stanowi naruszenie zabezpieczeń w sterowniku. Takie naruszenie pozwala aplikacji na ataki typu "odmowa usługi" na cały system.

Typowe założenia i błędy

Jednym z najczęstszych problemów w tym obszarze jest to, że autorzy sterowników zakładają zbyt wiele na temat środowiska operacyjnego. Niektóre typowe założenia i błędy obejmują:

  • Sterownik po prostu sprawdza, czy najbardziej znaczący bit jest ustawiony w adresie. Poleganie na stałym wzorcu bitowym w celu określenia typu adresu nie działa we wszystkich systemach ani scenariuszach. Na przykład ta kontrola nie działa na komputerach opartych na architekturze x86, gdy system korzysta z Czterogigabajtowego Tunowania (4GT). Przy użyciu 4GT adresy w trybie użytkownika ustawiają na wysoki bit dla trzeciego gigabajta przestrzeni adresowej.

  • Sterownik korzystający wyłącznie z ProbeForRead i ProbeForWrite w celu zweryfikowania adresu. Te wywołania zapewniają, że adres jest poprawnym adresem trybu użytkownika w momencie sondowania. Nie ma jednak gwarancji, że ten adres pozostanie prawidłowy po operacji sondy. W związku z tym technika ta wprowadza subtelny warunek wyścigu, który może prowadzić do okresowych niereprodukowalnych awarii.

    Wywołania ProbeForRead i ProbeForWrite są nadal niezbędne. Jeśli sterownik pomija sondę, użytkownicy mogą przekazać prawidłowe adresy trybu jądra, których bloki __try i __except (obsługa wyjątków strukturalnych) nie przechwycą, otwierając tym samym poważną lukę bezpieczeństwa.

    Najważniejsze jest to, że niezbędne są zarówno sondowanie, jak i obsługa wyjątków strukturalnych:

    • Sondowanie sprawdza, czy adres jest adresem trybu użytkownika i czy długość buforu mieści się w zakresie adresów użytkownika.

    • Blok __try/__except chroni przed dostępem.

    Należy pamiętać, że ProbeForRead sprawdza tylko, czy adres i długość mieszczą się w możliwym zakresie adresów trybu użytkownika (na przykład w zakresie trochę poniżej 2 GB dla systemu bez 4GT), a nie tego, czy adres pamięci jest prawidłowy. Natomiast parametr ProbeForWrite próbuje uzyskać dostęp do pierwszego bajtu na każdej stronie określonej długości, aby sprawdzić, czy te bajty są prawidłowymi adresami pamięci.

  • Sterownik korzystający z funkcji menedżera pamięci, takich jak MmIsAddressValid , aby upewnić się, że adres jest prawidłowy. Jak opisano w przypadku funkcji sondy, sytuacja ta wprowadza stan wyścigu, który może prowadzić do nieodwracalnych awarii.

  • Sterownik nie może używać obsługi wyjątków strukturalnych. Funkcje __try/except w kompilatorze korzystają z obsługi wyjątków na poziomie systemu operacyjnego. Wyjątki na poziomie jądra są zwracane do systemu za pomocą wywołania funkcji ExRaiseStatus lub jednej z powiązanych funkcji. Niewykorzystanie przez sterownik obsługi wyjątków strukturalnych wokół dowolnego wywołania, które może zgłosić wyjątek, doprowadzi do sprawdzenia błędu (zazwyczaj KMODE_EXCEPTION_NOT_HANDLED).

    Błędem jest użycie obsługi wyjątków strukturalnych wokół kodu, który nie powinien zgłaszać błędów. To użycie spowoduje po prostu zamaskowanie rzeczywistych usterek, które w przeciwnym razie zostaną znalezione. __try/__except Umieszczenie otoki na najwyższym poziomie wysyłki rutynowej nie jest właściwym rozwiązaniem tego problemu, chociaż czasami rozwiązanie refleksyjne wypróbowane przez pisarzy kierowców.

  • Sterownik zakłada, że zawartość pamięci użytkownika pozostanie stabilna. Załóżmy na przykład, że sterownik zapisał wartość w lokalizacji pamięci trybu użytkownika, a następnie później w tej samej procedurze odwoływał się do tej lokalizacji pamięci. Złośliwa aplikacja może aktywnie zmodyfikować pamięć po zapisie i w rezultacie spowodować awarię sterownika.

W przypadku systemów plików te problemy są poważne, ponieważ systemy plików zwykle polegają na bezpośrednim dostępie do buforów użytkowników (metoda transferu "METHOD_NEITHER"). Takie sterowniki bezpośrednio manipulują buforami użytkowników, a tym samym muszą uwzględniać metody ostrożnego obsługiwania, aby uniknąć awarii na poziomie systemu operacyjnego. Szybkie I/O zawsze przekazuje nieprzetworzone wskaźniki pamięci, więc sterowniki muszą zabezpieczać przed podobnymi problemami, jeśli szybkie I/O jest obsługiwane.

Przykładowy kod obsługi buforu

Zestaw WDK zawiera wiele przykładów weryfikacji buforu w przykładowym kodzie sterownika systemu plikówfastfat i CDFS, w tym:

  • Funkcja FatLockUserBuffer w fastfat\deviosup.c używa MmProbeAndLockPages do zablokowania fizycznych stron bufora użytkownika oraz MmGetSystemAddressForMdlSafe w FatMapUserBuffer, aby utworzyć wirtualne mapowanie dla zablokowanych stron.

  • Funkcja FatGetVolumeBitmap w fastfat\fsctl.c używa ProbeForRead i ProbeForWrite do sprawdzania poprawności buforów użytkownika w API defragmentacji.

  • Funkcja CdCommonRead w cdfs\read.c używa __try i __except wokół kodu do zerowania buforów użytkownika. Przykładowy kod w CdCommonRead wydaje się używać słów kluczowych try i except. W środowisku WDK te słowa kluczowe w języku C są definiowane pod względem rozszerzeń kompilatora __try i __except. Każda osoba używająca kodu C++ musi korzystać z natywnych typów kompilatora, aby prawidłowo obsługiwać wyjątki, ponieważ __try jest słowem kluczowym C++, ale nie słowem kluczowym C, i zapewni formę obsługi wyjątków języka C++, która nie jest odpowiednia dla sterowników jądra.