Security Vulnerability Report
中文
CVE-2025-39961 CVSS 4.7 MEDIUM

CVE-2025-39961

Published: 2025-10-09 13:15:32
Last Modified: 2026-02-26 23:03:21
Source: 416baaa9-dc9f-4396-8d5f-8c081fb06d67

Description

In the Linux kernel, the following vulnerability has been resolved: iommu/amd/pgtbl: Fix possible race while increase page table level The AMD IOMMU host page table implementation supports dynamic page table levels (up to 6 levels), starting with a 3-level configuration that expands based on IOVA address. The kernel maintains a root pointer and current page table level to enable proper page table walks in alloc_pte()/fetch_pte() operations. The IOMMU IOVA allocator initially starts with 32-bit address and onces its exhuasted it switches to 64-bit address (max address is determined based on IOMMU and device DMA capability). To support larger IOVA, AMD IOMMU driver increases page table level. But in unmap path (iommu_v1_unmap_pages()), fetch_pte() reads pgtable->[root/mode] without lock. So its possible that in exteme corner case, when increase_address_space() is updating pgtable->[root/mode], fetch_pte() reads wrong page table level (pgtable->mode). It does compare the value with level encoded in page table and returns NULL. This will result is iommu_unmap ops to fail and upper layer may retry/log WARN_ON. CPU 0 CPU 1 ------ ------ map pages unmap pages alloc_pte() -> increase_address_space() iommu_v1_unmap_pages() -> fetch_pte() pgtable->root = pte (new root value) READ pgtable->[mode/root] Reads new root, old mode Updates mode (pgtable->mode += 1) Since Page table level updates are infrequent and already synchronized with a spinlock, implement seqcount to enable lock-free read operations on the read path.

CVSS Details

CVSS Score
4.7
Severity
MEDIUM
CVSS Vector
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H

Configurations (Affected Products)

cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* - VULNERABLE
cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* - VULNERABLE
cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* - VULNERABLE
cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* - VULNERABLE
cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* - VULNERABLE
Linux Kernel < 6.17 (包含AMD IOMMU驱动iommu/amd/pgtbl的版本)

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
/* * CVE-2025-39961 PoC - Race condition trigger in AMD IOMMU pgtbl * This PoC demonstrates the race condition between increase_address_space() * and fetch_pte() in the AMD IOMMU page table implementation. * * Note: This is a conceptual PoC. Actual exploitation requires kernel-level * access and specific IOMMU configuration. */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <sys/mman.h> #define NUM_THREADS 2 #define ITERATIONS 10000 /* Simulated shared page table state */ volatile unsigned long pgtable_root = 0x1000; volatile int pgtable_mode = 3; /* Start with 3-level page table */ volatile int race_detected = 0; /* * Simulates increase_address_space() - CPU 0 (map path) * Updates root first, then mode - creating the race window */ void *cpu0_map_thread(void *arg) { for (int i = 0; i < ITERATIONS; i++) { /* Simulate alloc_pte() -> increase_address_space() */ pgtable_root = 0x2000 + i; /* Update root to new value */ /* Small delay to widen the race window */ for (volatile int j = 0; j < 10; j++); pgtable_mode = 4; /* Update mode after root */ } return NULL; } /* * Simulates iommu_v1_unmap_pages() -> fetch_pte() - CPU 1 (unmap path) * Reads root and mode without lock - detects inconsistency */ void *cpu1_unmap_thread(void *arg) { unsigned long local_root; int local_mode; for (int i = 0; i < ITERATIONS; i++) { local_root = pgtable_root; /* Read root without lock */ local_mode = pgtable_mode; /* Read mode without lock */ /* Check for inconsistency: new root with old mode */ if (local_root >= 0x2000 && local_mode == 3) { race_detected++; fprintf(stderr, "[RACE DETECTED] root=0x%lx mode=%d " "(new root with old mode)\n", local_root, local_mode); } } return NULL; } int main(int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; printf("CVE-2025-39961 PoC: AMD IOMMU pgtbl race condition\n"); printf("Starting %d threads to trigger race...\n", NUM_THREADS); /* Create threads to simulate concurrent map/unmap operations */ pthread_create(&threads[0], NULL, cpu0_map_thread, NULL); pthread_create(&threads[1], NULL, cpu1_unmap_thread, NULL); /* Wait for both threads */ pthread_join(threads[0], NULL); pthread_join(threads[1], NULL); printf("\nResults: %d race conditions detected\n", race_detected); if (race_detected > 0) { printf("Vulnerability confirmed: race condition exists in " "page table level update path\n"); return 1; } printf("No race detected in this run. Try increasing ITERATIONS.\n"); return 0; }

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2025-39961", "sourceIdentifier": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "published": "2025-10-09T13:15:32.250", "lastModified": "2026-02-26T23:03:20.560", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "In the Linux kernel, the following vulnerability has been resolved:\n\niommu/amd/pgtbl: Fix possible race while increase page table level\n\nThe AMD IOMMU host page table implementation supports dynamic page table levels\n(up to 6 levels), starting with a 3-level configuration that expands based on\nIOVA address. The kernel maintains a root pointer and current page table level\nto enable proper page table walks in alloc_pte()/fetch_pte() operations.\n\nThe IOMMU IOVA allocator initially starts with 32-bit address and onces its\nexhuasted it switches to 64-bit address (max address is determined based\non IOMMU and device DMA capability). To support larger IOVA, AMD IOMMU\ndriver increases page table level.\n\nBut in unmap path (iommu_v1_unmap_pages()), fetch_pte() reads\npgtable->[root/mode] without lock. So its possible that in exteme corner case,\nwhen increase_address_space() is updating pgtable->[root/mode], fetch_pte()\nreads wrong page table level (pgtable->mode). It does compare the value with\nlevel encoded in page table and returns NULL. This will result is\niommu_unmap ops to fail and upper layer may retry/log WARN_ON.\n\nCPU 0 CPU 1\n------ ------\nmap pages unmap pages\nalloc_pte() -> increase_address_space() iommu_v1_unmap_pages() -> fetch_pte()\n pgtable->root = pte (new root value)\n READ pgtable->[mode/root]\n\t\t\t\t\t Reads new root, old mode\n Updates mode (pgtable->mode += 1)\n\nSince Page table level updates are infrequent and already synchronized with a\nspinlock, implement seqcount to enable lock-free read operations on the read path."}], "metrics": {"cvssMetricV31": [{"source": "[email protected]", "type": "Primary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H", "baseScore": 4.7, "baseSeverity": "MEDIUM", "attackVector": "LOCAL", "attackComplexity": "HIGH", "privilegesRequired": "LOW", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "NONE", "integrityImpact": "NONE", "availabilityImpact": "HIGH"}, "exploitabilityScore": 1.0, "impactScore": 3.6}]}, "weaknesses": [{"source": "[email protected]", "type": "Primary", "description": [{"lang": "en", "value": "CWE-362"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "4.9.194", "versionEndExcluding": "4.10", "matchCriteriaId": "2572FA5E-845E-4DF3-9D9C-8918ECC777C8"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "4.14.146", "versionEndExcluding": "4.15", "matchCriteriaId": "F1F3489C-0F08-4C44-9AFD-D45E286F51D3"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "4.19.75", "versionEndExcluding": "4.20", "matchCriteriaId": "5EC25515-A753-47F9-823B-4483BE60F328"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "5.2.17", "versionEndExcluding": "5.3", "matchCriteriaId": "C42AB381-30AB-4C50-8C95-A4BFAF431AFB"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "5.3.1", "versionEndExcluding": "6.6.108", "matchCriteriaId": "983311BA-7332-42E8-9C1F-71C82FC23C03"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.7", "versionEndExcluding": "6.12.49", "matchCriteriaId": "CAA033E9-A2C5-4976-A83E-9804D8FB827F"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.13", "versionEndExcluding": "6.16.9", "matchCriteriaId": "638DD910-1189-4F5E-98BF-2D436B695112"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:5.3:-:*:*:*:*:*:*", "matchCriteriaId": "D036D76E-AC69-4382-B4C1-8EDA1ABB2941"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:5.3:rc8:*:*:*:*:*:*", "matchCriteriaId": "999345BA-F820-40B9-A711-32CA9265C289"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:6.17:rc1:*:*:*:*:*:*", "matchCriteriaId": "327D22EF-390B-454C-BD31-2ED23C998A1C"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:6.17:rc2:*:*:*:*:*:*", "matchCriteriaId": "C730CD9A-D969-4A8E-9522-162AAF7C0EE9"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:6.17:rc3:*:*:*:*:*:*", "matchCriteriaId": "39982C4B-716E-4B2F-8196-FA301F47807D"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:6.17:rc4:*:*:*:*:*:*", "matchCriteriaId": "340BEEA9-D70D-4 ... (truncated)