Udostępnij przez


Izolacja procesora GPU oparta na IOMMU

Izolacja procesora GPU oparta na IOMMU jest techniką służącą do zwiększania bezpieczeństwa i stabilności systemu przez zarządzanie sposobem uzyskiwania dostępu do pamięci systemu przez procesory GPU. W tym artykule opisano funkcję izolacji procesora GPU opartą na IOMMU WDDM dla urządzeń obsługujących funkcję IOMMU oraz sposób implementowania jej przez deweloperów w sterownikach graficznych.

Ta funkcja jest dostępna od systemu Windows 10 w wersji 1803 (WDDM 2.4). Zobacz Ponowne mapowanie IOMMU DMA , aby uzyskać najnowsze aktualizacje IOMMU.

Przegląd

Izolacja procesora GPU oparta na IOMMU umożliwia Dxgkrnl ograniczenie dostępu do pamięci systemowej z procesora GPU przez użycie sprzętu IOMMU. System operacyjny może udostępniać adresy logiczne zamiast adresów fizycznych. Te adresy logiczne mogą służyć do ograniczenia dostępu urządzenia do pamięci systemowej tylko do pamięci, do których powinien mieć dostęp. Zapewnia to, że IOMMU tłumaczy dostęp do pamięci za pośrednictwem pcIe na prawidłowe i dostępne strony fizyczne.

Jeśli adres logiczny uzyskiwany przez urządzenie jest nieprawidłowy, urządzenie nie może uzyskać dostępu do pamięci fizycznej. To ograniczenie zapobiega szeregowi luk w zabezpieczeniach, które umożliwiają atakującemu uzyskanie dostępu do pamięci fizycznej za pośrednictwem naruszonego urządzenia sprzętowego. Bez niego osoby atakujące mogą odczytać zawartość pamięci systemowej, która nie jest potrzebna do wykonania operacji urządzenia.

Domyślnie ta funkcja jest włączona tylko dla komputerów, na których funkcja Windows Defender Application Guard jest włączona dla przeglądarki Microsoft Edge (czyli wirtualizacji kontenerów).

W celach programistycznych rzeczywista funkcja ponownego mapowania IOMMU jest włączona lub wyłączona za pomocą następującego klucza rejestru:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers
DWORD: IOMMUFlags

0x01 Enabled
     * Enables creation of domain and interaction with HAL

0x02 EnableMappings
     * Maps all physical memory to the domain
     * EnabledMappings is only valid if Enabled is also set. Otherwise no action is performed

0x04 EnableAttach
     * Attaches the domain to the device(s)
     * EnableAttach is only valid if EnableMappings is also set. Otherwise no action is performed

0x08 BypassDriverCap
     * Allows IOMMU functionality regardless of support in driver caps. If the driver does not indicate support for the IOMMU and this bit is not set, the enabled bits are ignored.

0x10 AllowFailure
     * Ignore failures in IOMMU enablement and allow adapter creation to succeed anyway.
     * This value cannot override the behavior when created a secure VM, and only applies to forced IOMMU enablement at device startup time using this registry key.

Jeśli ta funkcja jest włączona, funkcja IOMMU również zostanie aktywowana wkrótce po uruchomieniu adaptera. Wszystkie alokacje sterowników wykonane przed tym czasem są mapowane, gdy zostanie włączone.

Ponadto, jeśli klucz przejściowy prędkości 14688597 jest ustawiony jako włączony, IOMMU jest aktywowany po utworzeniu bezpiecznej maszyny wirtualnej. Na razie ten klucz przejściowy jest domyślnie wyłączony, aby zezwolić na samodzielne hostowanie bez odpowiedniej obsługi IOMMU.

Po włączeniu tej opcji uruchamianie bezpiecznej maszyny wirtualnej kończy się niepowodzeniem, jeśli sterownik nie zapewnia obsługi IOMMU.

Obecnie nie ma możliwości wyłączenia IOMMU po jej włączeniu.

Dostęp do pamięci

Dxgkrnl zapewnia, że wszystkie pamięci dostępne przez procesor GPU są ponownie mapowane za pośrednictwem IOMMU, aby upewnić się, że ta pamięć jest dostępna. Pamięć fizyczna, do którego procesor GPU potrzebuje dostępu, można obecnie podzielić na cztery kategorie:

  • Alokacje specyficzne dla sterowników wykonywane za pośrednictwem funkcji typu MmAllocateContiguousMemory lub MmAllocatePagesForMdl (w tym SpecifyCache i ich rozszerzone odmiany) muszą być mapowane na IOMMU, zanim procesor GPU uzyska do nich dostęp. Zamiast wywoływać interfejsy API mm , Dxgkrnl udostępnia wywołania zwrotne do sterownika trybu jądra, aby umożliwić alokację i ponowne mapowanie w jednym kroku. Każda pamięć, która ma być dostępna dla GPU, musi przechodzić przez te wywołania zwrotne, w przeciwnym razie GPU nie może uzyskać do niej dostępu.

  • Cała pamięć dostępna przez GPU podczas operacji stronicowania lub mapowana przez GpuMmu musi być zmapowana do IOMMU. Ten proces jest całkowicie wewnętrzny dla Menedżera pamięci wideo (VidMm), który jest podkomponentem Dxgkrnl. Program VidMm obsługuje mapowanie i odmapowywanie logicznej przestrzeni adresowej w dowolnym momencie, gdy procesor GPU ma uzyskać dostęp do tej pamięci, w tym:

  • Mapowanie magazynu kopii zapasowych alokacji dla jednego z następujących:

    • Czas trwania transferu do lub z pamięci VRAM.
    • Cały czas, gdy pamięć pomocnicza jest mapowana do segmentów pamięci systemowej lub segmentów aperturowych.
  • Mapowanie i odmapowanie monitorowanych ogrodzeń.

  • Podczas przejść zasilania sterownik może wymagać zapisania części pamięci zarezerwowanej przez sprzęt. Aby poradzić sobie z tą sytuacją, Dxgkrnl udostępnia mechanizm dla sterownika w celu określenia, ile pamięci jest z góry do przechowywania tych danych. Dokładna ilość pamięci wymaganej przez sterownik może się dynamicznie zmieniać. Oznacza to, że Dxgkrnl pobiera opłatę za zatwierdzenie na górnej granicy w momencie zainicjowania adaptera w celu zapewnienia, że strony fizyczne można uzyskać, gdy jest to wymagane. Dxgkrnl jest odpowiedzialny za zapewnienie, że ta pamięć jest zablokowana i zamapowana do IOMMU w celu transferu podczas przejścia zasilania.

  • W przypadku wszystkich zasobów sprzętowych zarezerwowanych, VidMm gwarantuje, że prawidłowo mapuje zasoby IOMMU w momencie dołączenia urządzenia do IOMMU. Obejmuje to pamięć zgłaszaną przez segmenty pamięci zgłoszone za pomocą elementu PopulatedFromSystemMemory. W przypadku pamięci zarezerwowanej (na przykład zarezerwowanej przez oprogramowanie układowe/BIOS), która nie jest uwidoczniona za pośrednictwem segmentów VidMm, Dxgkrnl wywołuje DXGKDDI_QUERYADAPTERINFO, aby zapytać o wszystkie zarezerwowane zakresy pamięci, które sterownik musi wcześniej zmapować. Aby uzyskać szczegółowe informacje, zobacz Pamięć zarezerwowana sprzętu .

Przypisanie domeny

Podczas inicjowania sprzętu Dxgkrnl tworzy domenę dla każdego logicznego adaptera w systemie. Domena zarządza przestrzeń adresową logiczną i śledzi tabele stron oraz inne niezbędne dane dla mapowań. Wszystkie adaptery fizyczne w pojedynczym adapterze logicznym należą do tej samej domeny. Dxgkrnl śledzi całą zamapowaną pamięć fizyczną za pomocą nowych procedur wywołania zwrotnego dla alokacji i każdej pamięci przydzielonej przez sam system VidMm.

Domena zostanie dołączona do urządzenia przy pierwszym utworzeniu bezpiecznej maszyny wirtualnej lub wkrótce po uruchomieniu urządzenia, jeśli zostanie użyty powyższy klucz rejestru.

Wyłączny dostęp

Dołączanie i odłączanie domeny IOMMU jest szybkie, ale jednak nie jest obecnie atomowe. Ponieważ nie jest atomowa, transakcja przeprowadzona przez PCIe nie ma gwarancji poprawnego tłumaczenia podczas zmiany na domenę IOMMU z innymi mapowaniami.

Aby obsłużyć tę sytuację, począwszy od systemu Windows 10 w wersji 1803 (WDDM 2.4), KMD musi zaimplementować następującą parę DDI, aby móc wywołać Dxgkrnl:

Te identyfikatory DDI tworzą parowanie początku/końca, gdzie Dxgkrnl żąda, aby sprzęt milczał przez magistralę. Sterownik musi upewnić się, że jego sprzęt jest cichy za każdym razem, gdy urządzenie zostanie przełączone do nowej domeny IOMMU. Oznacza to, że sterownik musi upewnić się, że nie odczytuje ani nie zapisuje w pamięci systemowej z urządzenia między tymi dwoma wywołaniami.

Między tymi dwoma wywołaniami Dxgkrnl zapewnia następujące gwarancje:

  • Harmonogram jest zawieszony. Wszystkie aktywne obciążenia są wyczyszczone, a żadne nowe obciążenia nie są wysyłane ani zaplanowane na sprzęcie.
  • Nie są wykonywane żadne inne wywołania DDI.

W ramach tych wywołań sterownik może wyłączyć i pominąć przerwania (w tym przerwania asynchroniczne) podczas wyłącznego dostępu, nawet bez wyraźnego powiadomienia z systemu operacyjnego.

Dxgkrnl gwarantuje, że wszystkie oczekujące prace zaplanowane na sprzęcie zostaną ukończone, a następnie wchodzi w region wyłącznego dostępu. W tym czasie Dxgkrnl przypisuje domenę do urządzenia. Dxgkrnl nie wysyła żadnych żądań sterownika ani sprzętu między tymi wywołaniami.

Zmiany DDI

Wprowadzono następujące zmiany DDI w celu obsługi izolacji GPU w oparciu o IOMMU:

Alokacja pamięci i mapowanie na IOMMU

Dxgkrnl udostępnia pierwsze sześć wywołań zwrotnych w poprzedniej tabeli do sterownika trybu jądra, aby umożliwić mu przydzielanie pamięci i ponowne mapowanie jej do logicznej przestrzeni adresowej IOMMU. Te funkcje wywołania zwrotnego naśladują procedury udostępniane przez interfejs API mm . Udostępniają sterownikowi MDL oraz wskaźniki opisujące pamięć, która jest również mapowana na IOMMU. Te MDL-e nadal opisują strony fizyczne, ale przestrzeń adresowa logiczna IOMMU jest mapowana pod tym samym adresem.

Dxgkrnl śledzi żądania związane z tymi wywołaniami zwrotnymi, aby upewnić się, że nie ma przecieków pamięci w sterowniku. Wywołania zwrotne alokacji zapewniają kolejny uchwyt w ramach danych wyjściowych, które muszą zostać dostarczone z powrotem do odpowiedniego bezpłatnego wywołania zwrotnego.

W przypadku pamięci, której nie można przydzielić za pomocą jednego z podanych wywołań zwrotnych alokacji, wywołanie zwrotne DXGKCB_MAPMDLTOIOMMU jest udostępniane, aby umożliwić śledzenie i użycie list MDL zarządzanych przez sterowniki z użyciem IOMMU. Sterownik korzystający z tego wywołania zwrotnego jest odpowiedzialny za zapewnienie, że okres istnienia MDL przekracza odpowiednie wywołanie odmapowania. W przeciwnym razie wywołanie unmap ma niezdefiniowane działanie. To niezdefiniowane zachowanie mogło prowadzić do naruszenia zabezpieczeń stron MDL, które Mm zmieniło do czasu ich odmapowania.

Program VidMm automatycznie zarządza wszystkimi utworzonymi alokacjami (na przykład DdiCreateAllocationCb, monitorowane ogrodzenia itp.) w pamięci systemowej. Sterownik nie musi nic robić, aby te alokacje działały.

Rezerwacja bufora ramki

W przypadku sterowników, które muszą zapisywać zarezerwowane części buforu ramki do pamięci systemowej podczas przejść zasilania, Dxgkrnl obciąża rezerwacją wymaganą pamięć podczas inicjalizacji karty. Jeśli sterownik zgłasza obsługę izolacji IOMMU, Dxgkrnl wyda wywołanie DXGKDDI_QUERYADAPTERINFO z następującymi natychmiast po wykonaniu zapytania o fizyczne możliwości adaptera:

  • Typ jest DXGKQAITYPE_FRAMEBUFFERSAVESIZE
  • Dane wejściowe są typu UINT, który jest indeksem fizycznego adaptera.
  • Dane wyjściowe są typu DXGK_FRAMEBUFFERSAVEAREA i powinny być maksymalnym rozmiarem wymaganym przez sterownik w celu zapisania obszaru rezerw buforu ramki podczas przejścia zasilania.

Dxgkrnl pobiera opłatę za zatwierdzenie na kwotę określoną przez sterownik, aby upewnić się, że zawsze może pobierać strony fizyczne na żądanie. Ta akcja jest wykonywana przez utworzenie unikatowego obiektu sekcji dla każdego adaptera fizycznego, określającego wartość niezerową dla maksymalnego rozmiaru.

Maksymalny rozmiar zgłaszany przez sterownik musi być wielokrotnym PAGE_SIZE.

Przesyłanie danych do buforu ramowego i z powrotem można przeprowadzać w dowolnym momencie wybranym przez sterownik. Aby ułatwić transfer, Dxgkrnl udostępnia cztery ostatnie wywołania zwrotne w poprzedniej tabeli dla sterownika trybu jądra. Te wywołania zwrotne mogą służyć do mapowania odpowiednich części obiektu sekcji utworzonego podczas inicjacji adaptera.

Sterownik musi zawsze podać element hAdapter dla urządzenia prowadzącego w łańcuchu LDA, gdy wywołuje te cztery funkcje wywołania zwrotnego.

Sterownik ma dwie opcje implementacji rezerwacji bufora ramki:

  1. (Preferowana metoda) Sterownik powinien przydzielić miejsce na kartę fizyczną przy użyciu wywołania DXGKDDI_QUERYADAPTERINFO , aby określić ilość miejsca potrzebnego dla karty. W momencie zmiany zasilania sterownik powinien zapisywać lub przywracać pamięć dla jednego fizycznego adaptera jednocześnie. Ta pamięć jest podzielona na wiele obiektów sekcji, po jednym na każdy fizyczny adapter.

  2. Opcjonalnie sterownik może zapisywać lub przywracać wszystkie dane w jednym udostępnionym obiekcie sekcji. Tę akcję można wykonać, określając pojedynczy duży maksymalny rozmiar w wywołaniu DXGKDDI_QUERYADAPTERINFO dla fizycznego adaptera 0, a następnie wartość zero dla wszystkich innych fizycznych adapterów. Sterownik może następnie przypiąć cały obiekt sekcji raz do użycia we wszystkich operacjach zapisywania/przywracania dla wszystkich kart fizycznych. Ta metoda ma podstawową wadę, która wymaga zablokowania większej ilości pamięci jednocześnie, ponieważ nie obsługuje przypinania tylko podgrupy pamięci do formatu MDL. W związku z tym ta operacja jest bardziej skłonna do niepowodzenia pod presją pamięci. Sterownik powinien również mapować strony w MDL na GPU z użyciem poprawnych przesunięć stron.

Sterownik powinien wykonać następujące zadania, aby wykonać transfer do lub z buforu ramki:

  • Podczas inicjowania sterownik powinien wstępnie przydzielić mały fragment pamięci dostępnej dla GPU przy użyciu jednej z rutyn alokacji. Ta pamięć jest używana do zapewnienia ciągłości działania, jeśli nie można jednocześnie zamapować lub zablokować całego obiektu sekcji.

  • W momencie przejścia zasilania sterownik powinien najpierw wywołać Dxgkrnl , aby przypiąć bufor ramki. Po powodzeniu, Dxgkrnl udostępnia sterownikowi MDL do zablokowanych stron mapowanych na IOMMU. Sterownik może następnie wykonać transfer bezpośrednio do tych stron w sposób najbardziej wydajny dla sprzętu. Następnie sterownik powinien wywołać polecenie Dxgkrnl , aby odblokować/cofnąć mapowanie pamięci.

  • Jeśli Dxgkrnl nie może jednocześnie przypiąć całego buforu ramki, sterownik musi podjąć próbę kontynuowania operacji przy użyciu wstępnie przydzielonego buforu podczas inicjowania. W takim przypadku sterownik wykonuje transfer w małych fragmentach. Podczas każdej iteracji transferu (dla każdego fragmentu) sterownik musi poprosić Dxgkrnl o podanie zamapowanego zakresu obiektu sekcji, do którego mogą skopiować wyniki. Sterownik musi następnie odmapować część obiektu sekcji przed następną iteracją.

Poniższy pseudokod to przykładowa implementacja tego algorytmu.


#define SMALL_SIZE (PAGE_SIZE)

PMDL PHYSICAL_ADAPTER::m_SmallMdl;
PMDL PHYSICAL_ADAPTER::m_PinnedMdl;

NTSTATUS PHYSICAL_ADAPTER::Init()
{
    DXGKARGCB_ALLOCATEPAGESFORMDL Args = {};
    Args.TotalBytes = SMALL_SIZE;
    
    // Allocate small buffer up front for forward progress transfers
    Status = DxgkCbAllocatePagesForMdl(SMALL_SIZE, &Args);
    m_SmallMdl = Args.pMdl;

    ...
}

NTSTATUS PHYSICAL_ADAPTER::OnPowerDown()
{    
    Status = DxgkCbPinFrameBufferForSave(&m_pPinnedMdl);
    if(!NT_SUCCESS(Status))
    {
        m_pPinnedMdl = NULL;
    }
    
    if(m_pPinnedMdl != NULL)
    {        
        // Normal GPU copy: frame buffer -> m_pPinnedMdl
        GpuCopyFromFrameBuffer(m_pPinnedMdl, Size);
        DxgkCbUnpinFrameBufferForSave(m_pPinnedMdl);
    }
    else
    {
        SIZE_T Offset = 0;
        while(Offset != TotalSize)
        {
            SIZE_T MappedOffset = Offset;
            PVOID pCpuPointer;
            Status = DxgkCbMapFrameBufferPointer(SMALL_SIZE, &MappedOffset, &pCpuPointer);
            if(!NT_SUCCESS(Status))
            {
                // Driver must handle failure here. Even a 4KB mapping may
                // not succeed. The driver should attempt to cancel the
                // transfer and reset the adapter.
            }
            
            GpuCopyFromFrameBuffer(m_pSmallMdl, SMALL_SIZE);
            
            RtlCopyMemory(pCpuPointer + MappedOffset, m_pSmallCpuPointer, SMALL_SIZE);
            
            DxgkCbUnmapFrameBufferPointer(pCpuPointer);
            Offset += SMALL_SIZE;
        }
    }
}

NTSTATUS PHYSICAL_ADAPTER::OnPowerUp()
{
    Status = DxgkCbPinFrameBufferForSave(&m_pPinnedMdl);
    if(!NT_SUCCESS(Status))
    {
        m_pPinnedMdl = NULL;
    }
    
    if(pPinnedMemory != NULL)
    {
        // Normal GPU copy: m_pPinnedMdl -> frame buffer
        GpuCopyToFrameBuffer(m_pPinnedMdl, Size);
        DxgkCbUnpinFrameBufferForSave(m_pPinnedMdl);
    }
    else
    {
        SIZE_T Offset = 0;
        while(Offset != TotalSize)
        {
            SIZE_T MappedOffset = Offset;
            PVOID pCpuPointer;
            Status = DxgkCbMapFrameBufferPointer(SMALL_SIZE, &MappedOffset, &pCpuPointer);
            if(!NT_SUCCESS(Status))
            {
                // Driver must handle failure here. Even a 4KB mapping may
                // not succeed. The driver should attempt to cancel the
                // transfer and reset the adapter.
            }
                        
            RtlCopyMemory(m_pSmallCpuPointer, pCpuPointer + MappedOffset, SMALL_SIZE);
            
            GpuCopyToFrameBuffer(m_pSmallMdl, SMALL_SIZE);

            DxgkCbUnmapFrameBufferPointer(pCpuPointer);
            Offset += SMALL_SIZE;
        }
    }
}

Pamięć zarezerwowana dla sprzętu

Program VidMm mapuje pamięć zarezerwowaną sprzętu, zanim urządzenie zostanie dołączone do IOMMU.

Program VidMm automatycznie obsługuje dowolną pamięć zgłoszoną jako segment z flagą PopulatedFromSystemMemory . VidMm mapuje tę pamięć na podstawie podanego adresu fizycznego.

W przypadku regionów zarezerwowanych sprzętu prywatnego, które nie są uwidocznione przez segmenty, VidMm wykonuje wywołanie DXGKDDI_QUERYADAPTERINFO w celu wykonywania zapytań dotyczących zakresów przez sterownik. Podane zakresy nie mogą nakładać się na żadne regiony pamięci używane przez menedżera pamięci NTOS; VidMm sprawdza, czy takie przecięcia nie występują. Ta weryfikacja gwarantuje, że sterownik nie może przypadkowo zgłosić regionu pamięci fizycznej spoza zarezerwowanego zakresu, co narusza gwarancje bezpieczeństwa funkcji.

Wywołanie zapytania jest wykonywane jednorazowo w celu wykonania zapytania o liczbę wymaganych zakresów i następuje drugie wywołanie w celu wypełnienia tablicy zakresów zarezerwowanych.

Testowanie

Jeśli sterownik zdecyduje się na tę funkcję, test HLK skanuje tabelę importu sterownika, aby upewnić się, że żadne z następujących funkcji mm nie są wywoływane:

  • MmAllocateContiguousMemory
  • MmAllocateContiguousMemorySpecifyCache
  • MmFreeContiguousMemory
  • MmAllocatePagesForMdl
  • MmAllocatePagesForMdlEx
  • MmFreePagesFromMdl
  • MmProbeAndLockPages

Wszystkie alokacje dla pamięci ciągłej i struktur MDL powinny zamiast tego odbywać się przez interfejs wywołania zwrotnego Dxgkrnl przy użyciu wymienionych funkcji. Sterownik nie powinien również blokować żadnej pamięci. Dxgkrnl zarządza zablokowanymi stronami sterownika. Gdy pamięć zostanie ponownie zamapowana, adres logiczny stron dostarczonych do sterownika może nie być już zgodny z adresami fizycznymi.