Intel VT-d
Intel VT-d, formally called as Intel VT for Directed I/O, consists of the following three features:- DMA Remapping
- Interrupt Remapping
- Interrupt Posting
DMA remapping is the most commonly discussed feature out of those and is the focus of this article.
DMA Remapping
DMA Remapping is an important feature because it allows software to implement security against Direct Memory Access (DMA) from malicious devices by configuring access permissions for each physical memory page. While ordinary memory page protections can be configured through the paging structures, and when Intel VT-x is used, through the Extended Page Tables (EPT), those configurations are completely ignored in case of DMA access. Therefore, the other protection mechanism is required to complete the protection of memory. DMA remapping achieves this.
The following illustration from the specification highlights that DMA goes through DMA remapping instead of CPU memory virtualization (ie, EPT).
The following illustration from the specification highlights that DMA goes through DMA remapping instead of CPU memory virtualization (ie, EPT).
VT-x Not Required
Ignore the upper half of the illustration above. It is a typical misconception that VT-d (DMA remapping) is tied with VT-x, virtual machines, and such. DMA remapping is usable and useful without VT-x; Windows, for example, can enable a DMA remapping based security feature (called Kernel DMA Protection) without requiring VT-x based security (VBS: Virtualization Based Security) enabled.
The sample project shown in this post below enables DMA remapping independently as well.
IOMMU
DMA remapping is also referred to as IOMMU, as it functions like Memory Management Unit (MMU) for IO memory access. Not only the concept is similar, but it also has a very similar programming interface as that of MMU, that is, the paging structures and EPT.
At high-level, the major difference is that DMA remapping uses two more tables for translation, on the top of the familiar PML5, 4, PDPT, PD, and PT. Simply put, translation with MMU is
- Hardware register => PML4 => PDPT => ...
while that of IOMMU is
- Hardware register => Root table => Context table => PML4 => PDPT => ...
The specification refers to the tables referenced from the context table as the second-level page tables. The below diagram illustrates the translation flow.
Notice that,
- The entry of the root table is selected based on the bus number of the device requesting DMA.
- The entry of the context table is selected based on a combination of device and function numbers of the device.
As an example of bus:device:function (referred to as source-id) assignment, my test DMA-capable device is listed as Bus 6 : Device 0 : Function 0 on one system as shown below.
Sample Code and Demo
Let us jump into some code. The HelloIommuPkg is a runtime DXE driver that enables DMA remapping and protects its first page (PE header) from DMA read and write by any devices.
Loading this will yield the following output and protect the page if successful.
Then, performing DMA read with the test PCI device using PCILeech demonstrates that the other page is readable,
but the protected page is not.
By inspecting one of the reported fault-recording registers using RWEverything, it can be confirmed that DMA was indeed blocked by a lack of read-permission.
- The first column indicates the faulting address (0x6ff48000)
- The third column indicates the source-id of the requesting device (Bus 6 : Device 0 : Function 0)
- 6 in the fourth column indicates the lack of read-permission.
Programming IOMMU
Enabling DMA remapping at a minimum can be divided into the following steps:- Locating the DMA Remapping Reporting (DMAR) ACPI table.
- Gathering information about the available DMA remapping hardware units from DMA-remapping hardware unit definition (DRHD) structures in (1).
- Configuring translation by initializing the tables mentioned above.
- Writing hardware registers to use (3) and activating DMA remapping.
HelloIummuDxe.c roughly follows this sequence with some demonstration and error checking code. (1) and (2) are straightforward and can be validated tools like RWEverything.
The complexity of (3) varies largely depending on how granular and selective translations and memory protections are required. HelloIummuPkg allows any access from any device to anywhere, except against the single page, which simplifies this step. (4) is mostly just following the specification.
Overall, the minimum steps are simple and HelloIummuPkg's line count without comments is less than 700 lines.
The complexity of (3) varies largely depending on how granular and selective translations and memory protections are required. HelloIummuPkg allows any access from any device to anywhere, except against the single page, which simplifies this step. (4) is mostly just following the specification.
Overall, the minimum steps are simple and HelloIummuPkg's line count without comments is less than 700 lines.
Use of DMA Remapping on Windows
Windows uses DMA remapping when available. If the system does not enable Kernel DMA Protection, it configures translations mostly to pass-through all requests from all devices with few exceptions.The following screenshot taken from the system without Kernel DMA Protection shows translation for the DMA-capable device at Bus 7 : Device 0 : Function 0. The value 9 at the right bottom indicates DMA requests are passed thought (See "TT: Translation Type" in the specification).
Notice the most of the entries points to the same context table at 0x1ac000 which is configured for pass-through, providing no protection.
As a side note, it would be technically possible for third-party Windows drivers to modify those translations and attempt to provide additional security against DMA unless VBS is enabled.
Use of DMA Remapping with Kernel DMA Protection
If Kernel DMA Protection is enabled, most of the translations are configured to fail. This is achieved by pointing to the second-level PML4 that is filled with zero, meaning translations are not present.The below screenshot shows an example configuration with Kernel DMA Protection. Notice the context table at 0x1ac000 points to the second level PML4 at 0x251000, which is all zero.
Note that those memory locations are not visible if VBS is enabled. Disable it to inspect them.
Interestingly, I was not able to observe the described behavior of Kernel DMA Protection, in that, regardless of whether the screen is locked, performing DMA against the device resulted in bug check 0xE6: DRIVER_VERIFIER_DMA_VIOLATION (type 0x26). From what I read from Hal.dll, it made sense to bug check, but I doubt this is how Kernel DMA Protection is supposed to protect the system.
Conclusion
DMA remapping is part of the Intel VT-d architecture providing security against DMA from malicious devices and can be enabled without Intel VT-x to be used together. The sample project HelloIommuPkg demonstrates the simple setup of DMA remapping from UEFI with less than 700 lines of code.It is shown that Windows enables DMA remapping if available, and when the Kernel DMA Protection feature is enabled, DMA access is mostly blocked though the second-level PML4.
Further Learning Resources
- Intel® Virtualization Technology for Directed I/O Architecture Specification
- A Tour Beyond BIOS: Using IOMMU for DMA Protection in UEFI Firmware
- VTd module in IntelSiliconPkg
- Hal.dll, especially
- HalpIvtProcessDrhdEntry and ExtEnvRegisterIommu, which are responsible for the programming step (2). Those highlight the layout of the structure passed across many of IOMMU related functions.
- IvtInitializeIommu (and HalpIommuInitializeAll), which is responsible for part of the step (3) and (4).
- HalpIommuConfigureInterrupt, IvtSetMessageInterruptRouting and IvtHandleInterrupt for the implementation of translation fault reporting (ie, how bug check is initiated).
- Highly recommended to study the specification before reading Hal.