* [PATCH v2] x86/mm/pat: take cpa_lock around large-page collapse
@ 2026-07-15 18:34 Denis V. Lunev
2026-07-16 5:54 ` Mike Rapoport
2026-07-16 8:01 ` [tip: x86/mm] x86/mm/pat: Take " tip-bot2 for Denis V. Lunev
0 siblings, 2 replies; 3+ messages in thread
From: Denis V. Lunev @ 2026-07-15 18:34 UTC (permalink / raw)
To: Dave Hansen, Andy Lutomirski, Peter Zijlstra, x86
Cc: den, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H . Peter Anvin, Mike Rapoport, Juergen Gross, Kiryl Shutsemau,
linux-kernel
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 __change_page_attr+0x7cc/0x7e0
Write of size 8 at addr ffff888181139718 by task modprobe
...
The buggy address belongs to the physical page:
pfn:0x181139 ... page_type: f2(table)
cpa_collapse_large_pages() rebuilds a leaf PMD from its 4K PTEs and
frees the old PTE-table pages, while __change_page_attr() fetches a
PTE pointer from a lockless lookup_address_in_pgd_attr() and writes
it with set_pte_atomic() 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)
------------------- -----------------------
execmem_make_temp_rw
set_memory_nx
__change_page_attr
split 2M -> 4K table P
kpte = &P[i] (lockless)
execmem_restore_rox
set_memory_rox (CPA_COLLAPSE)
cpa_collapse_large_pages
rebuild leaf PMD
flush_tlb_all
pagetable_free(P)
set_pte_atomic(kpte, ...)
-> writes into freed P
P is a page-table page (page_type: 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 flush_tlb_all() 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 __change_page_attr() 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 cpa_lock, the same lock __change_page_attr() now takes
unconditionally since commit ("x86/mm/pat: stop gating cpa_lock on
debug_pagealloc_enabled()"), so a concurrent walker can no longer
hold a pointer into a table the collapse is about to free.
Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation")
Signed-off-by: Denis V. Lunev <den@openvz.org>
Acked-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
v2:
- drop the debug_pagealloc_enabled() skip and its comment: now that
__change_page_attr() takes cpa_lock unconditionally, the skip is no
longer needed to avoid locking only one side of the race
arch/x86/mm/pat/set_memory.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c
index e8316f5ffa8a..6dda81a629d6 100644
--- a/arch/x86/mm/pat/set_memory.c
+++ b/arch/x86/mm/pat/set_memory.c
@@ -417,6 +417,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)
int collapsed = 0;
int i;
+ spin_lock(&cpa_lock);
+
if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) {
for (i = 0; i < cpa->numpages; i++)
collapsed += collapse_large_pages(__cpa_addr(cpa, i),
@@ -430,8 +432,10 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)
collapsed += collapse_large_pages(addr, &pgtables);
}
- if (!collapsed)
+ if (!collapsed) {
+ spin_unlock(&cpa_lock);
return;
+ }
flush_tlb_all();
@@ -439,6 +443,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)
list_del(&ptdesc->pt_list);
pagetable_free(ptdesc);
}
+
+ spin_unlock(&cpa_lock);
}
static void cpa_flush(struct cpa_data *cpa, int cache)
base-commit: 4a0e2d0aa6a44155f9bf0289be3dd94024cee3b4
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH v2] x86/mm/pat: take cpa_lock around large-page collapse
2026-07-15 18:34 [PATCH v2] x86/mm/pat: take cpa_lock around large-page collapse Denis V. Lunev
@ 2026-07-16 5:54 ` Mike Rapoport
2026-07-16 8:01 ` [tip: x86/mm] x86/mm/pat: Take " tip-bot2 for Denis V. Lunev
1 sibling, 0 replies; 3+ messages in thread
From: Mike Rapoport @ 2026-07-16 5:54 UTC (permalink / raw)
To: Denis V. Lunev
Cc: Dave Hansen, Andy Lutomirski, Peter Zijlstra, x86,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, H . Peter Anvin,
Juergen Gross, Kiryl Shutsemau, linux-kernel
On Wed, Jul 15, 2026 at 08:34:52PM +0200, Denis V. Lunev wrote:
> 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 __change_page_attr+0x7cc/0x7e0
> Write of size 8 at addr ffff888181139718 by task modprobe
> ...
> The buggy address belongs to the physical page:
> pfn:0x181139 ... page_type: f2(table)
...
> Serialize the collapse - the PMD rebuild, TLB flush and PTE-table
> free - under cpa_lock, the same lock __change_page_attr() now takes
> unconditionally since commit ("x86/mm/pat: stop gating cpa_lock on
> debug_pagealloc_enabled()"), so a concurrent walker can no longer
> hold a pointer into a table the collapse is about to free.
>
> Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation")
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> Acked-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
I though I acked v1, but apparently I didn't :/
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 3+ messages in thread* [tip: x86/mm] x86/mm/pat: Take cpa_lock around large-page collapse
2026-07-15 18:34 [PATCH v2] x86/mm/pat: take cpa_lock around large-page collapse Denis V. Lunev
2026-07-16 5:54 ` Mike Rapoport
@ 2026-07-16 8:01 ` tip-bot2 for Denis V. Lunev
1 sibling, 0 replies; 3+ messages in thread
From: tip-bot2 for Denis V. Lunev @ 2026-07-16 8:01 UTC (permalink / raw)
To: linux-tip-commits
Cc: Denis V. Lunev, Dave Hansen, Kiryl Shutsemau (Meta), x86,
linux-kernel
The following commit has been merged into the x86/mm branch of tip:
Commit-ID: 1aac65f3e651334259ecb2a5f5ddb81c01f02599
Gitweb: https://git.kernel.org/tip/1aac65f3e651334259ecb2a5f5ddb81c01f02599
Author: Denis V. Lunev <den@openvz.org>
AuthorDate: Wed, 15 Jul 2026 20:34:52 +02:00
Committer: Dave Hansen <dave.hansen@linux.intel.com>
CommitterDate: Wed, 15 Jul 2026 13:06:55 -07:00
x86/mm/pat: Take cpa_lock 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 __change_page_attr+0x7cc/0x7e0
Write of size 8 at addr ffff888181139718 by task modprobe
...
The buggy address belongs to the physical page:
pfn:0x181139 ... page_type: f2(table)
cpa_collapse_large_pages() rebuilds a leaf PMD from its 4K PTEs and
frees the old PTE-table pages, while __change_page_attr() fetches a
PTE pointer from a lockless lookup_address_in_pgd_attr() and writes
it with set_pte_atomic() 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)
------------------- -----------------------
execmem_make_temp_rw
set_memory_nx
__change_page_attr
split 2M -> 4K table P
kpte = &P[i] (lockless)
execmem_restore_rox
set_memory_rox (CPA_COLLAPSE)
cpa_collapse_large_pages
rebuild leaf PMD
flush_tlb_all
pagetable_free(P)
set_pte_atomic(kpte, ...)
-> writes into freed P
P is a page-table page (page_type: 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 flush_tlb_all() 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 __change_page_attr() 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 cpa_lock, the same lock __change_page_attr() now takes
unconditionally since commit ("x86/mm/pat: stop gating cpa_lock on
debug_pagealloc_enabled()"), so a concurrent walker can no longer
hold a pointer into a table the collapse is about to free.
Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation")
Signed-off-by: Denis V. Lunev <den@openvz.org>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Acked-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Link: https://patch.msgid.link/20260715183453.2381141-1-den@openvz.org
---
arch/x86/mm/pat/set_memory.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c
index e9b4083..b1e780a 100644
--- a/arch/x86/mm/pat/set_memory.c
+++ b/arch/x86/mm/pat/set_memory.c
@@ -417,6 +417,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)
int collapsed = 0;
int i;
+ spin_lock(&cpa_lock);
+
if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) {
for (i = 0; i < cpa->numpages; i++)
collapsed += collapse_large_pages(__cpa_addr(cpa, i),
@@ -430,8 +432,10 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)
collapsed += collapse_large_pages(addr, &pgtables);
}
- if (!collapsed)
+ if (!collapsed) {
+ spin_unlock(&cpa_lock);
return;
+ }
flush_tlb_all();
@@ -439,6 +443,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)
list_del(&ptdesc->pt_list);
pagetable_free(ptdesc);
}
+
+ spin_unlock(&cpa_lock);
}
static void cpa_flush(struct cpa_data *cpa, int cache)
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-16 8:01 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-15 18:34 [PATCH v2] x86/mm/pat: take cpa_lock around large-page collapse Denis V. Lunev
2026-07-16 5:54 ` Mike Rapoport
2026-07-16 8:01 ` [tip: x86/mm] x86/mm/pat: Take " tip-bot2 for Denis V. Lunev
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.