{ "dataType": "CVE_RECORD", "dataVersion": "5.2", "cveMetadata": { "cveId": "CVE-2024-46787", "assignerOrgId": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "state": "PUBLISHED", "assignerShortName": "Linux", "dateReserved": "2024-09-11T15:12:18.278Z", "datePublished": "2024-09-18T07:12:43.264Z", "dateUpdated": "2026-08-05T11:38:33.153Z" }, "containers": { "cna": { "providerMetadata": { "orgId": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", "shortName": "Linux", "dateUpdated": "2026-08-05T11:38:33.153Z" }, "descriptions": [ { "lang": "en", "value": "In the Linux kernel, the following vulnerability has been resolved:\n\nuserfaultfd: fix checks for huge PMDs\n\nPatch series \"userfaultfd: fix races around pmd_trans_huge() check\", v2.\n\nThe pmd_trans_huge() code in mfill_atomic() is wrong in three different\nways depending on kernel version:\n\n1. The pmd_trans_huge() check is racy and can lead to a BUG_ON() (if you hit\n the right two race windows) - I've tested this in a kernel build with\n some extra mdelay() calls. See the commit message for a description\n of the race scenario.\n On older kernels (before 6.5), I think the same bug can even\n theoretically lead to accessing transhuge page contents as a page table\n if you hit the right 5 narrow race windows (I haven't tested this case).\n2. As pointed out by Qi Zheng, pmd_trans_huge() is not sufficient for\n detecting PMDs that don't point to page tables.\n On older kernels (before 6.5), you'd just have to win a single fairly\n wide race to hit this.\n I've tested this on 6.1 stable by racing migration (with a mdelay()\n patched into try_to_migrate()) against UFFDIO_ZEROPAGE - on my x86\n VM, that causes a kernel oops in ptlock_ptr().\n3. On newer kernels (>=6.5), for shmem mappings, khugepaged is allowed\n to yank page tables out from under us (though I haven't tested that),\n so I think the BUG_ON() checks in mfill_atomic() are just wrong.\n\nI decided to write two separate fixes for these (one fix for bugs 1+2, one\nfix for bug 3), so that the first fix can be backported to kernels\naffected by bugs 1+2.\n\n\nThis patch (of 2):\n\nThis fixes two issues.\n\nI discovered that the following race can occur:\n\n mfill_atomic other thread\n ============ ============\n \n pmdp_get_lockless() [reads none pmd]\n \n \n \n __pte_alloc [no-op]\n \n \n BUG_ON(pmd_none(*dst_pmd))\n\nI have experimentally verified this in a kernel with extra mdelay() calls;\nthe BUG_ON(pmd_none(*dst_pmd)) triggers.\n\nOn kernels newer than commit 0d940a9b270b (\"mm/pgtable: allow\npte_offset_map[_lock]() to fail\"), this can't lead to anything worse than\na BUG_ON(), since the page table access helpers are actually designed to\ndeal with page tables concurrently disappearing; but on older kernels\n(<=6.4), I think we could probably theoretically race past the two\nBUG_ON() checks and end up treating a hugepage as a page table.\n\nThe second issue is that, as Qi Zheng pointed out, there are other types\nof huge PMDs that pmd_trans_huge() can't catch: devmap PMDs and swap PMDs\n(in particular, migration PMDs).\n\nOn <=6.4, this is worse than the first issue: If mfill_atomic() runs on a\nPMD that contains a migration entry (which just requires winning a single,\nfairly wide race), it will pass the PMD to pte_offset_map_lock(), which\nassumes that the PMD points to a page table.\n\nBreakage follows: First, the kernel tries to take the PTE lock (which will\ncrash or maybe worse if there is no \"struct page\" for the address bits in\nthe migration entry PMD - I think at least on X86 there usually is no\ncorresponding \"struct page\" thanks to the PTE inversion mitigation, amd64\nlooks different).\n\nIf that didn't crash, the kernel would next try to write a PTE into what\nit wrongly thinks is a page table.\n\nAs part of fixing these issues, get rid of the check for pmd_trans_huge()\nbefore __pte_alloc() - that's redundant, we're going to have to check for\nthat after the __pte_alloc() anyway.\n\nBackport note: pmdp_get_lockless() is pmd_read_atomic() in older kernels." } ], "metrics": [ { "cvssV3_1": { "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" }, "scenarios": [ { "lang": "en", "value": "AV:L - The vulnerability is reached through the `userfaultfd(2)` syscall (or `/dev/userfaultfd`) followed by `UFFDIO_REGISTER` and `UFFDIO_COPY`/`UFFDIO_ZEROPAGE` ioctls on the attacker's own address space. This requires local execution on the target system; there is no remote or adjacent network path into `mm/userfaultfd.c`.\nAC:L - The attacker controls both sides of the race entirely from their own threads — one thread spins on `UFFDIO_ZEROPAGE`/`UFFDIO_COPY` while another drives `MADV_DONTNEED`/THP faults or forces PMD migration via `move_pages()`, NUMA balancing, or compaction — and the operation can be retried in a tight loop with no lockout or rate limit. The commit author characterizes the migration variant as \"a single, fairly wide race,\" and THP is enabled in essentially all production kernel configurations.\nPR:L - `userfaultfd_syscall_allowed()` returns true unconditionally when `UFFD_USER_MODE_ONLY` is set, so any unprivileged local user obtains a userfaultfd regardless of `CAP_SYS_PTRACE` or the `vm.unprivileged_userfaultfd` sysctl; on kernels in the affected range below 5.11 the syscall was unrestricted or defaulted to permitted. `validate_dst_vma()` only requires the target VMA to be uffd-registered, which the attacker does to their own mapping.\nUI:N - The attacker's own threads perform every step — creating the uffd, registering the VMA, and racing the ioctl against PMD teardown/migration. No victim action or interaction is involved.\nS:U - The corruption occurs in kernel page-table handling for the attacker's own mm and stays within the kernel's security authority, which is the standard local privilege-escalation scope. No hypervisor, IOMMU, or other trust boundary is crossed.\nC:H - On kernels ≤6.4 the kernel passes a migration/huge PMD to `pte_offset_map_lock()` and then writes a PTE into memory that is actually transhuge page contents the attacker has mapped, disclosing kernel-generated physical addresses and PTE flags. The resulting page-table-injection primitive can be leveraged into arbitrary physical memory reads.\nI:H - `set_pte_at()` writes a PTE into non-page-table memory at an attacker-influenced location, and `spin_lock()` is taken on a wild `ptlock_ptr()` derived from attacker-influenced PFN bits — both are kernel write primitives. Injecting attacker-controlled data into a structure used as a page table is a well-known route to arbitrary write and full control-flow hijack.\nA:H - The race reliably reaches `BUG_ON(pmd_none(*dst_pmd))`, which the author experimentally verified, and the migration-PMD variant produced a confirmed kernel oops in `ptlock_ptr()` on 6.1 stable. Either outcome is a kernel panic/oops, and it can be triggered repeatedly by any local user." } ] } ], "affected": [ { "product": "Linux", "vendor": "Linux", "defaultStatus": "unaffected", "repo": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git", "programFiles": [ "mm/userfaultfd.c" ], "versions": [ { "version": "c1a4de99fada21e2e9251e52cbb51eff5aadc757", "lessThan": "3c6b4bcf37845c9359aed926324bed66bdd2448d", "status": "affected", "versionType": "git" }, { "version": "c1a4de99fada21e2e9251e52cbb51eff5aadc757", "lessThan": "98cc18b1b71e23fe81a5194ed432b20c2d81a01a", "status": "affected", "versionType": "git" }, { "version": "c1a4de99fada21e2e9251e52cbb51eff5aadc757", "lessThan": "71c186efc1b2cf1aeabfeff3b9bd5ac4c5ac14d8", "status": "affected", "versionType": "git" } ] }, { "product": "Linux", "vendor": "Linux", "defaultStatus": "affected", "repo": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git", "programFiles": [ "mm/userfaultfd.c" ], "versions": [ { "version": "4.3", "status": "affected" }, { "version": "0", "lessThan": "4.3", "status": "unaffected", "versionType": "semver" }, { "version": "6.6.51", "lessThanOrEqual": "6.6.*", "status": "unaffected", "versionType": "semver" }, { "version": "6.10.10", "lessThanOrEqual": "6.10.*", "status": "unaffected", "versionType": "semver" }, { "version": "6.11", "lessThanOrEqual": "*", "status": "unaffected", "versionType": "original_commit_for_fix" } ] } ], "cpeApplicability": [ { "nodes": [ { "operator": "OR", "negate": false, "cpeMatch": [ { "vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "4.3", "versionEndExcluding": "6.6.51" }, { "vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "4.3", "versionEndExcluding": "6.10.10" }, { "vulnerable": true, "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", "versionStartIncluding": "4.3", "versionEndExcluding": "6.11" } ] } ] } ], "references": [ { "url": "https://git.kernel.org/stable/c/3c6b4bcf37845c9359aed926324bed66bdd2448d" }, { "url": "https://git.kernel.org/stable/c/98cc18b1b71e23fe81a5194ed432b20c2d81a01a" }, { "url": "https://git.kernel.org/stable/c/71c186efc1b2cf1aeabfeff3b9bd5ac4c5ac14d8" } ], "title": "userfaultfd: fix checks for huge PMDs", "x_generator": { "engine": "bippy-1.2.0" } }, "adp": [ { "metrics": [ { "other": { "type": "ssvc", "content": { "timestamp": "2024-09-29T14:28:53.563201Z", "id": "CVE-2024-46787", "options": [ { "Exploitation": "none" }, { "Automatable": "no" }, { "Technical Impact": "partial" } ], "role": "CISA Coordinator", "version": "2.0.3" } } } ], "title": "CISA ADP Vulnrichment", "providerMetadata": { "orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0", "shortName": "CISA-ADP", "dateUpdated": "2024-09-29T14:29:08.018Z" } } ] } }