Поделиться через


Изоляция GPU на основе IOMMU

Изоляция GPU на основе IOMMU — это метод, используемый для повышения безопасности системы и стабильности путем управления доступом к системной памяти GPU. В этой статье описывается функция изоляции GPU на основе IOMMU на основе WDDM для устройств, поддерживающих IOMMU, и как разработчики могут реализовать его в своих графических драйверах.

Эта функция доступна начиная с Windows 10 версии 1803 (WDDM 2.4). Дополнительные сведения о последних обновлениях IOMMU DMA см . в этой статье .

Обзор

Изоляция GPU на основе IOMMU позволяет Dxgkrnl ограничить доступ к системной памяти с GPU, используя оборудование IOMMU. ОС может предоставлять логические адреса вместо физических адресов. Эти логические адреса можно использовать для ограничения доступа устройства к системной памяти только памяти, к которым он должен иметь доступ. Это делает, гарантируя, что IOMMU преобразует доступ к памяти через PCIe на допустимые и доступные физические страницы.

Если логический адрес, к которым обращается устройство, недействителен, устройство не может получить доступ к физической памяти. Это ограничение предотвращает ряд эксплойтов, позволяющих злоумышленнику получить доступ к физической памяти с помощью скомпрометированного аппаратного устройства. Без этого злоумышленники могут считывать содержимое системной памяти, которая не требуется для операции устройства.

По умолчанию эта функция включена только для компьютеров, где application Guard в Защитнике Windows включена для Microsoft Edge (то есть виртуализация контейнеров).

Для целей разработки фактические функции перемечении IOMMU включены или отключены с помощью следующего раздела реестра:

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.

Если эта функция включена, IOMMU включен вскоре после запуска адаптера. Все выделения драйверов, сделанные до этого времени, сопоставляются при включении.

Кроме того, если для промежуточного ключа скорости 14688597 задано значение включено, IOMMU активируется при создании безопасной виртуальной машины. Сейчас этот промежуточный ключ отключен по умолчанию, чтобы разрешить самостоятельное размещение без надлежащей поддержки IOMMU.

Хотя эта функция включена, запуск безопасной виртуальной машины завершается ошибкой, если драйвер не предоставляет поддержку IOMMU.

В настоящее время нет способа отключить IOMMU после включения.

Обращение к памяти

Dxgkrnl гарантирует, что все память, доступная gpu, переназначается с помощью IOMMU, чтобы обеспечить доступ к этой памяти. Физическая память, к которому должен получить доступ GPU, в настоящее время может быть разделена на четыре категории:

  • Выделения для конкретного драйвера, сделанные с помощью функций стиля MmAllocateContiguousMemory или MmAllocatePagesForMdl (включая УказатьCache и расширенные варианты), необходимо сопоставить с IOMMU перед доступом к gpu. Вместо вызова API mm dxgkrnl предоставляет обратные вызовы драйверу режима ядра, чтобы разрешить выделение и переназначения на одном шаге. Любая память, предназначенная для доступа к GPU, должна проходить через эти обратные вызовы, или GPU не может получить доступ к этой памяти.

  • Все объемы памяти, доступ к которым осуществляется gpu во время разбиения на страницы, или сопоставлены с gpuMmu, должны быть сопоставлены с IOMMU. Этот процесс полностью внутренний для диспетчера памяти видео (VidMm), который является подкомпонентом Dxgkrnl. VidMm обрабатывает сопоставление и отменяет сопоставление логического адресного пространства в любое время, когда GPU будет получать доступ к этой памяти, включая:

  • Сопоставление резервного хранилища выделения для любого из следующих вариантов:

    • Всю длительность передачи в VRAM или из нее.
    • Все время, когда резервное хранилище сопоставляется с системной памятью или сегментами диафрагмы.
  • Сопоставление и отмена отслеживания отслеживаемых заборов.

  • Во время перехода питания драйверу может потребоваться сохранить части зарезервированной аппаратной памяти. Чтобы справиться с этой ситуацией, Dxgkrnl предоставляет механизм для драйвера, чтобы указать объем памяти перед хранением этих данных. Точный объем памяти, который требует драйвер, может динамически изменяться. Тем не менее, Dxgkrnl принимает плату за фиксацию на верхней границе во время инициализации адаптера, чтобы убедиться, что физические страницы можно получить при необходимости. Dxgkrnl отвечает за блокировку этой памяти и сопоставление с IOMMU для передачи во время переходов питания.

  • Для любых зарезервированных ресурсов оборудования VidMm гарантирует правильность сопоставления ресурсов IOMMU по времени, когда устройство подключено к IOMMU. К ним относится память, сообщаемая сегментами памяти с помощью ЗаполненияFromSystemMemory. Для зарезервированной памяти (например, зарезервированного встроенного ПО или BIOD), который не предоставляется через сегменты VidMm, Dxgkrnl делает вызов DXGKDDI_QUERYADAPTERINFO для запроса всех зарезервированных диапазонов памяти, которые драйвер должен сопоставить заранее. Дополнительные сведения см. в разделе "Зарезервированная память оборудования".

Назначение домена

Во время инициализации оборудования Dxgkrnl создает домен для каждого логического адаптера в системе. Домен управляет логическим адресным пространством и отслеживает таблицы страниц и другие необходимые данные для сопоставлений. Все физические адаптеры в одном логическом адаптере принадлежат одному домену. Dxgkrnl отслеживает все сопоставленные физические памяти с помощью новых подпрограмм обратного вызова выделения и любой памяти, выделенной самой VidMm.

Домен будет присоединен к устройству при первом создании безопасной виртуальной машины или вскоре после запуска устройства, если используется указанный выше раздел реестра.

Эксклюзивный доступ

Подключение и отсоединение домена IOMMU быстро, но, тем не менее, не является атомарным. Так как это не атомарная, транзакция, выданная через PCIe, не гарантируется правильной переводом на домен IOMMU с различными сопоставлениями.

Чтобы справиться с этой ситуацией, начиная с Windows 10 версии 1803 (WDDM 2.4), KMD должен реализовать следующую пару DDI для вызова Dxgkrnl :

  • DxgkDdiBeginExclusiveAccess вызывается для уведомления KMD о том, что переключение домена IOMMU происходит.
  • DxgkDdiEndExclusiveAccess вызывается после завершения коммутатора домена IOMMU.

Эти DDIs образуют связывание начала и конца, где Dxgkrnl запрашивает, что оборудование безмолвно над шиной. Драйвер должен убедиться, что его оборудование безмолвно при переключении устройства на новый домен IOMMU. То есть драйвер должен убедиться, что он не считывает или записывает в системную память с устройства между этими двумя вызовами.

Между этими двумя вызовами Dxgkrnl делает следующие гарантии:

  • Планировщик приостановлен. Все активные рабочие нагрузки сбрасываются, и новые рабочие нагрузки не отправляются в оборудование или не планируются.
  • Другие вызовы DDI не выполняются.

В рамках этих вызовов драйвер может отключить и отключить прерывания (включая прерывания Vsync) во время эксклюзивного доступа, даже без явного уведомления из ОС.

Dxgkrnl гарантирует, что все ожидающие работы, запланированные на оборудование завершены, а затем введите этот регион монопольного доступа. В это время Dxgkrnl назначает домен устройству. Dxgkrnl не делает никаких запросов драйвера или оборудования между этими вызовами.

Изменения DDI

Для поддержки изоляции GPU на основе IOMMU были внесены следующие изменения DDI:

Выделение памяти и сопоставление с IOMMU

Dxgkrnl предоставляет первые шесть обратных вызовов в предыдущей таблице драйверу режима ядра, чтобы разрешить ему выделять память и перенаправлять его в логическое адресное пространство IOMMU. Эти функции обратного вызова имитируют подпрограммы, предоставляемые интерфейсом API Mm . Они предоставляют драйверу многомерные выражения или указатели, описывающие память, которая также сопоставляется с IOMMU. Эти mdls продолжают описывать физические страницы, но логическое адресное пространство IOMMU сопоставляется с тем же адресом.

Dxgkrnl отслеживает запросы на эти обратные вызовы, чтобы обеспечить отсутствие утечек драйвером. Обратные вызовы выделения предоставляют другой дескриптор как часть выходных данных, которые должны быть предоставлены обратно в соответствующий бесплатный обратный вызов.

Для памяти, которая не может быть выделена с помощью одного из предоставленных обратных вызовов выделения, DXGKCB_MAPMDLTOIOMMU обратный вызов предоставляется для отслеживания и использования с IOMMU управляемыми драйверами MDLs. Драйвер, использующий этот обратный вызов, отвечает за обеспечение того, что время существования MDL превышает соответствующий вызов unmap. В противном случае вызов unmap имеет неопределенное поведение. Это неопределенное поведение может привести к скомпрометированной безопасности страниц MDL, которые мм перепрофилированы на время их отмены.

VidMm автоматически управляет всеми созданными выделениями (например, DdiCreateAllocationCb, отслеживаемых заборов и т. д.) в системной памяти. Драйверу не нужно ничего делать, чтобы сделать эти выделения работой.

Резервирование буфера кадра

Для драйверов, которые должны сохранять зарезервированные части буфера кадра в системную память во время перехода питания, Dxgkrnl принимает плату за фиксацию в требуемой памяти при инициализации адаптера. Если драйвер сообщает о поддержке изоляции IOMMU, dxgkrnl вызовет DXGKDDI_QUERYADAPTERINFO со следующими моментами после запроса ограничений физического адаптера:

  • Тип DXGKQAITYPE_FRAMEBUFFERSAVESIZE
  • Входные данные — тип UINT, который является индексом физического адаптера.
  • Выходные данные являются типом DXGK_FRAMEBUFFERSAVEAREA и должны быть максимальным размером, необходимым драйвером для сохранения области резерва буфера кадра во время переходов питания.

Dxgkrnl принимает плату за фиксацию на сумму, указанную драйвером, чтобы гарантировать, что она всегда может получать физические страницы по запросу. Это действие выполняется путем создания уникального объекта раздела для каждого физического адаптера, указывающего ненулевое значение максимального размера.

Максимальный размер, сообщаемый драйвером, должен иметь несколько PAGE_SIZE.

Выполнение передачи в буфер кадра и из нее можно выполнить во время выбора драйвера. Чтобы помочь в передаче, Dxgkrnl предоставляет последние четыре обратных вызова в предыдущей таблице драйверу режима ядра. Эти обратные вызовы можно использовать для сопоставления соответствующих частей объекта раздела, созданного при инициализации адаптера.

Драйвер должен всегда предоставлять hAdapter для устройства свинца в цепочке LDA при вызове этих четырех функций обратного вызова.

Драйвер имеет два варианта реализации резервирования буфера кадров:

  1. (Предпочтительный метод) Драйвер должен выделить место на физический адаптер с помощью вызова DXGKDDI_QUERYADAPTERINFO , чтобы указать объем хранилища, необходимого для каждого адаптера. Во время перехода питания драйвер должен сохранять или восстанавливать память одного физического адаптера одновременно. Эта память разделена по нескольким объектам раздела, по одному на физический адаптер.

  2. При необходимости драйвер может сохранять или восстанавливать все данные в одном объекте общего раздела. Это можно сделать, указав один большой максимальный размер в DXGKDDI_QUERYADAPTERINFO вызове физического адаптера 0, а затем нулевое значение для всех остальных физических адаптеров. Затем драйвер может закрепить весь объект раздела один раз для использования во всех операциях сохранения и восстановления для всех физических адаптеров. Этот метод имеет основной недостаток, который требует блокировки большего объема памяти одновременно, так как он не поддерживает закрепление только подранга памяти в MDL. В результате эта операция, скорее всего, завершится сбоем под давлением памяти. Драйвер также должен сопоставить страницы в MDL с GPU с помощью правильных смещения страниц.

Драйвер должен выполнить следующие задачи, чтобы завершить передачу в буфер кадров или из нее:

  • Во время инициализации драйвер должен предварительно выделить небольшую часть доступной памяти GPU с помощью одной из подпрограмм обратного вызова выделения. Эта память используется для обеспечения прогресса вперед, если весь объект раздела не может быть сопоставлен или заблокирован одновременно.

  • Во время перехода питания драйвер должен сначала вызвать Dxgkrnl , чтобы закрепить буфер кадра. При успешном выполнении Dxgkrnl предоставляет драйверу MDL заблокированные страницы, сопоставленные с IOMMU. Затем драйвер может выполнить передачу непосредственно на эти страницы в любом средстве, наиболее эффективным для оборудования. Затем драйвер должен вызвать Dxgkrnl , чтобы разблокировать или отменить сопоставление памяти.

  • Если Dxgkrnl не может закрепить весь буфер кадров одновременно, драйвер должен попытаться выполнить переадресацию с помощью предварительно выделенного буфера во время инициализации. В этом случае драйвер выполняет передачу в небольших блоках. Во время каждой итерации передачи (для каждого блока) драйвер должен попросить Dxgkrnl предоставить сопоставленный диапазон объекта раздела, в который они могут скопировать результаты. Затем драйвер должен отменить сопоставление части объекта раздела перед следующей итерацией.

Следующий псевдокод является примером реализации этого алгоритма.


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

Зарезервированная память оборудования

VidMm сопоставляет зарезервированную память оборудования перед присоединением устройства к IOMMU.

VidMm автоматически обрабатывает любую память, сообщаемую как сегмент с флагом ЗаполнениеFromSystemMemory . VidMm сопоставляет эту память на основе предоставленного физического адреса.

Для зарезервированных частных областей оборудования, не предоставляемых сегментами, VidMm выполняет вызов DXGKDDI_QUERYADAPTERINFO для запроса диапазонов драйвером. Указанные диапазоны не должны перекрываться в каких-либо регионах памяти, используемых диспетчером памяти NTOS; VidMm проверяет, что такие пересечения не происходят. Эта проверка гарантирует, что драйвер не может случайно сообщить о области физической памяти, которая находится за пределами зарезервированного диапазона, что нарушает гарантии безопасности функции.

Вызов запроса выполняется один раз, чтобы запросить количество необходимых диапазонов, за которым следует второй вызов для заполнения массива зарезервированных диапазонов.

Тестирование

Если драйвер выбирает эту функцию, тест HLK проверяет таблицу импорта драйвера, чтобы убедиться, что ни одна из следующих функций Mm не вызывается:

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

Все выделение памяти для непрерывной памяти и многомерных выражений должно вместо этого пройти через интерфейс обратного вызова Dxgkrnl с помощью перечисленных функций. Драйвер также не должен блокировать память. Dxgkrnl управляет заблокированными страницами для драйвера. После переназначения памяти логический адрес страниц, предоставленных драйверу, может больше не соответствовать физическим адресам.