IOMMU tabanlı GPU yalıtımı

IOMMU tabanlı GPU yalıtımı, GPU'ların sistem belleğine nasıl eriştiklerini yöneterek sistem güvenliğini ve kararlılığını geliştirmek için kullanılan bir tekniktir. Bu makalede, WDDM'nin IOMMU özellikli cihazlar için IOMMU tabanlı GPU yalıtım özelliği ve geliştiricilerin bunu grafik sürücülerinde nasıl uygulayabileceği açıklanmaktadır.

Bu özellik, Windows 10 sürüm 1803 'den (WDDM 2.4) itibaren kullanılabilir. Daha yeni IOMMU güncelleştirmeleri için bkz. IOMMU DMA yeniden eşlemesi .

Genel Bakış

IOMMU tabanlı GPU yalıtımı , Dxgkrnl'nin IOMMU donanımını kullanarak GPU'dan sistem belleğine erişimi kısıtlamasına olanak tanır. İşletim sistemi fiziksel adresler yerine mantıksal adresler sağlayabilir. Bu mantıksal adresler, cihazın sistem belleğine erişimini yalnızca erişebileceği bellekle kısıtlamak için kullanılabilir. Bunu yapmak için IOMMU'nin PCIe üzerinden bellek erişimlerini geçerli ve erişilebilir fiziksel sayfalara çevirmesini sağlar.

Cihaz tarafından erişilen mantıksal adres geçerli değilse, cihaz fiziksel belleğe erişemez. Bu kısıtlama, bir saldırganın güvenliği aşılmış bir donanım cihazı aracılığıyla fiziksel belleğe erişmesine olanak sağlayan çeşitli açıklardan yararlanmaları önler. Bu olmadan, saldırganlar cihazın işlemi için gerekli olmayan sistem belleğinin içeriğini okuyabilir.

Varsayılan olarak, bu özellik yalnızca Windows Defender Application Guard'ın Microsoft Edge (kapsayıcı sanallaştırma) için etkinleştirildiği bilgisayarlarda etkinleştirilir.

Geliştirme amacıyla, gerçek IOMMU yeniden eşleme işlevi aşağıdaki kayıt defteri anahtarı aracılığıyla etkinleştirilir veya devre dışı bırakılır:

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.

Bu özellik etkinleştirilirse, bağdaştırıcı başlatıldıktan kısa süre sonra IOMMU etkinleştirilir. Bu saatten önce yapılan tüm sürücü ayırmaları etkinleştirildiğinde eşlenir.

Ayrıca, hız hazırlama anahtarı 14688597 etkin olarak ayarlanırsa, güvenli bir sanal makine oluşturulduğunda IOMMU etkinleştirilir. Şimdilik bu hazırlama anahtarı, uygun IOMMU desteği olmadan kendi kendine barındırmaya izin vermek için varsayılan olarak devre dışı bırakılmıştır.

Etkinleştirildiğinde, sürücü IOMMU desteği sağlamazsa güvenli bir sanal makine başlatılamaz.

Şu anda etkinleştirildikten sonra IOMMU'nun devre dışı bırakılması mümkün değildir.

Bellek erişimi

Dxgkrnl , GPU tarafından erişilebilen tüm belleğin IOMMU aracılığıyla yeniden eşlenerek bu belleğin erişilebilir olmasını sağlar. GPU'ya erişmesi gereken fiziksel bellek şu anda dört kategoriye ayrılabilir:

  • MmAllocateContiguousMemory- veya MmAllocatePagesForMdl stili işlevler (SpecifyCache ve genişletilmiş çeşitlemeler dahil) aracılığıyla yapılan belirli sürücülere özgü ayırmalar, GPU erişimi öncesinde IOMMU ile eşlenmelidir. Dxgkrnl, Mm API'lerini çağırmak yerine, bir adımda ayırmaya ve yeniden eşlemeye izin vermek için çekirdek modu sürücüsüne geri çağırmalar sağlar. GPU'ya erişilebilir olması amaçlanan tüm bellekler bu geri çağırmalardan geçmelidir, aksi takdirde GPU bu belleğe erişemez.

  • Disk belleği işlemleri sırasında GPU tarafından erişilen veya GpuMmu aracılığıyla eşlenen tüm bellekler IOMMU ile eşlenmelidir. Bu işlem, Dxgkrnl'nin bir alt bileşeni olan Video Bellek Yöneticisi'nin (VidMm) tamamen dahilidir. VidMm, GPU'nun bu belleğe erişmesinin gerektiği her seferinde, aşağıdakiler dahil olmak üzere mantıksal adres alanını eşleme ve eşlemeyi kaldırma işlemlerini yönetir:

  • Aşağıdakilerden biri için ayırmanın yedekleme deposu eşleme:

    • VRAM'a veya VRAM'den aktarım boyunca geçen tüm süre.
    • Yedekleme deposunun sistem belleğine veya diyafram kesimlerine eşlendiği sürenin tamamı.
  • İzlenen çitleri eşleme ve kapatma.

  • Güç geçişleri sırasında sürücünün donanıma ayrılmış belleğin bölümlerini kaydetmesi gerekebilir. Bu durumu işlemek için Dxgkrnl , sürücüye bu verileri depolamak için önde ne kadar bellek olduğunu belirten bir mekanizma sağlar. Sürücünün tam olarak gerektirdiği bellek miktarı dinamik olarak değişebilir. Bu doğrultuda, Dxgkrnl, gerektiğinde fiziksel sayfaların alınabilmesini sağlamak için bağdaştırıcı başlatıldığında üst sınırda bir taahhüt yükü alır. Dxgkrnl , güç geçişleri sırasında aktarım için bu belleğin kilitlendiğinden ve IOMMU'ya eşlendiğinden emin olmakla sorumludur.

  • Tüm donanıma ayrılmış kaynaklar için VidMm, cihazın IOMMU'ya bağlı olduğu zamana kadar IOMMU kaynaklarını doğru şekilde eşlemesini sağlar. Bu, PopulatedFromSystemMemory ile bildirilen bellek bölümleri tarafından bildirilen belleği içerir. Dxgkrnl, VidMm kesimleri aracılığıyla sunulmamış ayrılmış bellek (örneğin, üretici yazılımı/BIOD ayrılmış) için, sürücünün önceden eşlenmesi gereken tüm ayrılmış bellek aralıklarını sorgulamak için DXGKDDI_QUERYADAPTERINFO bir çağrı yapar. Ayrıntılar için bkz Donanıma ayrılmış bellek.

Etki alanı ataması

Donanımın başlatılması sırasında, Dxgkrnl sistemdeki her mantıksal bağdaştırıcı için bir etki alanı oluşturur. Etki alanı mantıksal adres alanını yönetir ve eşlemeler için sayfa tablolarını ve diğer gerekli verileri izler. Tek bir mantıksal bağdaştırıcıdaki tüm fiziksel bağdaştırıcılar aynı etki alanına aittir. Dxgkrnl, yeni ayırma geri çağırma rutinleri ve VidMm tarafından bizzat tahsis edilen bellekler dahil olmak üzere tüm eşlenen fiziksel belleği izler.

Etki alanı, güvenli bir sanal makine ilk kez oluşturulduğunda veya yukarıdaki kayıt defteri anahtarı kullanılırsa cihaz başlatıldıktan kısa bir süre sonra cihaza eklenir.

Özel erişim

IOMMU etki alanı ekleme ve ayırma işlemleri hızlıdır, ancak yine de şu anda atomik değildir. Atomik olmadığından, FARKLı eşlemelere sahip bir IOMMU etki alanına geçiş yapılırken PCIe üzerinden yapılan bir işlemin doğru çevrilmesi garanti edilemez.

Windows 10 sürüm 1803'ten (WDDM 2.4) başlayarak, bu durumu yönetmek için, bir KMD'nin Dxgkrnl'i çağırabilmesi amacıyla aşağıdaki DDI çiftini uygulaması gerekir:

Bu DDI'ler, Dxgkrnl'nin donanımın veri yolu üzerinde sessiz kalmasını talep ettiği bir başlangıç/bitiş eşleştirmesi oluşturur. Sürücü, cihaz yeni bir IOMMU etki alanına geçtiğinde donanımının sessiz olduğundan emin olmalıdır. Başka bir ifadeyle, sürücünün bu iki çağrı arasında cihazdan sistem belleğini okumadığından veya yazmadığından emin olması gerekir.

Bu iki çağrı arasında Dxgkrnl aşağıdaki garantileri verir:

  • Zamanlayıcı askıya alındı. Tüm etkin iş yükleri boşaltılır ve donanıma yeni iş yükü gönderilmez veya zamanlanmaz.
  • Başka DDI çağrısı yapılmaz.

Bu çağrıların bir parçası olarak, sürücü özel erişim sırasında işletim sisteminden açık bildirimde bulunmadan bile kesmeleri (Vsync kesmeleri dahil) devre dışı bırakmayı ve gizlemeyi seçebilir.

Dxgkrnl , donanımda zamanlanmış bekleyen tüm çalışmaların tamamlanmasını sağlar ve ardından bu özel erişim bölgesini girer. Bu süre boyunca Dxgkrnl , etki alanını cihaza atar. Dxgkrnl , bu çağrılar arasında sürücüden veya donanımdan herhangi bir istekte bulunmaz.

DDI Değişiklikleri

IOMMU tabanlı GPU yalıtımını desteklemek için aşağıdaki DDI değişiklikleri yapıldı:

Bellek ayırma ve IOMMU'ya eşleme

Dxgkrnl, daha önceki tabloda yer alan çekirdek modu sürücüsünde bellek ayırmasına ve bunu IOMMU'nun mantıksal adres alanına yeniden eşlemesine izin vermek için ilk altı geri çağırma işlevini sağlar. Bu geri çağırma işlevleri , Mm API arabirimi tarafından sağlanan yordamları taklit eder. Sürücüye MDL'ler veya IOMMU ile eşlenen belleği açıklayan işaretçiler sağlar. Bu MDL'ler fiziksel sayfaları açıklamaya devam eder. Ancak, IOMMU'nun mantıksal adres alanı da aynı adreste eşleştirilmiştir.

Dxgkrnl , sürücü tarafından herhangi bir sızıntı olmadığından emin olmak için bu geri çağırmalara yönelik istekleri izler. Ayırma geri çağırmaları, ilgili ücretsiz geri çağırmaya geri sağlanması gereken çıkışın bir parçası olarak başka bir tanıtıcı sağlar.

Sağlanan ayırma geri çağırmalarından biri aracılığıyla ayrılabilen bellek için, sürücü tarafından yönetilen MDL'lerin IOMMU ile izlenmesine ve kullanılmasına izin vermek için DXGKCB_MAPMDLTOIOMMU geri çağırma sağlanır . Bu geri çağırmayı kullanan bir sürücü, MDL'nin ömrünün ilgili eşlemesiz çağrıyı aşmasını sağlamakla sorumludur. Aksi takdirde, unmap çağrısı tanımsız davranışa sahiptir. Bu tanımlanmamış davranış, MDL'nin eşlenmemiş hale geldiklerinde Mm tarafından farklı bir amaçla kullanılan sayfalarının güvenliğinin tehlikeye atılmasına yol açabilir.

VidMm, sistem belleğinde oluşturduğu tüm ayırmaları (örneğin, DdiCreateAllocationCb, izlenen çitler vb.) otomatik olarak yönetir. Sürücünün bu ayırmaların çalışması için herhangi bir işlem yapması gerekmez.

Çerçeve arabelleği ayırma

Güç geçişleri sırasında çerçeve arabelleğinin ayrılmış bölümlerini sistem belleğine kaydetmesi gereken sürücüler için, bağdaştırıcı başlatıldığında Dxgkrnl, gerekli bellek üzerinde bir taahhüt yükü alır. Sürücü IOMMU yalıtım desteğini bildirirse , Dxgkrnl fiziksel bağdaştırıcı büyük harflerini sorguladıktan hemen sonra aşağıdakiyle DXGKDDI_QUERYADAPTERINFO çağrısı yapar:

  • TürDXGKQAITYPE_FRAMEBUFFERSAVESIZE
  • Giriş, fiziksel bağdaştırıcı dizini olan UINT türündedir.
  • Çıkış DXGK_FRAMEBUFFERSAVEAREA türündedir ve güç geçişleri sırasında çerçeve arabelleği yedek alanını kaydetmek için sürücü tarafından gereken en büyük boyut olmalıdır.

Dxgkrnl , her zaman istek üzerine fiziksel sayfalar alabilmesini sağlamak için sürücü tarafından belirtilen miktarda işleme ücreti alır. Bu eylem, her fiziksel bağdaştırıcı için en büyük boyut için sıfır olmayan bir değer belirten benzersiz bir bölüm nesnesi oluşturularak gerçekleştirilir.

Sürücü tarafından bildirilen boyut üst sınırı PAGE_SIZE katı olmalıdır.

Çerçeve arabelleğine ve bu arabellekten aktarım, sürücünün seçeceği bir zamanda gerçekleştirilebilir. Aktarıma yardımcı olmak için Dxgkrnl , önceki tablodaki son dört geri çağırmayı çekirdek modu sürücüsüne sağlar. Bu geri çağırmalar, bağdaştırıcı başlatıldığında oluşturulan bölüm nesnesinin uygun bölümlerini eşlemek için kullanılabilir.

Sürücü, bu dört geri çağırma işlevini çağırdığında, bir LDA zincirinde öncü cihaz için her zaman hAdapter sağlamalıdır.

Sürücünün çerçeve arabelleği rezervasyonu gerçekleştirmesi için iki seçeneği vardır:

  1. (Tercih edilen yöntem) Sürücü, bağdaştırıcı başına gereken depolama miktarını belirtmek için DXGKDDI_QUERYADAPTERINFO çağrısını kullanarak fiziksel bağdaştırıcı başına alan ayırmalıdır. Güç geçişi sırasında, sürücü belleği bir kerede bir fiziksel bağdaştırıcı olarak kaydetmeli veya geri yüklemelidir. Bu bellek, fiziksel bağdaştırıcı başına bir tane olan birden çok bölüm nesnesine bölünür.

  2. İsteğe bağlı olarak, sürücü tüm verileri tek bir paylaşılan bölüm nesnesine kaydedebilir veya geri yükleyebilir. Bu eylem, fiziksel bağdaştırıcı 0 için DXGKDDI_QUERYADAPTERINFO çağrısında tek bir en büyük boyut ve ardından diğer tüm fiziksel bağdaştırıcılar için sıfır değer belirtilerek gerçekleştirilebilir. Sürücü daha sonra tüm fiziksel bağdaştırıcılar için tüm kaydetme/geri yükleme işlemlerinde kullanmak üzere bölüm nesnesinin tamamını bir kez sabitleyebilir. Bu yöntem, MDL'ye yalnızca bir alt bellek alanını sabitlemeyi desteklemediğinden, aynı anda daha büyük miktarda belleği kilitlemeyi gerektiren birincil dezavantaja sahiptir. Sonuç olarak, bu işlemin bellek baskısı altında başarısız olma olasılığı daha yüksektir. Sürücünün doğru sayfa uzaklıklarını kullanarak MDL'deki sayfaları GPU'ya eşlemesi de beklenir.

Çerçeve arabelleğine veya arabelleğinden bir aktarımı tamamlamak için sürücü, aşağıdaki görevleri gerçekleştirmelidir:

  • Başlatma sırasında, sürücü ayırma geri çağırma yordamlarından birini kullanarak GPU erişilebilir belleğin küçük bir kısmını önceden ayırmalıdır. Bu bellek alanı, eğer bölüm nesnesinin tamamı aynı anda eşlenemiyor/kilitlenemiyorsa ileri hareketi sağlamaya yardımcı olmak için kullanılır.

  • Güç geçişi sırasında, sürücü önce çerçeve arabelleğini sabitlemek için Dxgkrnl'yi çağırmalıdır. Başarılı olduğunda Dxgkrnl , sürücüye IOMMU'ya eşlenmiş kilitli sayfalara bir MDL sağlar. Sürücü daha sonra donanım için en verimli olan her ne şekilde olursa olsun doğrudan bu sayfalara aktarım gerçekleştirebilir. Sürücü daha sonra belleğin kilidini açmak/eşlemesini açmak için Dxgkrnl'yi çağırmalıdır.

  • Dxgkrnl, çerçeve arabelleğinin tamamını aynı anda sabitleyemezse, sürücü başlatma sırasında ayrılan önceden ayrılmış arabelleği kullanarak ileriye doğru ilerlemeyi denemelidir. Bu durumda, sürücü aktarımı küçük parçalar halinde gerçekleştirir. Aktarımın her yinelemesi sırasında (her öbek için), sürücünün Dxgkrnl'den sonuçları kopyalayabilecekleri bölüm nesnesinin eşlenmiş aralığını sağlamasını istemesi gerekir. Ardından sürücü, sonraki yinelemeden önce bölüm nesnesinin bir kısmının eşlemesini kaldırmalıdır.

Aşağıdaki sahte kod, bu algoritmanın örnek bir uygulamasıdır.


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

Donanıma ayrılmış bellek

VidMm, cihaz IOMMU'ya bağlanmadan önce donanım tarafından ayrılmış belleği eşler.

VidMm, PopulatedFromSystemMemory bayrağıyla kesim olarak bildirilen tüm belleği otomatik olarak işler. VidMm bu belleği sağlanan fiziksel adrese göre eşler.

Özel donanım için ayrılmış ve segmentler tarafından açığa çıkarılmayan bölgeler için, VidMm sürücü tarafından aralıkları sorgulamak amacıyla bir DXGKDDI_QUERYADAPTERINFO çağrısı yapar. Sağlanan aralıklar NTOS bellek yöneticisi tarafından kullanılan hiçbir bellek bölgesiyle çakışmamalıdır; VidMm, böyle bir kesişim olmadığını doğrular. Bu doğrulama, sürücünün ayrılmış aralığın dışındaki bir fiziksel bellek bölgesini yanlışlıkla raporlayamaz ve bu da özelliğin güvenlik garantilerini ihlal eder.

Sorgu çağrısı, gerekli aralıkların sayısını sorgulamak için bir kez yapılır ve ardından ayrılmış aralık dizisini doldurmak için ikinci bir çağrı yapılır.

Test aşaması

Sürücü bu özelliği kabul ederse, bir HLK testi aşağıdaki Mm işlevlerinden hiçbirinin çağrılmadığından emin olmak için sürücünün içeri aktarma tablosunu tarar:

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

Bitişik bellek ve MDL'ler için tüm bellek ayırma işlemi, listelenen işlevleri kullanarak Dxgkrnl'nin geri çağırma arabiriminden geçmelidir. Sürücü ayrıca herhangi bir belleği kilitlememelidir. Dxgkrnl , sürücü için kilitli sayfaları yönetir. Bellek yeniden eşlendikten sonra, sürücüye sağlanan sayfaların mantıksal adresi artık fiziksel adreslerle eşleşmeyebilir.