Isolamento GPU basato su IOMMU

Questa pagina descrive la funzionalità di isolamento GPU basata su IOMMU per i dispositivi compatibili con IOMMU, introdotta in Windows 10 versione 1803 (WDDM 2.4). Per aggiornamenti più recenti di IOMMU DMA, vedere IOMMU Remapping .

Panoramica

L'isolamento gpu basato su IOMMU consente a Dxgkrnl di limitare l'accesso alla memoria di sistema dalla GPU usando l'hardware IOMMU. Il sistema operativo può fornire indirizzi logici anziché indirizzi fisici. Questi indirizzi logici possono essere usati per limitare l'accesso al dispositivo alla memoria di sistema solo alla memoria che deve essere in grado di accedere. Ciò avviene assicurando che L'IOMMU converte gli accessi alla memoria tramite PCIe in pagine fisiche valide e accessibili.

Se l'indirizzo logico a cui si accede dal dispositivo non è valido, il dispositivo non può accedere alla memoria fisica. Questa restrizione impedisce un intervallo di exploit che consentono a un utente malintenzionato di accedere alla memoria fisica tramite un dispositivo hardware compromesso e di leggere il contenuto della memoria di sistema che non è necessario per l'operazione del dispositivo.

A partire da Windows 10 versione 1803, per impostazione predefinita questa funzionalità è abilitata solo per i PC in cui Windows Defender Application Guard è abilitato per Microsoft Edge, ovvero la virtualizzazione dei contenitori.

A scopo di sviluppo, la funzionalità di remapping IOMMU effettiva è abilitata o disabilitata tramite la chiave del Registro di sistema seguente:

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.

Se questa funzionalità è abilitata, L'IOMMU viene abilitato poco dopo l'avvio dell'adattatore. Tutte le allocazioni dei driver effettuate prima di questa ora vengono mappate quando viene abilitato.

Inoltre, se la chiave di gestione temporanea velocità 14688597 è impostata come abilitata, L'IOMMU viene attivato quando viene creata una macchina virtuale sicura. Per il momento, questa chiave di gestione temporanea è disabilitata per impostazione predefinita per consentire l'self-hosting senza il supporto di IOMMU appropriato.

Durante l'abilitazione, l'avvio di una macchina virtuale sicura ha esito negativo se il driver non fornisce supporto IOMMU.

Attualmente non è possibile disabilitare l'IOMMU dopo che è stata abilitata.

Accesso alla memoria

Dxgkrnl garantisce che tutta la memoria accessibile dalla GPU venga ricompressa tramite IOMMU per assicurarsi che questa memoria sia accessibile. La memoria fisica a cui deve accedere la GPU può attualmente essere suddivisa in quattro categorie:

  • Le allocazioni specifiche del driver effettuate tramite le funzioni MmAllocateContiguousMemory- o MmAllocatePagesForMdl-style (incluse le varianti SpecificaCache e estese) devono essere mappate all'IOMMU prima dell'accesso alla GPU. Anziché chiamare le API Mm , Dxgkrnl fornisce callback al driver in modalità kernel per consentire l'allocazione e il remapping in un unico passaggio. Qualsiasi memoria destinata a essere accessibile dalla GPU deve passare attraverso questi callback o la GPU non è in grado di accedere a questa memoria.

  • Tutte le memoria accessibili dalla GPU durante le operazioni di paging o mappate tramite GpuMmu devono essere mappate all'IOMMU. Questo processo è interamente interno a Video Memory Manager (VidMm), che è un sottocomponente di Dxgkrnl. VidMm gestisce il mapping e l'annullamento del mapping dello spazio degli indirizzi logici ogni volta che la GPU deve accedere a questa memoria, tra cui:

  • Mapping dell'archivio di backup di un'allocazione per l'intera durata durante un trasferimento verso o da VRAM o dall'intera durata del mapping alla memoria di sistema o ai segmenti di apertura.

  • Mapping e annullamento del mapping delle recinzioni monitorate.

  • Durante le transizioni di alimentazione, il driver potrebbe dover salvare parti di memoria riservata hardware. Per gestire questa situazione, Dxgkrnl fornisce un meccanismo per il driver per specificare la quantità di memoria anteriore per archiviare questi dati. La quantità esatta di memoria richiesta dal driver può cambiare dinamicamente, ma Dxgkrnl accetta un addebito di commit sul limite superiore al momento in cui l'adattatore viene inizializzato per garantire che le pagine fisiche possano essere ottenute quando necessario. Dxgkrnl è responsabile della garanzia che questa memoria sia bloccata e mappata all'IOMMU per il trasferimento durante le transizioni di alimentazione.

  • Per tutte le risorse riservate hardware, VidMm garantisce che esegue correttamente il mapping delle risorse IOMMU entro il momento in cui il dispositivo è collegato all'IOMMU. Ciò include la memoria segnalata dai segmenti di memoria segnalati con PopolamentoFromSystemMemory. Per la memoria riservata (ad esempio, firmware/BIOD riservata) non esposta tramite segmenti VidMm, Dxgkrnl esegue una chiamata DXGKDDI_QUERYADAPTERINFO per eseguire query su tutti gli intervalli di memoria riservati necessari per il driver in anticipo. Per informazioni dettagliate, vedere Memoria riservata hardware .

Assegnazione di dominio

Durante l'inizializzazione dell'hardware, Dxgkrnl crea un dominio per ogni scheda logica nel sistema. Il dominio gestisce lo spazio indirizzi logico e tiene traccia delle tabelle di pagina e altri dati necessari per i mapping. Tutti gli adattatori fisici in una singola scheda logica appartengono allo stesso dominio. Dxgkrnl tiene traccia di tutta la memoria fisica mappata attraverso le nuove routine di callback di allocazione e qualsiasi memoria allocata da VidMm stessa.

Il dominio verrà collegato al dispositivo la prima volta che viene creata una macchina virtuale sicura o poco dopo l'avvio del dispositivo se viene usata la chiave del Registro di sistema precedente.

Accesso esclusivo

Il collegamento e il scollegamento del dominio IOMMU è estremamente veloce, ma non è tuttavia attualmente atomico. Ciò significa che una transazione emessa su PCIe non è garantita la conversione corretta durante lo scambio con un dominio IOMMU con mapping diversi.

Per gestire questa situazione, a partire da Windows 10 versione 1803 (WDDM 2.4), un KMD deve implementare la coppia DDI seguente per Dxgkrnl per chiamare:

Queste DDI formano un'associazione iniziale/finale, in cui Dxgkrnl richiede che l'hardware sia invisibile sul bus. Il driver deve assicurarsi che l'hardware sia invisibile ogni volta che il dispositivo passa a un nuovo dominio IOMMU. Ovvero, il driver deve assicurarsi che non venga letto o scritto nella memoria di sistema dal dispositivo tra queste due chiamate.

Tra queste due chiamate , Dxgkrnl garantisce le garanzie seguenti:

  • L'utilità di pianificazione viene sospesa. Tutti i carichi di lavoro attivi vengono scaricati e non vengono inviati nuovi carichi di lavoro o pianificati nell'hardware.
  • Non vengono effettuate altre chiamate DDI.

Come parte di queste chiamate, il driver può scegliere di disabilitare e eliminare gli interruzioni (inclusi gli interruzioni Vsync) per la durata dell'accesso esclusivo, anche senza notifica esplicita dal sistema operativo.

Dxgkrnl garantisce che qualsiasi lavoro in sospeso pianificato nell'hardware venga completato e quindi entra in questa area di accesso esclusiva. Durante questo periodo , Dxgkrnl assegna il dominio al dispositivo. Dxgkrnl non effettua richieste del driver o dell'hardware tra queste chiamate.

Modifiche DDI

Le modifiche DDI seguenti sono state apportate per supportare l'isolamento GPU basato su IOMMU:

Allocazione della memoria e mapping a IOMMU

Dxgkrnl fornisce i primi sei callback nella tabella precedente al driver in modalità kernel per consentire l'allocazione della memoria e il mapping allo spazio indirizzi logici di IOMMU. Queste funzioni di callback simulano le routine fornite dall'interfaccia API Mm . Forniscono al driver mdls o puntatori che descrivono la memoria mappata anche all'IOMMU. Questi MDL continuano a descrivere le pagine fisiche, ma lo spazio indirizzi logico di IOMMU viene mappato nello stesso indirizzo.

Dxgkrnl tiene traccia delle richieste a questi callback per garantire che non ci siano perdite dal conducente. I callback di allocazione forniscono un handle aggiuntivo come parte dell'output che deve essere restituito al rispettivo callback gratuito.

Per la memoria che non può essere allocata tramite uno dei callback di allocazione forniti, viene fornito il callback DXGKCB_MAPMDLTOIOMMU per consentire il rilevamento e l'uso di MDLs gestiti dal driver con IOMMU. Un driver che usa questo callback è responsabile di garantire che la durata del file MDL superi la chiamata unmap corrispondente. In caso contrario, la chiamata non mappa ha un comportamento indefinito che potrebbe causare una compromissione della sicurezza delle pagine dal file MDL che vengono riutilizzate da Mm al momento in cui non vengono mappate.

VidMm gestisce automaticamente tutte le allocazioni create (ad esempio, DdiCreateAllocationCb, recinzioni monitorate e così via) nella memoria di sistema. Il driver non deve eseguire alcuna operazione per eseguire queste allocazioni.

Prenotazione del buffer di frame

Per i driver che devono salvare parti riservate del buffer dei fotogrammi nella memoria di sistema durante le transizioni di alimentazione, Dxgkrnl assume un addebito di commit sulla memoria richiesta quando l'adattatore viene inizializzato. Se il driver segnala il supporto per l'isolamento IOMMU, Dxgkrnl emetterà una chiamata a DXGKDDI_QUERYADAPTERINFO con il codice seguente subito dopo aver eseguito una query sui limiti dell'adattatore fisico:

  • Il tipo è DXGKQAITYPE_FRAMEBUFFERSAVESIZE
  • L'input è di tipo UINT, ovvero l'indice dell'adattatore fisico.
  • L'output è del tipo DXGK_FRAMEBUFFERSAVEAREA e deve essere la dimensione massima richiesta dal driver per salvare l'area di riserva del buffer dei fotogrammi durante le transizioni di alimentazione.

Dxgkrnl prende un addebito di commit sull'importo specificato dal driver per garantire che possa sempre ottenere pagine fisiche su richiesta. Questa azione viene eseguita creando un oggetto sezione univoco per ogni adattatore fisico che specifica un valore diverso da zero per le dimensioni massime.

Le dimensioni massime segnalate dal driver devono essere un multiplo di PAGE_SIZE.

L'esecuzione del trasferimento da e verso il buffer dei frame può essere eseguita alla volta della scelta del driver. Per facilitare il trasferimento, Dxgkrnl fornisce gli ultimi quattro callback nella tabella precedente al driver in modalità kernel. Questi callback possono essere utilizzati per eseguire il mapping delle parti appropriate dell'oggetto sezione creato durante l'inizializzazione dell'adattatore.

Il driver deve sempre fornire hAdapter per il dispositivo master/lead in una catena LDA quando chiama queste quattro funzioni di callback.

Il driver offre due opzioni per implementare la prenotazione del buffer dei frame:

  1. (Metodo preferito) Il driver deve allocare spazio per ogni scheda fisica usando la chiamata DXGKDDI_QUERYADAPTERINFO precedente per specificare la quantità di spazio di archiviazione necessaria per ogni scheda. Al momento della transizione della potenza, il driver deve salvare o ripristinare la memoria una scheda fisica alla volta. Questa memoria è suddivisa tra più oggetti sezione, uno per ogni adattatore fisico.

  2. Facoltativamente, il driver può salvare o ripristinare tutti i dati in un singolo oggetto sezione condivisa. Questa azione può essere eseguita specificando una singola dimensione massima elevata nella DXGKDDI_QUERYADAPTERINFO chiamare per l'adattatore fisico 0 e quindi un valore zero per tutti gli altri adattatori fisici. Il driver può quindi aggiungere l'intero oggetto sezione una volta per l'uso in tutte le operazioni di salvataggio/ripristino, per tutte le schede fisiche. Questo metodo presenta lo svantaggio principale che richiede il blocco di una quantità maggiore di memoria contemporaneamente, perché non supporta l'aggiunta solo di un sottoinsieme della memoria in un MDL. Di conseguenza, è più probabile che questa operazione abbia esito negativo sotto pressione sulla memoria. Il driver dovrebbe anche eseguire il mapping delle pagine nel file MDL alla GPU usando gli offset di pagina corretti.

Il driver deve eseguire le attività seguenti per completare un trasferimento da o verso il buffer dei frame:

  • Durante l'inizializzazione, il driver deve preallocare un piccolo blocco di memoria accessibile dalla GPU usando una delle routine di callback di allocazione. Questa memoria viene usata per garantire lo stato di avanzamento in avanti se l'intero oggetto sezione non può essere mappato/bloccato contemporaneamente.

  • Al momento della transizione di alimentazione, il driver deve prima chiamare Dxgkrnl per aggiungere il buffer dei fotogrammi. In caso di esito positivo, Dxgkrnl fornisce al driver un MDL per bloccare le pagine mappate all'IOMMU. Il driver può quindi eseguire un trasferimento direttamente a queste pagine in qualsiasi modo sia più efficiente per l'hardware. Il driver dovrebbe quindi chiamare Dxgkrnl per sbloccare/annullare il mapping della memoria.

  • Se Dxgkrnl non è in grado di bloccare l'intero buffer dei fotogrammi contemporaneamente, il driver deve tentare di inoltrare lo stato di avanzamento usando il buffer preallocato allocato durante l'inizializzazione. In questo caso, il driver esegue il trasferimento in piccoli blocchi. Durante ogni iterazione del trasferimento (per ogni blocco), il driver deve chiedere a Dxgkrnl di fornire un intervallo mappato dell'oggetto sezione in cui possono copiare i risultati. Il driver deve quindi annullare il mapping della parte dell'oggetto sezione prima dell'iterazione successiva.

Lo pseudocodice seguente è un'implementazione di esempio di questo algoritmo.


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

Memoria riservata hardware

VidMm esegue il mapping della memoria riservata hardware prima che il dispositivo sia collegato all'IOMMU.

VidMm gestisce automaticamente qualsiasi memoria segnalata come segmento con il flag PopulatedFromSystemMemory . VidMm esegue il mapping di questa memoria in base all'indirizzo fisico specificato.

Per le aree riservate dell'hardware privato non esposte da segmenti, VidMm effettua una chiamata DXGKDDI_QUERYADAPTERINFO per eseguire una query degli intervalli dal driver. Gli intervalli forniti non devono sovrapporsi ad alcuna area di memoria utilizzata dal gestore di memoria NTOS; VidMm verifica che non si verifichino tali intersezioni. Questa convalida garantisce che il driver non possa segnalare accidentalmente un'area di memoria fisica esterna all'intervallo riservato, che viola le garanzie di sicurezza della funzionalità.

La chiamata di query viene eseguita una volta per eseguire una query sul numero di intervalli necessari e viene seguita da una seconda chiamata per popolare la matrice di intervalli riservati.

Test

Se il driver acconsente a questa funzionalità, un test HLK analizza la tabella di importazione del driver per assicurarsi che nessuna delle funzioni Mm seguenti venga chiamata:

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

Tutte le allocazioni di memoria per la memoria contigua e gli MDL devono invece passare attraverso l'interfaccia di callback di Dxgkrnl usando le funzioni elencate. Inoltre, il driver non deve bloccare alcuna memoria. Dxgkrnl gestisce le pagine bloccate per il driver. Dopo il mapping della memoria, l'indirizzo logico delle pagine fornite al driver potrebbe non corrispondere più agli indirizzi fisici.