Sdílet prostřednictvím


Izolace GPU založená na IOMMU

Izolace GPU založená na ioMMU je technika, která se používá k vylepšení zabezpečení a stability systému tím, že spravuje, jak gpu přistupují k systémové paměti. Tento článek popisuje funkci izolace GPU založenou na ioMMU společnosti WDDM pro zařízení podporující ioMMU a způsob, jakým ho můžou vývojáři implementovat do svých grafických ovladačů.

Tato funkce je dostupná od Windows 10 verze 1803 (WDDM 2.4). Nejnovější aktualizace IOMMU najdete v části o Přemapování DMA IOMMU.

Přehled

Izolace GPU založená na ioMMU umožňuje Dxgkrnl omezit přístup k systémové paměti z GPU pomocí hardwaru IOMMU. Operační systém může místo fyzických adres poskytovat logické adresy. Tyto logické adresy lze použít k omezení přístupu zařízení k systémové paměti jenom na paměť, ke které by měla mít přístup. Tím zajistí, že ioMMU přeloží přístup k paměti přes PCIe na platné a přístupné fyzické stránky.

Pokud logická adresa, ke které zařízení přistupuje, není platná, nemůže získat přístup k fyzické paměti. Toto omezení brání rozsahu zneužití, které útočníkovi umožňují získat přístup k fyzické paměti prostřednictvím ohroženého hardwarového zařízení. Bez něj by útočníci mohli číst obsah systémové paměti, které nejsou potřeba pro operaci zařízení.

Ve výchozím nastavení je tato funkce povolená jenom pro počítače, ve kterých je povolená ochrana Application Guard v programu Windows Defender pro Microsoft Edge (to znamená virtualizace kontejnerů).

Pro účely vývoje je skutečná funkce opětovného mapování ioMMU povolená nebo zakázaná prostřednictvím následujícího klíče registru:

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.

Pokud je tato funkce povolená, je ioMMU krátce po spuštění adaptéru povolená. Veškerá přidělení ovladačů provedená před touto dobou budou mapována, když budou povolena.

Kromě toho, pokud je klíč řazení rychlosti 14688597 nastavený jako povolený, aktivuje se při vytvoření zabezpečeného virtuálního počítače IOMMU. Prozatím je tento přípravný klíč ve výchozím nastavení zakázaný, aby umožňoval samoobslužné hostování bez správné podpory ioMMU.

Když je tato možnost povolená, spuštění zabezpečeného virtuálního počítače selže, pokud ovladač neposkytuje podporu ioMMU.

V současné době neexistuje způsob, jak ioMMU po povolení zakázat.

Přístup k paměti

Dxgkrnl zajišťuje, aby se veškerá paměť přístupná gpu přemapovala prostřednictvím ioMMU, aby byla zajištěna dostupnost této paměti. Fyzickou paměť, ke které gpu potřebuje přístup, je v současné době rozdělená do čtyř kategorií:

  • Přidělení specifická pro ovladače provedená prostřednictvím funkcí MmAllocateContiguousMemory nebo MmAllocatePagesForMdl (včetně SpecifyCache a rozšířených variant) musí být mapována na IOMMU předtím, než k nim přistupuje GPU. Místo volání rozhraní MM API poskytuje Dxgkrnl zpětná volání pro ovladač režimu jádra, aby umožnil přidělení a opětovné mapování v jednom kroku. Veškerá paměť, která má být přístupná pomocí GPU, musí projít těmito zpětnými voláními nebo gpu nemá přístup k této paměti.

  • Veškerá paměť přístupná GPU během operací stránkování nebo namapovaná přes GpuMmu musí být namapována na ioMMU. Tento proces je zcela interní pro Správce paměti videa (VidMm), což je dílčí součást Dxgkrnl. VidMm zpracovává mapování a odmapování logického adresního prostoru kdykoli se očekává, že GPU bude přistupovat k této paměti, včetně:

  • Mapování záložního úložiště přidělení pro:

    • Celá doba trvání přenosu do nebo z VRAM.
    • Po celou dobu, kdy je záložní úložiště mapováno na systémovou paměť nebo segmenty výřezu.
  • Mapování a demapování monitorovaných plotů

  • Během přechodu napájení může ovladač potřebovat uložit části hardwarově rezervované paměti. Pro zvládnutí této situace dxgkrnl poskytuje mechanismus pro ovladač, který určuje, kolik paměti je předem k uložení těchto dat. Přesné množství paměti, které ovladač vyžaduje, se může dynamicky měnit. To znamená, že Dxgkrnl uplatní rezervaci závazku na horní hranici v době inicializace adaptéru, aby bylo zajištěno, že fyzické stránky lze získat, když je to potřeba. Dxgkrnl zodpovídá za zajištění uzamčení a namapování této paměti na ioMMU pro přenos během přechodu napájení.

  • Nástroj VidMm pro všechny hardwarově rezervované prostředky zajistí, že správně namapuje prostředky IOMMU ve chvíli, kdy je zařízení připojeno k IOMMU. To zahrnuje paměť hlášenou segmenty paměti označené jako PopulatedFromSystemMemory. Pro rezervovanou paměť (například firmware nebo rezervovaný BIOD), která není zpřístupněna prostřednictvím segmentů VidMm, dxgkrnl provede volání DXGKDDI_QUERYADAPTERINFO dotazování všech rezervovaných oblastí paměti, které ovladač potřebuje předem namapovat. Podrobnosti najdete u Hardwarově vyhrazené paměti.

Přiřazení domény

Během inicializace hardwaru vytvoří Dxgkrnl doménu pro každý logický adaptér v systému. Doména spravuje logický adresní prostor a sleduje tabulky stránek a další potřebná data pro mapování. Všechny fyzické adaptéry v jednom logickém adaptéru patří do stejné domény. Dxgkrnl sleduje veškerou mapovanou fyzickou paměť prostřednictvím nových přidělovacích rutin zpětného volání a veškerou paměť přidělenou samotným VidMm.

Doména se připojí k zařízení při prvním vytvoření zabezpečeného virtuálního počítače nebo krátce po spuštění zařízení, pokud se použije výše uvedený klíč registru.

Výhradní přístup

Připojení a odpojení domény IOMMU je rychlé, ale tyto operace momentálně nejsou atomické. Vzhledem k tomu, že není atomová, není zaručeno, že transakce vyslaná přes PCIe bude správně přeložena při přechodu na doménu IOMMU s odlišnými mapováními.

Od verze 1803 (WDDM 2.4) ve Windows 10 musí KMD implementovat následující pár DDI, aby ho mohl volat Dxgkrnl :

Tyto identifikátory DDI tvoří počáteční/koncové párování, kde Dxgkrnl požaduje, aby hardware byl tichý přes sběrnici. Ovladač musí zajistit, aby byl jeho hardware bezobslužný, kdykoli se zařízení přepne na novou doménu IOMMU. To znamená, že ovladač musí zajistit, aby mezi těmito dvěma voláními nepřečetl nebo nezapisoval do systémové paměti ze zařízení.

Mezi těmito dvěma voláními dxgkrnl zaručuje následující záruky:

  • Plánovač je pozastavený. Všechny aktivní úlohy se vyprázdní a na hardware se neposílají ani neplánují žádné nové úlohy.
  • Neprovedou se žádná další volání DDI.

V rámci těchto volání se ovladač může rozhodnout zakázat a potlačit přerušení (včetně přerušení Vsync) během výhradního přístupu, a to i bez explicitního oznámení z operačního systému.

Dxgkrnl zajišťuje, že všechny čekající práce naplánované na hardwaru se dokončí a pak vstoupí do této exkluzivní oblasti přístupu. Během této doby dxgkrnl přiřadí doménu zařízení. Dxgkrnl neprovádí žádné požadavky ovladače ani hardwaru mezi těmito voláními.

Změny DDI

Pro podporu izolace GPU založeného na ioMMU byly provedeny následující změny DDI:

Přidělení paměti a mapování na ioMMU

Dxgkrnl poskytuje prvních šest zpětných volání v předchozí tabulce ovladači režimu jádra, aby mu umožnil přidělit paměť a znovu ji namapovat na logický adresní prostor IOMMU. Tyto funkce zpětného volání kopírují rutiny poskytované rozhraním API Mm. Poskytují ovladači MDLs nebo ukazatele, které popisují paměť, která je také namapována na IOMMU. Tyto knihovny MDL nadále popisují fyzické stránky, ale logický adresní prostor IOMMU je mapován na stejnou adresu.

Dxgkrnl sleduje požadavky na tato zpětná volání, aby se zajistilo, že ovladač neobsahuje žádné úniky. Volání alokace poskytují další popisovač jako součást výstupu, který musí být poskytnut zpět příslušnému uvolňovacímu volání.

Pro paměť, která se nedá přidělit prostřednictvím některého z poskytnutých zpětných volání přidělení, je k dispozici DXGKCB_MAPMDLTOIOMMU zpětné volání, aby bylo možné sledovat a používat MDL spravované ovladačem a používat s IOMMU. Ovladač, který používá toto zpětné volání, je zodpovědný za zajištění, aby životnost MDL překročila odpovídající volání unmap. V opačném případě má volání unmap nedefinované chování. Toto nedefinované chování může vést k ohrožení zabezpečení stránek MDL, které Mm využil v době jejich nemapování.

VidMm automaticky spravuje všechna přidělení, která vytvoří (například DdiCreateAllocationCb, monitorované ploty atd.) v systémové paměti. Řidič nemusí dělat nic, aby tyto přidělení fungovalo.

Rezervace frame bufferu

U ovladačů, které musí uložit rezervované části vyrovnávací paměti snímků do systémové paměti během přechodů napájení, Dxgkrnl po inicializaci adaptéru zabírá potřebnou paměť. Pokud ovladač hlásí podporu izolace IOMMU, dxgkrnl vydá volání DXGKDDI_QUERYADAPTERINFO s následujícím kódem okamžitě po dotazování schopností fyzického adaptéru:

  • Typ je DXGKQAITYPE_FRAMEBUFFERSAVESIZE
  • Vstup je typu UINT, což je index fyzického adaptéru.
  • Výstup je typu DXGK_FRAMEBUFFERSAVEAREA a měl by být maximální velikost vyžadovaná ovladačem k uložení oblasti rezerv vyrovnávací paměti rámce během přechodů napájení.

Dxgkrnl přebírá poplatek za potvrzení částky určené ovladačem, aby zajistil, že na vyžádání může vždy získat fyzické stránky. Tato akce se provádí tak, že se pro každý fyzický adaptér vytvoří jedinečný objekt oddílu, který určí nenulovou hodnotu pro maximální velikost.

Maximální velikost hlášená ovladačem musí být násobkem PAGE_SIZE.

Přenos do vyrovnávací paměti rámce a z této vyrovnávací paměti lze provést v okamžiku výběru ovladače. Pro podporu přenosu poskytuje Dxgkrnl poslední čtyři zpětné volání v předchozí tabulce ovladači režimu jádra. Tyto zpětná volání lze použít k mapování příslušných částí objektu oddílu, které byly vytvořeny při inicializaci adaptéru.

Ovladač musí vždy poskytnout hAdapter pro hlavní zařízení v řetězci LDA, když volá tyto čtyři funkce zpětného volání.

Ovladač má dvě možnosti implementace rezervace vyrovnávací paměti pro snímky:

  1. (Upřednostňovaná metoda) Ovladač by měl přidělit místo na fyzický adaptér pomocí volání DXGKDDI_QUERYADAPTERINFO určit množství úložiště, které je potřeba na adaptér. V době přechodu napájení by ovladač měl současně uložit nebo obnovit paměť jednoho fyzického adaptéru. Tato paměť je rozdělena mezi více sekčních objektů, jeden pro každý fyzický adaptér.

  2. Volitelně může ovladač uložit nebo obnovit všechna data do jednoho objektu sdíleného oddílu. Tuto akci lze provést tak, že ve volání DXGKDDI_QUERYADAPTERINFO pro fyzický adaptér 0 nastavíte jednu velkou maximální velikost; poté určíte nulovou hodnotu pro všechny ostatní fyzické adaptéry. Ovladač pak může jednou uzamknout celý objekt sekce pro použití napříč všemi operacemi ukládání/obnovení, pro všechny fyzické adaptéry. Tato metoda má primární nevýhodu, že vyžaduje uzamknutí většího množství paměti najednou, protože nepodporuje připnutí pouze podrozsahu paměti do MDL. Výsledkem je, že tato operace pravděpodobně selže při vysokém zatížení paměti. Očekává se také, že ovladač mapuje stránky v MDL na GPU pomocí správných posunů stránek.

Ovladač by měl provést následující úlohy pro dokončení přenosu do rámcové vyrovnávací paměti nebo z ní:

  • Během inicializace by měl ovladač předem přidělit malou část paměti přístupné GPU pomocí jedné z rutin přidělení zpětného volání. Tato paměť slouží k zajištění pokroku vpřed, pokud celý objekt sekce nejde najednou namapovat nebo uzamknout.

  • V době přechodu napájení by ovladač měl nejdříve zavolat Dxgkrnl, aby připnul frame buffer. Při úspěšné operaci poskytuje Dxgkrnl ovladači MDL k uzamčeným stránkám mapovaným na IOMMU. Ovladač pak může provést přenos přímo na tyto stránky bez ohledu na to, co je pro hardware nejúčinnější. Ovladač by pak měl zavolat Dxgkrnl k odemknutí nebo zrušení mapování paměti.

  • Pokud Dxgkrnl nemůže najednou připnout celou vyrovnávací paměť rámce, ovladač se musí pokusit o pokrok pomocí předem přidělené vyrovnávací paměti přidělené během inicializace. V tomto případě ovladač provádí přenos v malých blocích. Během každé iterace přenosu (pro každý blok dat) musí ovladač požádat Dxgkrnl , aby poskytl mapovaný rozsah objektu oddílu, do kterého může výsledky zkopírovat. Ovladač pak musí zrušit mapování části objektu sekce před další iterací.

Následující pseudokód je příkladem implementace tohoto algoritmu.


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

Hardwarová rezervovaná paměť

VidMm mapuje hardwarovou rezervovanou paměť před tím, než se zařízení připojí k ioMMU.

VidMm automaticky zpracovává veškerou paměť hlášenou jako segment s příznakem PopulatedFromSystemMemory . VidMm mapuje tuto paměť na základě zadané fyzické adresy.

Pro vyhrazené oblasti privátního hardwaru, které nejsou segmenty ovlivněny, VidMm provede volání DXGKDDI_QUERYADAPTERINFO k dotazování rozsahů ovladačem. Poskytnuté oblasti nesmí překrývat žádné oblasti paměti používané správcem paměti NTOS; VidMm ověří, že k takovým průsečíkům nedochází. Toto ověření zajišťuje, že ovladač nemůže náhodně hlásit oblast fyzické paměti, která je mimo rezervovaný rozsah, což by porušilo bezpečnostní záruky funkce.

Volání dotazu se provede jednou za účelem dotazování na počet potřebných oblastí a následuje druhé volání pro naplnění pole vyhrazených oblastí.

Testování

Pokud se ovladač přihlásí k této funkci, test HLK zkontroluje tabulku importu ovladače, aby se zajistilo, že se nevolají žádné z následujících funkcí Mm :

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

Veškeré přidělení paměti pro souvislou paměť a knihovny MDL by mělo místo toho projít rozhraním zpětného volání Dxgkrnl pomocí uvedených funkcí. Ovladač by také neměl zamknout žádnou paměť. Dxgkrnl spravuje uzamčené stránky pro ovladač. Po přemapování paměti nemusí logická adresa stránek poskytovaných ovladači odpovídat fyzickým adresám.