Aislamiento de GPU basado en IOMMU

En esta página se describe la característica de aislamiento de GPU basada en IOMMU para dispositivos compatibles con IOMMU, introducidas en Windows 10 versión 1803 (WDDM 2.4). Consulte la reasignación de IOMMU DMA para obtener actualizaciones más recientes de IOMMU.

Información general

El aislamiento de GPU basado en IOMMU permite a Dxgkrnl restringir el acceso a la memoria del sistema desde la GPU mediante el uso del hardware de IOMMU. El sistema operativo puede proporcionar direcciones lógicas en lugar de direcciones físicas. Estas direcciones lógicas se pueden usar para restringir el acceso del dispositivo a la memoria del sistema solo a la memoria a la que debería poder acceder. Para ello, garantiza que la IOMMU traduce el acceso a la memoria a través de PCIe a páginas físicas válidas y accesibles.

Si la dirección lógica a la que accede el dispositivo no es válida, el dispositivo no puede obtener acceso a la memoria física. Esta restricción evita una serie de vulnerabilidades de seguridad que permiten a un atacante obtener acceso a la memoria física a través de un dispositivo de hardware en peligro y leer el contenido de la memoria del sistema que no son necesarios para la operación del dispositivo.

A partir de Windows 10 versión 1803, esta característica solo está habilitada para equipos en los que Windows Defender Protección de aplicaciones está habilitado para Microsoft Edge (es decir, virtualización de contenedores).

Con fines de desarrollo, la funcionalidad real de reasignación de IOMMU está habilitada o deshabilitada a través de la siguiente clave del Registro:

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.

Si esta característica está habilitada, la IOMMU se habilita poco después de que se inicie el adaptador. Todas las asignaciones de controladores realizadas antes de esta hora se asignan cuando se habilita.

Además, si la clave de almacenamiento provisional de velocidad 14688597 está establecida como habilitada, la IOMMU se activa cuando se crea una máquina virtual segura. Por ahora, esta clave de almacenamiento provisional está deshabilitada de forma predeterminada para permitir el autohospedaje sin la compatibilidad adecuada con IOMMU.

Mientras está habilitada, se produce un error al iniciar una máquina virtual segura si el controlador no proporciona compatibilidad con IOMMU.

Actualmente no hay ninguna manera de deshabilitar la IOMMU después de que se haya habilitado.

Acceso a memoria

Dxgkrnl garantiza que toda la memoria accesible por la GPU se reasigna a través de la IOMMU para asegurarse de que esta memoria sea accesible. La memoria física a la que necesita acceder la GPU se puede dividir actualmente en cuatro categorías:

  • Las asignaciones específicas del controlador realizadas a través de las funciones de estilo MmAllocateContiguousMemory- o MmAllocatePagesForMdl (incluidas Las variaciones especificadas y extendidas) se deben asignar a la IOMMU antes de que la GPU acceda a ellas. En lugar de llamar a las API mm , Dxgkrnl proporciona devoluciones de llamada al controlador en modo kernel para permitir la asignación y reasignación en un paso. Cualquier memoria diseñada para ser accesible para GPU debe pasar por estas devoluciones de llamada o la GPU no puede acceder a esta memoria.

  • Toda la memoria a la que accede la GPU durante las operaciones de paginación o asignada a través de GpuMmu debe asignarse a la IOMMU. Este proceso es completamente interno para el Administrador de memoria de vídeo (VidMm), que es un subcomponente de Dxgkrnl. VidMm controla la asignación y desasignación del espacio de direcciones lógico en cualquier momento en que se espera que la GPU acceda a esta memoria, entre las que se incluyen:

  • Asignación del almacén de respaldo de una asignación durante todo el tiempo durante una transferencia hacia o desde VRAM o desde todo el tiempo asignado a segmentos de apertura o memoria del sistema.

  • Asignación y desasignación de vallas supervisadas.

  • Durante las transiciones de energía, es posible que el controlador necesite guardar partes de la memoria reservada de hardware. Para controlar esta situación, Dxgkrnl proporciona un mecanismo para que el controlador especifique la cantidad de memoria inicial para almacenar estos datos. La cantidad exacta de memoria requerida por el controlador puede cambiar dinámicamente, pero Dxgkrnl toma un cargo de confirmación en el límite superior en el momento en que se inicializa el adaptador para asegurarse de que las páginas físicas se pueden obtener cuando sea necesario. Dxgkrnl es responsable de garantizar que esta memoria está bloqueada y asignada a la IOMMU para la transferencia durante las transiciones de energía.

  • Para cualquier recurso reservado de hardware, VidMm garantiza que asigna correctamente los recursos de IOMMU por el momento en que el dispositivo está conectado a la IOMMU. Esto incluye la memoria notificada por segmentos de memoria notificados con PopulatedFromSystemMemory. Para la memoria reservada (por ejemplo, firmware/BIOD reservado) que no se expone a través de segmentos VidMm, Dxgkrnl realiza una llamada de DXGKDDI_QUERYADAPTERINFO para consultar todos los intervalos de memoria reservados que el controlador necesita asignar con antelación. Consulte Memoria reservada de hardware para obtener más información.

Asignación de dominio

Durante la inicialización del hardware, Dxgkrnl crea un dominio para cada adaptador lógico del sistema. El dominio administra el espacio de direcciones lógico y realiza un seguimiento de las tablas de páginas y otros datos necesarios para las asignaciones. Todos los adaptadores físicos de un único adaptador lógico pertenecen al mismo dominio. Dxgkrnl realiza un seguimiento de toda la memoria física asignada a través de las nuevas rutinas de devolución de llamada de asignación y cualquier memoria asignada por VidMm.

El dominio se conectará al dispositivo la primera vez que se crea una máquina virtual segura o poco después de que se inicie el dispositivo si se usa la clave del Registro anterior.

Acceso exclusivo

La asociación y desasociación de dominios de IOMMU es extremadamente rápida, pero no es actualmente atómica. Esto significa que no se garantiza que una transacción emitida a través de PCIe se traduzca correctamente mientras se intercambia a un dominio DE IOMMU con diferentes asignaciones.

Para controlar esta situación, a partir de Windows 10 versión 1803 (WDDM 2.4), un KMD debe implementar el siguiente par DDI para que Dxgkrnl llame a:

Estos DDIs forman un emparejamiento inicial o final, donde Dxgkrnl solicita que el hardware esté en silencio sobre el bus. El controlador debe asegurarse de que su hardware sea silencioso cada vez que el dispositivo se cambie a un nuevo dominio DE IOMMU. Es decir, el controlador debe asegurarse de que no lee ni escribe en la memoria del sistema desde el dispositivo entre estas dos llamadas.

Entre estas dos llamadas, Dxgkrnl realiza las siguientes garantías:

  • El programador está suspendido. Todas las cargas de trabajo activas se vacían y no se envían cargas de trabajo nuevas a o programadas en el hardware.
  • No se realizan otras llamadas DDI.

Como parte de estas llamadas, el controlador puede optar por deshabilitar y suprimir las interrupciones (incluidas las interrupciones asincrónicas de V) durante el acceso exclusivo, incluso sin notificación explícita del sistema operativo.

Dxgkrnl garantiza que se complete cualquier trabajo pendiente programado en el hardware y, a continuación, escriba esta región de acceso exclusivo. Durante este tiempo, Dxgkrnl asigna el dominio al dispositivo. Dxgkrnl no realiza ninguna solicitud del controlador o hardware entre estas llamadas.

Cambios de DDI

Se realizaron los siguientes cambios de DDI para admitir el aislamiento de GPU basado en IOMMU:

Asignación y asignación de memoria a IOMMU

Dxgkrnl proporciona las seis primeras devoluciones de llamada de la tabla anterior al controlador en modo kernel para permitirle asignar memoria y reasignarla al espacio de direcciones lógicos de IOMMU. Estas funciones de devolución de llamada imitan las rutinas proporcionadas por la interfaz de API mm . Proporcionan al controlador MDL o punteros que describen la memoria que también se asigna a la IOMMU. Estas MDL siguen describen las páginas físicas, pero el espacio de direcciones lógicos de IOMMU se asigna en la misma dirección.

Dxgkrnl realiza un seguimiento de las solicitudes a estas devoluciones de llamada para ayudar a garantizar que el controlador no haya fugas. Las devoluciones de llamada de asignación proporcionan un identificador adicional como parte de la salida que se debe devolver a la devolución de llamada gratuita correspondiente.

En el caso de la memoria que no se puede asignar a través de una de las devoluciones de llamada de asignación proporcionadas, se proporciona la DXGKCB_MAPMDLTOIOMMU devolución de llamada para permitir que se realice un seguimiento de las MDL administradas por el controlador y se usen con la IOMMU. Un controlador que usa esta devolución de llamada es responsable de garantizar que la duración de MDL supere la llamada sin asignar correspondiente. De lo contrario, la llamada a un mapa tiene un comportamiento indefinido que podría dar lugar a la seguridad comprometida de las páginas de la MDL que se vuelven a asignar por Mm en el momento en que no están asignadas.

VidMm administra automáticamente las asignaciones que crea (por ejemplo, DdiCreateAllocationCb, barreras supervisadas, etc.) en la memoria del sistema. El controlador no necesita hacer nada para que estas asignaciones funcionen.

Reserva de búfer de fotogramas

Para los controladores que deben guardar partes reservadas del búfer de fotogramas en la memoria del sistema durante las transiciones de energía, Dxgkrnl cobra un cargo de confirmación en la memoria necesaria cuando se inicializa el adaptador. Si el controlador informa del soporte de aislamiento de IOMMU, Dxgkrnl emitirá una llamada a DXGKDDI_QUERYADAPTERINFO con lo siguiente inmediatamente después de consultar los límites del adaptador físico:

  • El tipo es DXGKQAITYPE_FRAMEBUFFERSAVESIZE
  • La entrada es del tipo UINT, que es el índice del adaptador físico.
  • La salida es del tipo DXGK_FRAMEBUFFERSAVEAREA y debe ser el tamaño máximo requerido por el controlador para guardar el área de reserva del búfer de fotogramas durante las transiciones de energía.

Dxgkrnl cobra un cargo de confirmación en la cantidad especificada por el controlador para asegurarse de que siempre puede obtener páginas físicas bajo petición. Esta acción se realiza mediante la creación de un objeto de sección único para cada adaptador físico que especifica un valor distinto de cero para el tamaño máximo.

El tamaño máximo notificado por el controlador debe ser un múltiplo de PAGE_SIZE.

Realizar la transferencia hacia y desde el búfer de fotogramas se puede realizar en un momento de la elección del controlador. Para ayudar en la transferencia, Dxgkrnl proporciona las cuatro últimas devoluciones de llamada de la tabla anterior al controlador en modo kernel. Estas devoluciones de llamada se pueden usar para asignar las partes adecuadas del objeto de sección que se creó cuando se inicializó el adaptador.

El controlador siempre debe proporcionar el hAdapter para el dispositivo maestro/cliente potencial en una cadena LDA cuando llama a estas cuatro funciones de devolución de llamada.

El controlador tiene dos opciones para implementar la reserva de búfer de fotogramas:

  1. (Método preferido) El controlador debe asignar espacio por adaptador físico mediante la llamada anterior DXGKDDI_QUERYADAPTERINFO para especificar la cantidad de almacenamiento que se necesita por adaptador. En el momento de la transición de energía, el controlador debe guardar o restaurar la memoria de un adaptador físico a la vez. Esta memoria se divide entre varios objetos de sección, uno por adaptador físico.

  2. Opcionalmente, el controlador puede guardar o restaurar todos los datos en un único objeto de sección compartida. Esta acción se puede realizar especificando un único tamaño máximo grande en el DXGKDDI_QUERYADAPTERINFO llamada al adaptador físico 0 y, a continuación, un valor cero para todos los demás adaptadores físicos. A continuación, el controlador puede anclar todo el objeto de sección una vez para usarlo en todas las operaciones de guardado y restauración, para todos los adaptadores físicos. Este método tiene el inconveniente principal de que requiere bloquear una mayor cantidad de memoria a la vez, ya que no admite anclar solo un subrango de la memoria en una MDL. Como resultado, es más probable que esta operación produzca errores bajo presión de memoria. También se espera que el controlador asigne las páginas de MDL a la GPU mediante los desplazamientos de página correctos.

El controlador debe realizar las siguientes tareas para completar una transferencia hacia o desde el búfer de fotogramas:

  • Durante la inicialización, el controlador debe asignar previamente un pequeño fragmento de memoria accesible de GPU mediante una de las rutinas de devolución de llamada de asignación. Esta memoria se usa para ayudar a garantizar el progreso hacia delante si todo el objeto de sección no se puede asignar o bloquear a la vez.

  • En el momento de la transición de energía, el controlador debe llamar primero a Dxgkrnl para anclar el búfer de fotogramas. En caso de éxito, Dxgkrnl proporciona al controlador un MDL a páginas bloqueadas asignadas a la IOMMU. A continuación, el controlador puede realizar una transferencia directamente a estas páginas en cualquier medio más eficaz para el hardware. A continuación, el controlador debe llamar a Dxgkrnl para desbloquear o desasignación de la memoria.

  • Si Dxgkrnl no puede anclar todo el búfer de fotogramas a la vez, el controlador debe intentar realizar el progreso hacia delante mediante el búfer asignado previamente durante la inicialización. En este caso, el controlador realiza la transferencia en fragmentos pequeños. Durante cada iteración de la transferencia (para cada fragmento), el controlador debe pedir a Dxgkrnl que proporcione un intervalo asignado del objeto de sección en el que puedan copiar los resultados. A continuación, el controlador debe desasignación de la parte del objeto de sección antes de la siguiente iteración.

El siguiente pseudocódigo es una implementación de ejemplo de este 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 reservada de hardware

VidMm asigna memoria reservada de hardware antes de que el dispositivo esté conectado a la IOMMU.

VidMm controla automáticamente cualquier memoria notificada como un segmento con la marca PopulatedFromSystemMemory . VidMm asigna esta memoria en función de la dirección física proporcionada.

En el caso de las regiones reservadas de hardware privado no expuestas por segmentos, VidMm realiza una llamada DXGKDDI_QUERYADAPTERINFO para consultar los intervalos por el controlador. Los intervalos proporcionados no deben superponerse a ninguna región de memoria utilizada por el administrador de memoria de NTOS; VidMm valida que no se produzcan estas intersecciones. Esta validación garantiza que el controlador no puede notificar accidentalmente una región de memoria física que está fuera del intervalo reservado, lo que infringiría las garantías de seguridad de la característica.

La llamada de consulta se realiza una vez para consultar el número de intervalos necesarios y va seguida de una segunda llamada para rellenar la matriz de intervalos reservados.

Prueba

Si el controlador opta por esta característica, una prueba de HLK examina la tabla de importación del controlador para asegurarse de que no se llama a ninguna de las siguientes funciones Mm :

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

En su lugar, todas las asignaciones de memoria para memoria contigua y MDL deben pasar por la interfaz de devolución de llamada de Dxgkrnl mediante las funciones enumeradas. El controlador tampoco debe bloquear ninguna memoria. Dxgkrnl administra páginas bloqueadas para el controlador. Una vez que se reasigna la memoria, es posible que la dirección lógica de las páginas proporcionadas al controlador ya no coincida con las direcciones físicas.