Udostępnij przez


Błędy w bezpośrednim I/O

Najczęstszym bezpośrednim problemem we/wy jest nieprawidłowe obsługiwanie buforów o zerowej długości. Ponieważ menedżer we/wy nie tworzy list MDL dla transferów o zerowej długości, bufor o zerowej długości powoduje wartość NULL w Irp->MdlAddress.

Aby zamapować przestrzeń adresową, sterowniki powinny używać MmGetSystemAddressForMdlSafe, który zwraca NULL w przypadku niepowodzenia mapowania, ponieważ to nastąpi, gdy sterownik przekazuje NULL MdlAddress. Sterowniki powinny zawsze sprawdzać, czy zwracana wartość ma wartość NULL, zanim podejmą próbę użycia zwróconego adresu.

Bezpośrednie I/O obejmuje podwójne mapowanie przestrzeni adresowej użytkownika z buforem adresu systemowego, tak że dwa różne adresy wirtualne mają ten sam adres fizyczny. Podwójne mapowanie ma następujące konsekwencje, które czasami mogą powodować problemy ze sterownikami:

  • Przesunięcie na stronę wirtualną adresu użytkownika staje się przesunięciem na stronę systemową.

    Dostęp poza końcem tych buforów systemowych może pozostać niezauważony przez długi czas, w zależności od granularności strony mapowania. Jeśli bufor obiektu wywołującego nie zostanie przydzielony na końcu strony, dane zapisane poza końcem buforu pojawią się jednak w buforze, a obiekt wywołujący nie będzie wiedział, że wystąpił błąd. Jeśli koniec buforu zbiega się z końcem strony, systemowe adresy wirtualne poza końcem mogą wskazywać coś lub być nieprawidłowe. Takie problemy mogą być niezwykle trudne do znalezienia.

  • Jeśli proces wywołujący ma inny wątek, który modyfikuje mapowanie pamięci użytkownika, zawartość buforu systemowego zmieni się, gdy mapowanie pamięci użytkownika ulegnie zmianie.

    W takiej sytuacji użycie buforu systemowego do przechowywania danych tymczasowych może powodować problemy. Dwa pobrania z tej samej lokalizacji pamięci mogą przynieść różne wartości.

    Poniższy fragment kodu odbiera ciąg w bezpośrednim żądaniu we/wy, a następnie próbuje przekonwertować ten ciąg na wielkie litery:

    PWCHAR  PortName = NULL;
    
    PortName = (PWCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
    
    //
    // Null-terminate the PortName so that RtlInitUnicodeString will not
    // be invalid.
    //
    PortName[Size / sizeof(WCHAR) - 1] = UNICODE_NULL;
    
    RtlInitUnicodeString(&AdapterName, PortName);
    

    Ponieważ bufor może nie być poprawnie sformułowany, kod próbuje wymusić wartość NULL Unicode jako ostatni znak buforu. Jeśli jednak podstawowa pamięć fizyczna jest podwójnie mapowana na adresy zarówno w trybie użytkownika, jak i w trybie jądra, inny wątek w procesie może zastąpić bufor zaraz po zakończeniu tej operacji zapisu.

    Z drugiej strony, jeśli wartość NULL nie jest obecna, wywołanie funkcji RtlInitUnicodeString może przekroczyć zakres buforu i spowodować sprawdzenie błędów, jeśli znajduje się poza mapowaniem systemu.

Jeśli sterownik tworzy i mapuje własny MDL (Lista Deskryptorów Pamięci), powinien upewnić się, że uzyskuje dostęp do MDL tylko za pomocą metody, dla której został on zweryfikowany. Oznacza to, że gdy sterownik wywołuje MmProbeAndLockPages, określa metodę dostępu (IoReadAccess, IoWriteAccess lub IoModifyAccess). Jeśli sterownik określa IoReadAccess, nie może później próbować zapisywać w buforze systemowym udostępnionym przez mmGetSystemAddressForMdl lub MmGetSystemAddressForMdlSafe.