CVE-2026-93066: x86/mm/pat: Take cpa_lock around large-page collapse
In the Linux kernel, the following vulnerability has been resolved:
x86/mm/pat: Take cpalock around large-page collapse
Loading and unloading modules concurrently on several CPUs on a KASAN build, with a short delay injected at the CPA page-table lookup to widen the window, faults within minutes:
BUG: KASAN: use-after-free in changepageattr+0x7cc/0x7e0 Write of size 8 at addr ffff888181139718 by task modprobe ... The buggy address belongs to the physical page: pfn:0x181139 ... pagetype: f2(table)
cpacollapselargepages() rebuilds a leaf PMD from its 4K PTEs and frees the old PTE-table pages, while changepageattr() fetches a PTE pointer from a lockless lookupaddressinpgdattr() and writes it with setpteatomic() only later. When module text is served from a shared large ROX mapping the two run on the same PMD:
CPU A (module load) CPU B (module finalize) ------------------- ----------------------- execmemmaketemprw setmemorynx changepageattr split 2M -> 4K table P kpte = &P[i] (lockless) execmemrestorerox setmemoryrox (CPACOLLAPSE) cpacollapselargepages rebuild leaf PMD flushtlball pagetablefree(P) setpteatomic(kpte, ...) -> writes into freed P
P is a page-table page (pagetype: table), reused at once, so the write corrupts whatever got the page next: a bad-pte or bad-page splat, or a fatal fault once P has been turned into read-only text.
The flushtlball() before the free does not close this: its IPI only serializes against page-table walkers that run with interrupts off (e.g. GUP-fast); the walk in changepageattr() runs with interrupts on, so nothing stops it from holding a stale pointer into P.
Serialize the collapse - the PMD rebuild, TLB flush and PTE-table free - under cpalock, the same lock changepageattr() now takes unconditionally since commit ("x86/mm/pat: stop gating cpalock on debugpageallocenabled()"), so a concurrent walker can no longer hold a pointer into a table the collapse is about to free.
Affected Software
Event History
Frequently Asked Questions
What workload is most likely to trigger this issue?
The reported failure occurs when modules are loaded and unloaded concurrently on several CPUs. The race involves module text that is served from a shared large read-only executable mapping.
How can I identify a possible occurrence?
On a KASAN build, the reported symptom is a "BUG: KASAN: use-after-free" in __change_page_attr, involving a write through set_pte_atomic(). The fault was reproduced within minutes when a short delay was injected into the CPA page-table lookup path.
What operations race with each other?
One CPU can split a 2 MiB mapping into 4 KiB PTEs during module loading, retain a PTE pointer from a lockless lookup, and later write through it. Another CPU can restore the mapping to ROX and collapse it back to a large page, freeing the PTE-table page before that write occurs.