尽管 IOMMU 隔离,但 DMA 攻击

信息安全 linux 硬件 记忆 核心 dma 攻击
2021-08-19 03:37:48

如果您已经熟悉 PCI 行为和 Linux 对 DMA 缓冲区的处理,请跳到第三部分了解我的实际问题。否则请继续阅读有关 PCI 设备如何执行内存访问以及内核如何处理使用 DMA 与设备通信的小摘要。我将其包含在这里,既希望为提出相同问题的人们提供有用的信息,也希望其他人有机会在我的理解不正确的情况下纠正我。

(我的理解)PCI、IOMMU、DMA

PCI 和 PCIe 设备在其配置空间中有一个两字节命令寄存器,其中包含一个位掩码,用于启用或禁用几个不同的硬件功能。位 2 是总线主机使能该位在设置时允许设备启动 DMA 请求。该位和命令寄存器上的任何其他位由以超级用户模式运行的软件(通常由内核驱动程序)设置,尽管物理存储在 PCI 设备上,但不能被它更改(也许 PCH 保留一个卷影副本每个设备的 PCI 配置空间?)。在没有 IOMMU 的硬件上,设备可以请求读取和写入任何合法的内存地址。这通常被称为 DMA 攻击或恶意总线主控,并且在任何带有恶意 PCI 设备的未受保护系统上都是一个问题。IOMMU 应该是提高安全性和性能的解决方案。作为参考,我专门询问英特尔的实现,VT-d(准确地说是更现代的 VT-d2)。

大多数系统都可以配置为DMA 重新映射或 DMAR。BIOS 中包含的 ACPI 表通常具有 DMAR 表,其中包含一个地址列表,各个 PCI 组将所有内存访问路由到这些地址。这在 Intel 的VT-d 规范的第 2.5.1.1 节中都有描述文档中的一张图表总结了它的工作原理:

DMA 重新映射

Linux 内核 DMA API

DMAR 表由 BIOS 硬编码。一个给定的 PCI 设备(或者更确切地说,一个给定的IOMMU 组)只允许访问一个预先确定的内存范围。内核被告知该内存在哪里,并被指示不要在那里分配任何它不希望通过 DMA 读取/写入的内存。重新映射值在内核日志缓冲区中报告:

DMAR:为设备 0000:00:02.0 [0xad000000 - 0xaf1fffff] 设置身份映射
DMAR:为设备 0000:00:14.0 [0xa95dc000 - 0xa95e8fff] 设置身份映射
DMAR:为设备 0000:00:1a.0 [0xa95dc000 - 0xa95e8fff] 设置身份映射
DMAR:为设备 0000:00:1d.0 [0xa95dc000 - 0xa95e8fff] 设置身份映射
DMAR:为 LPC 准备 0-16MiB 统一映射
DMAR:为设备 0000:00:1f.0 [0x0 - 0xffffff] 设置身份映射
DMAR:面向定向 I/O 的英特尔(R) 虚拟化技术
iommu:将设备 0000:00:00.0 添加到组 0
iommu:将设备 0000:00:01.0 添加到组 1
iommu:将设备 0000:00:02.0 添加到组 2
iommu:将设备 0000:00:14.0 添加到组 3
iommu:将设备 0000:00:16.0 添加到组 4
iommu:将设备 0000:00:1a.0 添加到组 5
iommu:将设备 0000:00:1b.0 添加到组 6
iommu:将设备 0000:00:1c.0 添加到组 7
iommu:将设备 0000:00:1c.2 添加到组 8
iommu:将设备 0000:00:1c.3 添加到组 9
iommu:将设备 0000:00:1c.4 添加到组 10
iommu:将设备 0000:00:1d.0 添加到组 11
iommu:将设备 0000:00:1f.0 添加到组 12
iommu:将设备 0000:00:1f.2 添加到组 12
iommu:将设备 0000:00:1f.3 添加到组 12
iommu:将设备 0000:01:00.0 添加到组 1
iommu:将设备 0000:03:00.0 添加到组 13
iommu:将设备 0000:04:00.0 添加到组 14
iommu:将设备 0000:05:00.0 添加到组 15

从粗体线中,我们看到第 11 组包含(仅)设备0000:00:1d.0,它能够自由访问 13 页的内存0xa95dc000 - 0xa95e8fff组 11 中设备的所有访问都只能在那里写入,防止它们修改其他 DMA 缓冲区的内容或不相关的 OS 代码。这样,即使设备设置了它的总线主控位,它也不需要跟踪它正在写入的位置,并且它不能(意外或恶意)写入它不应该写入的任何地方。

当内核驱动程序想要通过 DMA 与设备交互时,它会为此目的专门分配内存,例如使用void *addr = kmalloc(len, GFP_KERNEL | GFP_DMA). 这将在 in 中返回addr一个虚拟内存地址,该地址指向len大小适合 DMA 使用的内存字节的连续部分。这在 Linux DMA API 文档中有更详细的描述然后驱动程序可以通过这个共享内存区域自由地与 PCI 设备通信。简化后的一系列事件可能如下所示:

  • OpenCL 驱动程序分配与 GPU PCI 设备共享的内存供 DMA 使用。
  • 驱动程序将一些向量数据写入 DMA 地址,然后开始执行其他操作。
  • GPU 通过 PCI 设备读取数据,并开始处理它的缓慢任务。
  • 完成后,GPU 将完成的数据写入缓冲区并触发中断。
  • 由于中断,驱动程序停止正在执行的操作并从内存中读取渲染的图形。

内核是否不信任 DMA 缓冲区并安全地处理它们?

内核是否隐式信任这些 DMA 缓冲区?一个恶意的或被破坏的 PCI 设备,除了指定的缓冲区(IOMMU 阻止它做其他事情)之外没有其他地方写入,是否可以通过利用它们共享的数据结构来破坏内核?显而易见的答案是可能的,因为使用内存不安全语言对复杂数据结构的任何共享和解析都会带来被利用的风险。但是内核开发人员可能会假设这些缓冲区是可信的,并且完全没有努力保护内核免受其中的恶意活动(例如,在非特权用户空间和内核之间共享的数据通过copy_from_user()和类似的功能)。我开始认为,尽管 IOMMU 的限制,恶意 PCI 设备是否可以危害主机的答案可能是

利用这样的漏洞可以像这样工作,buf在设备控制和 DMA 可写地址空间中,dest在内核内存中的其他地方:

  • 设备将数据写入为struct { size_t len; char data[32]; char foo[32]; } buf.
  • 驱动是要复制到datastruct { char data[32]; bool summon_demons; } dest
  • 设备恶意设置buf.len = sizeof(buf.data) + 1buf.foo[0] = 1.
  • 驱动程序不安全地复制数据,使用memcpy(dest.data, buf.data, buf.len).
  • PCI 设备在经典的缓冲区溢出中获得对内核和不朽灵魂的控制。

显然这是一个人为的例子,虽然这样一个明显的错误很可能一开始就不会进入内核,但它说明了我的观点,并将我带到了我的主要问题:

是否存在因不当处理通过 DMA 共享的数据结构或任何特定驱动程序将来自 PCI 设备的输入视为可信的漏洞示例?

VT-d 作为 IOMMU 的局限性

我知道它的局限性,不想要一个试图解释设备如何直接绕过 IOMMU 或使用另一个漏洞来控制系统的答案。我知道:

  • 除非支持 x2APIC 和中断重映射,否则无法充分保护系统。
  • 地址转换服务 (ATS)可以绕过IOMMU。
  • 修改后的 PCI扩展 ROM可以在重新启动时攻击系统。
  • 给定 IOMMU 组中的所有设备都可以访问相同的内存(假设没有 ACS)。
  • 一些 BIOS 带有损坏的 DMAR 表,导致 IOMMU 被禁用。
  • CSME(“Intel ME”)可能能够通过PSF 和 PAVP禁用 VT-d 。
  • 然而,未知的攻击可能会禁用或绕过 IOMMU。

*DMA 表示直接内存访问。这是一项硬件功能,某些硬件接口(如 PCIe)能够请求直接访问系统内存,而无需通过 CPU。

1个回答

最近我一直在阅读 NDSSS 2019 上发表的论文,我认为 2 月份发表的这篇论文完全回答了这个问题。在提出问题时,似乎存在漏洞的答案是肯定的,但它们已在 Linux 内核中从 5.0 开始修复可以在此处找到属于2019 年网络和分布式系统安全研讨会上发表的论文的幻灯片

这些问题仍然相关,因为 Thunderbolt 接口也有 DMA。Linux 中的补丁说明可以在这里找到。