Look up another CVE · open CVE-2024-46787 in the interactive tracker
CVE-2024-46787
userfaultfd: fix checks for huge PMDs
Published 2024-09-18 (2 years ago) by the Linux kernel CNA. Subsystem mm. Debian urgency: not yet assigned.
What to do, per Debian release
Debian's own status for CVE-2024-46787 in every release this site tracks, and what each one means in practice. Every status below is the one the Debian Security Team publishes for this CVE. source
| Release | Status | Version | What this means |
|---|---|---|---|
| Debian 12bookworm | vulnerable | 6.1.190-1 | No fix published anywhere yet, neither in Debian nor upstream for the series bookworm tracks. Watch the Debian tracker page for this CVE. Advisories for bookworm have historically landed a median of 23 days after publication. |
| Debian 13trixie | fixed | 6.10.11-1 | Update the kernel and reboot. Fixed in 6.10.11-1. A running kernel keeps the old code until the machine restarts. |
| Debian 14forky | fixed | 6.10.11-1 | Update the kernel and reboot. Fixed in 6.10.11-1. A running kernel keeps the old code until the machine restarts. |
| Debian unstablesid | fixed | 6.10.11-1 | Update the kernel and reboot. Fixed in 6.10.11-1. A running kernel keeps the old code until the machine restarts. |
The vulnerability
What the bug is
Patch series "userfaultfd: fix races around pmd_trans_huge() check", v2. The pmd_trans_huge() code in mfill_atomic() is wrong in three different ways depending on kernel version: 1. The pmd_trans_huge() check is racy and can lead to a BUG_ON() (if you hit the right two race windows) - I've tested this in a kernel build with some extra mdelay() calls. See the commit message for a description of the race scenario. On older kernels (before 6.5), I think the same bug can even theoretically lead to accessing transhuge page contents as a page table if you hit the right 5 narrow race windows (I haven't tested this case). 2. As pointed out by Qi Zheng, pmd_trans_huge() is not sufficient for detecting PMDs that don't point to page tables. On older kernels (before 6.5), you'd just have to win a single fairly wide race to hit this. I've tested this on 6.1 stable by racing migration (with a mdelay() patched into try_to_migrate()) against UFFDIO_ZEROPAGE - on my x86 VM, that causes a kernel oops in ptlock_ptr(). 3. On newer kernels (>=6.5), for shmem mappings, khugepaged is allowed to yank page tables out from under us (though I haven't tested that), so I think the BUG_ON() checks in mfill_atomic() are just wrong. I decided to write two separate fixes for these (one fix for bugs 1+2, one fix for bug 3), so that the first fix can be backported to kernels affected by bugs 1+2. This patch (of 2): This fixes two issues. I discovered that the following race can occur: mfill_atomic other thread ============ ============ <zap PMD> pmdp_get_lockless() [reads none pmd] <bail if trans_huge> <if none:> <pagefault creates transhuge zeropage> __pte_alloc [no-op] <zap PMD> <bail if pmd_trans_huge(*dst_pmd)> BUG_ON(pmd_none(*dst_pmd)) I have experimentally verified this in a kernel with extra mdelay() calls; the BUG_ON(pmd_none(*dst_pmd)) triggers. On kernels newer than commit 0d940a9b270b ("mm/pgtable: allow pte_offset_map[_lock]() to fail"), this can't lead to anything worse than a BUG_ON(), since the page table access helpers are actually designed to deal with page tables concurrently disappearing; but on older kernels (<=6.4), I think we could probably theoretically race past the two BUG_ON() checks and end up treating a hugepage as a page table. The second issue is that, as Qi Zheng pointed out, there are other types of huge PMDs that pmd_trans_huge() can't catch: devmap PMDs and swap PMDs (in particular, migration PMDs). On <=6.4, this is worse than the first issue: If mfill_atomic() runs on a PMD that contains a migration entry (which just requires winning a single, fairly wide race), it will pass the PMD to pte_offset_map_lock(), which assumes that the PMD points to a page table. Breakage follows: First, the kernel tries to take the PTE lock (which will crash or maybe worse if there is no "struct page" for the address bits in the migration entry PMD - I think at least on X86 there usually is no corresponding "struct page" thanks to the PTE inversion mitigation, amd64 looks different). If that didn't crash, the kernel would next try to write a PTE into what it wrongly thinks is a page table. As part of fixing these issues, get rid of the check for pmd_trans_huge() before __pte_alloc() - that's redundant, we're going to have to check for that after the __pte_alloc() anyway. Backport note: pmdp_get_lockless() is pmd_read_atomic() in older kernels.
Where it lives
The fix touches these files. If the subsystem is one you do not use, the practical exposure is lower, though the package is still the vulnerable one.
mm/userfaultfd.c
- What this is
- MEMORY MANAGEMENT - USERFAULTFDMAINTAINERS section matched on mm/userfaultfd.c · linux-mm@kvack.org · Maintained
Check it on the machine
grep -w CONFIG_USERFAULTFD /boot/config-$(uname -r)Reads the booted kernel's own configuration, on the machine and nowhere else. =y is built in, =m is a module, and an is not set line means the code is not there.
Show whether Debian builds this, per release
Debian builds this code. Every release below compiles the affected file into the kernels it ships.
- bookworm, trixie, forky, sidy
- y
- built into the kernel image on every flavour - only a patch removes it
- Build switch
- CONFIG_USERFAULTFD
From the configuration Debian publishes for every kernel flavour it builds. It covers only the architectures Debian ships a linux-config package for, and nothing about a kernel you built yourself. Debian's own status above stays authoritative about the source package.MAINTAINERS kbuild Makefiles Debian linux-config packages
Signals
- Published
- 2024-09-18 (2 years ago)source
- CVSS
- 7.8 high
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:Hsource recompute it - Attack vector
- local (AV:L)source
- EPSS
- 0.22%, higher than 11.7% of all CVEssource
- CISA KEV
- not listedthe KEV catalogue
- Debian urgency
- not yet assignedsource
- Subsystem
- mm
Upstream fixes
The versions the CNA records as introducing and fixing this, and the commit for each. source
| Introduced | Fixed in | Commit |
|---|---|---|
| 4.3 | 6.6.51 | 3c6b4bcf3784 |
| 4.3 | 6.10.10 | 98cc18b1b71e |
| 4.3 | 6.11 | 71c186efc1b2 |
Why the CNA scored it this way
Quoted from the kernel CNA's own scoring file, word for word. A highlighted metric carries the most severe value CVSS v3.1 defines for it, which is what pushes the score up. source
- AV:LAttack vector, local
- 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`.
- AC:LAttack complexity, low
- 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.
- PR:LPrivileges required, low
- `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.
- UI:NUser interaction, none
- 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.
- S:UScope, unchanged
- 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.
- C:HConfidentiality impact, high
- 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.
- I:HIntegrity impact, high
- `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.
- A:HAvailability impact, high
- 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.