CVE-2025-39961CVE-2025-39961是Linux内核AMD IOMMU主机页表实现中的一个竞态条件漏洞。AMD IOMMU主机页表实现支持动态页表级别(最多6级),从3级配置开始,根据IOVA地址进行扩展。内核维护一个根指针和当前页表级别,以便在alloc_pte()/fetch_pte()操作中正确执行页表遍历。当IOMMU IOVA分配器耗尽32位地址空间时,会切换到64位地址模式,此时AMD IOMMU驱动程序需要增加页表级别以支持更大的IOVA空间。然而,在unmap路径(iommu_v1_unmap_pages())中,fetch_pte()在不加锁的情况下读取pgtable->[root/mode],这可能导致在极端情况下,当increase_address_space()正在更新pgtable->[root/mode]时,fetch_pte()读取到错误的页表级别(pgtable->mode),使其与页表中编码的级别进行比较后返回NULL,最终导致iommu_unmap操作失败,上层可能触发重试或记录WARN_ON警告。该漏洞的CVSS评分为4.7,属于中危级别,主要影响系统可用性,攻击者需要本地低权限访问即可触发。
该漏洞的核心技术原理在于AMD IOMMU页表管理中的并发读写竞态问题。具体而言:
1. **页表结构**:AMD IOMMU使用多级页表(最多6级),初始为3级。当IOVA地址空间从32位扩展到64位时,需要增加页表级别。内核通过pgtable->root(根指针)和pgtable->mode(当前页表级别)来管理页表状态。
2. **竞态窗口**:在increase_address_space()函数中,代码先更新pgtable->root为新的根页表值,然后再更新pgtable->mode(pgtable->mode += 1)。这两个操作之间存在时间窗口。
3. **竞态触发**:与此同时,在unmap路径中,iommu_v1_unmap_pages()调用fetch_pte()读取pgtable->[mode/root]时没有加锁。当CPU 0执行map操作触发increase_address_space(),CPU 1同时执行unmap操作调用fetch_pte()时,可能出现以下情况:fetch_pte()读取到新的root值但旧的mode值,导致模式不匹配而返回NULL。
4. **影响后果**:fetch_pte()返回NULL会导致iommu_unmap操作失败,上层可能进行重试或触发WARN_ON警告,在极端情况下可能导致系统不稳定或拒绝服务。
5. **修复方案**:由于页表级别更新操作频率较低且已经使用自旋锁同步,开发者采用seqcount机制来实现读路径的无锁读取,从而避免竞态条件。