Delen via


Gpu-isolatie op basis van IOMMU

Gpu-isolatie op basis van IOMMU is een techniek die wordt gebruikt om systeembeveiliging en stabiliteit te verbeteren door te beheren hoe GPU's toegang hebben tot systeemgeheugen. In dit artikel wordt de op IOMMU gebaseerde GPU-isolatiefunctie van WDDM beschreven voor IOMMU-compatibele apparaten en hoe ontwikkelaars deze kunnen implementeren in hun grafische stuurprogramma's.

Deze functie is beschikbaar vanaf Windows 10 versie 1803 (WDDM 2.4). Zie IOMMU DMA remapping voor meer recente IOMMU-updates.

Overzicht

Met GPU-isolatie op basis van IOMMU kan Dxgkrnl de toegang tot systeemgeheugen van de GPU beperken door gebruik te maken van IOMMU-hardware. Het besturingssysteem kan logische adressen opgeven in plaats van fysieke adressen. Deze logische adressen kunnen worden gebruikt om de toegang van het apparaat tot systeemgeheugen te beperken tot alleen het geheugen dat het kan openen. Dit doet u door ervoor te zorgen dat de IOMMU geheugentoegang via PCIe vertaalt naar geldige en toegankelijke fysieke pagina's.

Als het logische adres dat door het apparaat wordt geopend, niet geldig is, kan het apparaat geen toegang krijgen tot het fysieke geheugen. Deze beperking voorkomt een reeks aanvallen waarmee een aanvaller toegang kan krijgen tot fysiek geheugen via een gecompromitteerd hardwareapparaat. Zonder dit kunnen aanvallers de inhoud van het systeemgeheugen lezen die niet nodig zijn voor de werking van het apparaat.

Deze functie is standaard alleen ingeschakeld voor pc's waarop Windows Defender Application Guard is ingeschakeld voor Microsoft Edge (dat wil gezegd containervirtualisatie).

Voor ontwikkelingsdoeleinden wordt de werkelijke IOMMU-functionaliteit voor opnieuw toewijzen ingeschakeld of uitgeschakeld via de volgende registersleutel:

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.

Als deze functie is ingeschakeld, wordt de IOMMU kort nadat de adapter is gestart ingeschakeld. Alle toewijzingen van stuurprogramma's die vóór deze tijd zijn gemaakt, worden in kaart gebracht wanneer deze wordt ingeschakeld.

Als de snelheidsfaseringssleutel 14688597 is ingesteld als ingeschakeld, wordt de IOMMU geactiveerd wanneer een beveiligde virtuele machine wordt gemaakt. Voorlopig is deze staging-sleutel standaard uitgeschakeld zodat self-hosting zonder de juiste IOMMU-ondersteuning mogelijk is.

Hoewel deze optie is ingeschakeld, mislukt het starten van een beveiligde virtuele machine als het stuurprogramma geen IOMMU-ondersteuning biedt.

Er is momenteel geen manier om de IOMMU uit te schakelen nadat deze is ingeschakeld.

Geheugentoegang

Dxgkrnl zorgt ervoor dat al het geheugen dat toegankelijk is voor de GPU opnieuw wordt toegewezen via de IOMMU om ervoor te zorgen dat dit geheugen toegankelijk is. Het fysieke geheugen waartoe de GPU toegang nodig heeft, kan momenteel worden onderverdeeld in vier categorieën:

  • Stuurprogrammaspecifieke toewijzingen via MmAllocateContiguousMemory- of MmAllocatePagesForMdl-functies (inclusief de SpecifyCache- en uitgebreide variaties) moeten naar de IOMMU worden gemapt voordat de GPU er toegang toe heeft. In plaats van de Mm-API's aan te roepen, biedt Dxgkrnl callbacks aan het kernelmodusstuurprogramma om de toewijzing en hermapping in één stap uit te voeren. Elk geheugen dat bedoeld is om door de GPU toegankelijk te zijn, moet deze callbacks doorlopen, anders kan de GPU geen toegang tot dit geheugen krijgen.

  • Al het geheugen dat wordt geopend door de GPU tijdens pagingbewerkingen, of toegewezen via de GpuMmu, moet worden toegewezen aan de IOMMU. Dit proces is volledig intern voor de Video Memory Manager (VidMm), een subcomponent van Dxgkrnl. VidMm verwerkt het toewijzen en ongedaan maken van de logische adresruimte wanneer verwacht wordt dat de GPU toegang heeft tot dit geheugen, waaronder:

  • Toewijzing van de geheugenopslag van een toewijzing voor een van de alternatieven:

    • De volledige duur tijdens een overdracht naar of van VRAM.
    • De hele tijd dat de backing store is toegewezen aan systeemgeheugen- of aperturesegmenten.
  • Bewaakte omheiningen in kaart brengen en uit kaart brengen.

  • Tijdens stroomovergangen moet het stuurprogramma mogelijk delen van hardware-gereserveerd geheugen besparen. Om deze situatie af te handelen, biedt Dxgkrnl een mechanisme voor de bestuurder om op te geven hoeveel geheugen er vooraf is om deze gegevens op te slaan. De exacte hoeveelheid geheugen die het stuurprogramma nodig heeft, kan dynamisch worden gewijzigd. Dat gezegd hebbende, dxgkrnl neemt een commit-lasten in rekening op de bovengrenswaarde op het moment dat de adapter wordt geïnitialiseerd om te waarborgen dat fysieke pagina's beschikbaar zijn wanneer dat nodig is. Dxgkrnl is verantwoordelijk voor het garanderen dat dit geheugen is vergrendeld en toegewezen aan de IOMMU voor de overdracht tijdens stroomovergangen.

  • Voor alle gereserveerde hardwarebronnen zorgt VidMm ervoor dat de IOMMU-resources correct worden toegewezen op het moment dat het apparaat is gekoppeld aan de IOMMU. Dit omvat geheugen gerapporteerd door geheugensegmenten die zijn gerapporteerd met PopulatedFromSystemMemory. Voor gereserveerd geheugen (bijvoorbeeld firmware/BIOD gereserveerd) dat niet beschikbaar is via VidMm-segmenten, maakt Dxgkrnl een DXGKDDI_QUERYADAPTERINFO aanroep om alle gereserveerde geheugenbereiken op te vragen die het stuurprogramma van tevoren moet toewijzen. Zie Gereserveerd geheugen voor hardware voor meer informatie.

Domeintoewijzing

Tijdens de initialisatie van de hardware maakt Dxgkrnl een domein voor elke logische adapter op het systeem. Het domein beheert de logische adresruimte en houdt paginatabellen en andere benodigde gegevens voor de toewijzingen bij. Alle fysieke adapters in één logische adapter behoren tot hetzelfde domein. Dxgkrnl houdt alle toegewezen fysieke geheugen bij via de nieuwe callbackroutines voor toewijzing en alle geheugen dat door VidMm zelf is toegewezen.

Het domein wordt gekoppeld aan het apparaat wanneer er voor het eerst een beveiligde virtuele machine wordt gemaakt, of kort nadat het apparaat is gestart als de bovenstaande registersleutel wordt gebruikt.

Exclusieve toegang

IOMMU domein koppelen en loskoppelen is snel, maar is nochtans niet atomisch. Aangezien het niet atomair is, is het niet gegarandeerd dat een transactie die via PCIe plaatsvindt correct wordt vertaald tijdens de overschakeling naar een IOMMU-domein met verschillende toewijzingen.

Om deze situatie af te handelen, moet een KMD vanaf Windows 10 versie 1803 (WDDM 2.4) het volgende DDI-paar implementeren voor Dxgkrnl om aan te roepen:

Deze DDIs vormen een begin-/eindkoppeling, waarbij Dxgkrnl vraagt dat de hardware stil is over de bus. Het stuurprogramma moet ervoor zorgen dat de hardware stil is wanneer het apparaat wordt overgeschakeld naar een nieuw IOMMU-domein. Dat wil gezegd, het stuurprogramma moet ervoor zorgen dat het niet leest of schrijft naar het systeemgeheugen van het apparaat tussen deze twee aanroepen.

Tussen deze twee aanroepen maakt Dxgkrnl de volgende garanties:

  • De scheduler wordt opgeschort. Alle actieve workloads worden leeggemaakt en er worden geen nieuwe workloads verzonden naar of gepland op de hardware.
  • Er worden geen andere DDI-aanroepen gedaan.

Als onderdeel van deze aanroepen kan het stuurprogramma ervoor kiezen om onderbrekingen (inclusief Vsync-interrupts) uit te schakelen en te onderdrukken tijdens de exclusieve toegang, zelfs zonder expliciete melding van het besturingssysteem.

Dxgkrnl zorgt ervoor dat alle geplande werkzaamheden op de hardware zijn voltooid, en betreedt vervolgens deze exclusieve toegangsregio. Gedurende deze tijd wijst Dxgkrnl het domein toe aan het apparaat. Dxgkrnl doet geen aanvragen van het stuurprogramma of de hardware tussen deze aanroepen.

DDI-wijzigingen

De volgende DDI-wijzigingen zijn aangebracht ter ondersteuning van GPU-isolatie op basis van IOMMU:

Geheugentoewijzing en mapping naar IOMMU

Dxgkrnl biedt de eerste zes callbacks in de voorgaande tabel aan het kernelmodus-stuurprogramma om het geheugen toe te wijzen en het opnieuw toe te wijzen aan de logische adresruimte van de IOMMU. Deze callback-functies bootsen de routines na die worden geleverd door de Mm-API-interface. Ze bieden het stuurprogramma MDL's of aanwijzers die het geheugen beschrijven dat ook is toegewezen aan de IOMMU. Deze MDL's blijven fysieke pagina's beschrijven, maar de logische adresruimte van de IOMMU is toegewezen aan hetzelfde adres.

Dxgkrnl houdt aanvragen voor deze callbacks bij om ervoor te zorgen dat er geen lekken door de chauffeur zijn. De callbacks voor toewijzing bieden een andere ingang als onderdeel van de uitvoer die moet worden teruggegeven aan de respectieve gratis callback.

Voor geheugen dat niet kan worden toegewezen via een van de gegeven allocatiecallbacks, wordt de DXGKCB_MAPMDLTOIOMMU callback verstrekt om door stuurprogramma beheerde MDL's te traceren en gebruiken met de IOMMU. Een stuurprogramma dat gebruikmaakt van deze callback is verantwoordelijk om ervoor te zorgen dat de levensduur van de MDL de bijbehorende unmap-aanroep overschrijdt. Anders heeft de aanroep unmap niet-gedefinieerd gedrag. Dit niet-gedefinieerde gedrag kan leiden tot gecompromitteerde beveiliging van de MDL-pagina's die door mm opnieuw worden gebruikt wanneer ze niet zijn toegewezen.

VidMm beheert automatisch alle toewijzingen die het maakt (bijvoorbeeld DdiCreateAllocationCb, bewaakte hekken, enzovoort) in het systeemgeheugen. Het stuurprogramma hoeft niets te doen om deze toewijzingen te laten werken.

Reservering framebuffer

Voor stuurprogramma's die gereserveerde delen van de framebuffer moeten opslaan in het systeemgeheugen tijdens stroomstatusovergangen, legt Dxgkrnl een geheugentoezegging op wanneer de adapter wordt geïnitialiseerd. Als het stuurprogramma IOMMU-isolatieondersteuning rapporteert, geeft Dxgkrnl een aanroep uit naar DXGKDDI_QUERYADAPTERINFO met het volgende onmiddellijk na het uitvoeren van query's op de fysieke adapterlimieten:

  • Type is DXGKQAITYPE_FRAMEBUFFERSAVESIZE
  • De invoer is van het type UINT, de fysieke adapterindex.
  • De uitvoer is van het type DXGK_FRAMEBUFFERSAVEAREA en moet de maximale grootte zijn die het stuurprogramma nodig heeft om het reservegebied van de framebuffer op te slaan tijdens stroomovergangen.

Dxgkrnl brengt kosten in rekening voor het bedrag dat door de bestuurder is opgegeven om ervoor te zorgen dat deze op verzoek altijd fysieke pagina's kan krijgen. Deze actie wordt uitgevoerd door een uniek sectieobject te maken voor elke fysieke adapter die een niet-nulwaarde opgeeft voor de maximale grootte.

De maximale grootte die door de bestuurder wordt gerapporteerd, moet een veelvoud van PAGE_SIZE zijn.

Het overdragen naar en van de framebuffer kan op een tijdstip naar keuze van de driver worden uitgevoerd. Voor hulp bij de overdracht biedt Dxgkrnl de laatste vier callbacks in de voorgaande tabel aan het stuurprogramma voor de kernelmodus. Deze callbacks kunnen worden gebruikt om de juiste gedeelten van het object van de sectie in kaart te brengen dat is gemaakt toen de adapter werd geïnitialiseerd.

Het stuurprogramma moet altijd de hAdapter voor het hoofdapparaat in een LDA-keten leveren wanneer deze vier callbackfuncties worden aangeroepen.

Het stuurprogramma heeft twee opties voor het implementeren van de framebufferreservering:

  1. (Voorkeursmethode) Het stuurprogramma moet ruimte per fysieke adapter toewijzen met behulp van de DXGKDDI_QUERYADAPTERINFO aanroep om de hoeveelheid opslagruimte op te geven die per adapter nodig is. Op het moment van de voedingstransitie moet de driver het geheugen één fysieke adapter tegelijk opslaan of herstellen. Dit geheugen wordt verdeeld over meerdere sectieobjecten, één per fysieke adapter.

  2. Het stuurprogramma kan eventueel alle gegevens opslaan of herstellen in één gedeeld sectieobject. Deze actie kan worden uitgevoerd door één grote maximale grootte op te geven in de DXGKDDI_QUERYADAPTERINFO oproep voor fysieke adapter 0 en vervolgens een nulwaarde voor alle andere fysieke adapters. Het stuurprogramma kan vervolgens het hele sectieobject één keer vastmaken voor gebruik in alle bewerkingen voor opslaan/herstellen, voor alle fysieke adapters. Deze methode heeft het belangrijkste nadeel dat het vergrendelen van een grotere hoeveelheid geheugen tegelijk vereist, omdat het niet ondersteunt het vastmaken van alleen een subbereik van het geheugen in een MDL. Als gevolg hiervan zal deze bewerking waarschijnlijker mislukken onder geheugendruk. Er wordt ook verwacht dat het stuurprogramma de pagina's in de MDL aan de GPU toewijst met de juiste pagina-offsets.

Het stuurprogramma moet de volgende taken uitvoeren om een overdracht naar of van de framebuffer te voltooien:

  • Tijdens de initialisatie moet de driver een klein deel van GPU-geheugen vooraf toewijzen met behulp van een van de toewijzings-callbackroutines. Dit geheugen wordt gebruikt om de vooruitgang te garanderen als het hele sectieobject niet tegelijk kan worden toegekend/vergrendeld.

  • Op het moment van de machtsoverdracht moet de bestuurder eerst Dxgkrnl aanroepen om de frame buffer vast te maken. Bij succes biedt Dxgkrnl het stuurprogramma een MDL voor gelockte pagina's die zijn gemapt naar de IOMMU. Het stuurprogramma kan vervolgens een overdracht rechtstreeks naar deze pagina's uitvoeren op de manier die het meest efficiënt is voor de hardware. Het stuurprogramma moet vervolgens Dxgkrnl aanroepen om het geheugen te ontgrendelen/los te maken.

  • Als Dxgkrnl de volledige framebuffer niet tegelijk kan vastmaken, moet het stuurprogramma proberen vooruit te gaan met behulp van de vooraf toegewezen buffer die tijdens de initialisatie is toegewezen. In dit geval voert de chauffeur de overdracht uit in kleine segmenten. Tijdens elke iteratie van de overdracht (voor elk segment) moet de bestuurder Dxgkrnl vragen om een toegewezen bereik van het sectieobject op te geven waarnaar ze de resultaten kunnen kopiëren. Het stuurprogramma moet vervolgens het gedeelte van het sectieobject ongedaan maken voor de volgende iteratie.

De volgende pseudocode is een voorbeeld van de implementatie van dit algoritme.


#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;
        }
    }
}

Gereserveerd geheugen voor hardware

VidMm mapt hardware-gereserveerd geheugen voordat het apparaat aan de IOMMU wordt gekoppeld.

VidMm verwerkt automatisch elk geheugen dat wordt gerapporteerd als een segment met de vlag PopulatedFromSystemMemory . VidMm kaart dit geheugen op basis van het opgegeven fysieke adres.

Voor gereserveerde privéhardwareregio's die niet worden weergegeven door segmenten, voert VidMm een DXGKDDI_QUERYADAPTERINFO aanroep uit om de bereiken op te vragen die door het stuurprogramma worden beheerd. De opgegeven bereiken mogen geen gebieden van geheugen overlappen die door de NTOS-geheugenbeheerder worden gebruikt; VidMm valideert dat er geen snijpunten optreden. Deze validatie zorgt ervoor dat het stuurprogramma niet per ongeluk een regio van fysiek geheugen kan rapporteren die zich buiten het gereserveerde bereik bevindt, wat de beveiligingsgaranties van de functie zou schenden.

De queryoproep wordt één keer uitgevoerd om een query uit te voeren op het aantal benodigde bereiken en wordt gevolgd door een tweede aanroep om de matrix met gereserveerde bereiken te vullen.

Testen

Als het stuurprogramma zich voor deze functie aanmeldt, scant een HLK-test de importtabel van het stuurprogramma om ervoor te zorgen dat geen van de volgende mm-functies wordt aangeroepen:

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

Alle geheugentoewijzingen voor aaneengesloten geheugen en MDL's moeten in plaats daarvan de callback-interface van Dxgkrnl doorlopen met behulp van de vermelde functies. Het stuurprogramma mag ook geen geheugen vergrendelen. Dxgkrnl beheert vergrendelde pagina's voor de chauffeur. Zodra het geheugen opnieuw is toegewezen, komt het logische adres van de pagina's die aan het stuurprogramma worden verstrekt mogelijk niet meer overeen met de fysieke adressen.