* [PATCH v4 0/3] mm: khugepaged: fix tracepoint UAF
@ 2026-08-28 5:59 Vernon Yang
2026-08-28 5:59 ` [PATCH v4 1/3] mm: khugepaged: fix swap entry value to folio_pfn() Vernon Yang
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Vernon Yang @ 2026-08-28 5:59 UTC (permalink / raw)
To: akpm, david, ljs
Cc: nico.pache, ryan.roberts, dev.jain, baohua, lance.yang,
usama.arif, zokeefe, linux-kernel, linux-mm, stable, Vernon Yang
From: Vernon Yang <yanglincheng@kylinos.cn>
The khugepaged tracepoints take a folio pointer and call folio_pfn(),
but by then the folio may no longer be valid: freed after folio_put(),
folio_unlock() or pte_unmap_unlock(), or not a folio at all but an
xarray-encoded swap entry. On classic SPARSEMEM, dereferencing it oopses
khugepaged as soon as the trace event is enabled; on other memory models
it merely prints a bogus pfn.
Pass the pfn to the tracepoints directly, captured while the folio is
still pinned, closing the use-after-free windows in
mm_khugepaged_scan_file(), mm_khugepaged_scan_pmd() and
mm_khugepaged_collapse_file().
This series is based on mm-new.
V3 -> V4:
- Only trace the PFN if it really was problematic.
- Calling the respective trace_xxx() functions separately on success and
failure.
- Set new_pfn once after successful alloc_charge_folio().
V2 -> V3:
- Place folio_pfn() inside the xas_for_each() loop in PATCH#1.
- Already defaulted the pfn value to -1, to simple it in PATCH#2.
V1 -> V2:
- Instead of passing the folio, just pass the pfn directly.
- Using the folio_pfn() before dropping the reference or the page table
lock.
V3 : https://lore.kernel.org/linux-mm/20260824092935.73892-1-vernon2gm@gmail.com/
V2 : https://lore.kernel.org/linux-mm/20260815051924.194810-1-vernon2gm@gmail.com/
V1 : https://lore.kernel.org/linux-mm/20260811133655.267739-1-vernon2gm@gmail.com/
Vernon Yang (3):
mm: khugepaged: fix swap entry value to folio_pfn()
mm: khugepaged: fix folio is used after pte_unmap_unlock()
mm: khugepaged: fix folio is used after folio_put/unlock()
include/trace/events/huge_memory.h | 18 ++++++++---------
mm/khugepaged.c | 32 +++++++++++++++++++++++++-----
2 files changed, 36 insertions(+), 14 deletions(-)
base-commit: 1a46b1e97bde62afa7d925bb0dcd9f9748a1d7c3
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v4 1/3] mm: khugepaged: fix swap entry value to folio_pfn()
2026-08-28 5:59 [PATCH v4 0/3] mm: khugepaged: fix tracepoint UAF Vernon Yang
@ 2026-08-28 5:59 ` Vernon Yang
2026-08-28 5:59 ` [PATCH v4 2/3] mm: khugepaged: fix folio is used after pte_unmap_unlock() Vernon Yang
2026-08-28 5:59 ` [PATCH v4 3/3] mm: khugepaged: fix folio is used after folio_put/unlock() Vernon Yang
2 siblings, 0 replies; 4+ messages in thread
From: Vernon Yang @ 2026-08-28 5:59 UTC (permalink / raw)
To: akpm, david, ljs
Cc: nico.pache, ryan.roberts, dev.jain, baohua, lance.yang,
usama.arif, zokeefe, linux-kernel, linux-mm, stable, Vernon Yang
From: Vernon Yang <yanglincheng@kylinos.cn>
When the swap entries found exceed max_ptes_swap, the loop is left via
break with folio still holding the xarray value that encodes the swap
entry, not valid folio pointer.
That value is passed to trace_mm_khugepaged_scan_file(), which feeds it
to folio_pfn(). On FLATMEM and SPARSEMEM_VMEMMAP, the page_to_pfn() is
plain pointer arithmetic, so the trace event merely prints bogus
scan_pfn. On classic SPARSEMEM, the page_to_pfn() reads page->flags,
dereferencing the tiny encoded integer and oopsing khugepaged whenever
the trace event is enabled.
So when folio is the swap entry value, simply set pfn to -1, just like
exhausted scan naturally.
And the folio_put() has maybe dropped the last reference of folio. The
trace_mm_khugepaged_scan_file() is left with a dangling folio pointer.
so using the folio_pfn() before dropping the reference, closing
use-after-free window.
About calling the respective trace_xxx() functions separately on success
and failure, refer to [1].
[1] https://lore.kernel.org/linux-mm/ao6jVbVHLUmuY2UA@gremlin/
Fixes: d41fd2016ed0 ("mm/khugepaged: add tracepoint to hpage_collapse_scan_file()")
Cc: stable@vger.kernel.org
Signed-off-by: Vernon Yang <yanglincheng@kylinos.cn>
---
include/trace/events/huge_memory.h | 6 +++---
mm/khugepaged.c | 11 ++++++++++-
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/include/trace/events/huge_memory.h b/include/trace/events/huge_memory.h
index 5a48c5406cce..7b526528f85b 100644
--- a/include/trace/events/huge_memory.h
+++ b/include/trace/events/huge_memory.h
@@ -178,10 +178,10 @@ TRACE_EVENT(mm_collapse_huge_page_swapin,
TRACE_EVENT(mm_khugepaged_scan_file,
- TP_PROTO(struct mm_struct *mm, struct folio *folio, struct file *file,
+ TP_PROTO(struct mm_struct *mm, unsigned long pfn, struct file *file,
int present, int swap, int result),
- TP_ARGS(mm, folio, file, present, swap, result),
+ TP_ARGS(mm, pfn, file, present, swap, result),
TP_STRUCT__entry(
__field(struct mm_struct *, mm)
@@ -194,7 +194,7 @@ TRACE_EVENT(mm_khugepaged_scan_file,
TP_fast_assign(
__entry->mm = mm;
- __entry->pfn = folio ? folio_pfn(folio) : -1;
+ __entry->pfn = pfn;
__assign_str(filename);
__entry->present = present;
__entry->swap = swap;
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 75639298efc2..b597a3e68606 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2683,6 +2683,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
int present, swap;
int node = NUMA_NO_NODE;
enum scan_result result = SCAN_SUCCEED;
+ unsigned long failed_pfn = -1;
present = 0;
swap = 0;
@@ -2715,6 +2716,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
if (is_pmd_order(folio_order(folio))) {
result = SCAN_PTE_MAPPED_HUGEPAGE;
+ failed_pfn = folio_pfn(folio);
/*
* PMD-sized THP implies that we can only try
* retracting the PTE table.
@@ -2726,6 +2728,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
node = folio_nid(folio);
if (collapse_scan_abort(node, cc)) {
result = SCAN_SCAN_ABORT;
+ failed_pfn = folio_pfn(folio);
folio_put(folio);
break;
}
@@ -2733,12 +2736,14 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
if (!folio_test_lru(folio)) {
result = SCAN_PAGE_LRU;
+ failed_pfn = folio_pfn(folio);
folio_put(folio);
break;
}
if (folio_expected_ref_count(folio) + 1 != folio_ref_count(folio)) {
result = SCAN_PAGE_COUNT;
+ failed_pfn = folio_pfn(folio);
folio_put(folio);
break;
}
@@ -2771,9 +2776,13 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
} else {
result = collapse_file(mm, addr, file, start, cc);
}
+ trace_mm_khugepaged_scan_file(mm, -1, file, present, swap,
+ SCAN_SUCCEED);
+ } else {
+ trace_mm_khugepaged_scan_file(mm, failed_pfn, file, present,
+ swap, result);
}
- trace_mm_khugepaged_scan_file(mm, folio, file, present, swap, result);
return result;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v4 2/3] mm: khugepaged: fix folio is used after pte_unmap_unlock()
2026-08-28 5:59 [PATCH v4 0/3] mm: khugepaged: fix tracepoint UAF Vernon Yang
2026-08-28 5:59 ` [PATCH v4 1/3] mm: khugepaged: fix swap entry value to folio_pfn() Vernon Yang
@ 2026-08-28 5:59 ` Vernon Yang
2026-08-28 5:59 ` [PATCH v4 3/3] mm: khugepaged: fix folio is used after folio_put/unlock() Vernon Yang
2 siblings, 0 replies; 4+ messages in thread
From: Vernon Yang @ 2026-08-28 5:59 UTC (permalink / raw)
To: akpm, david, ljs
Cc: nico.pache, ryan.roberts, dev.jain, baohua, lance.yang,
usama.arif, zokeefe, linux-kernel, linux-mm, stable, Vernon Yang
From: Vernon Yang <yanglincheng@kylinos.cn>
After the page table lock has dropped, the folio can be freed
concurrently. The trace_mm_khugepaged_scan_pmd() is left with
a dangling folio pointer.
So using the folio_pfn() before dropping the page table lock, closing
use-after-free window.
And other pre-existing bug, When the `for (i = 0; i < HPAGE_PMD_NR; i++)`
iteration to terminate and the folio operation preceding is normal, but
pfn will be incorrect. so we really only trace the PFN if it really was
problematic.
About calling the respective trace_xxx() functions separately on success
and failure, refer to [1].
[1] https://lore.kernel.org/linux-mm/ao6jVbVHLUmuY2UA@gremlin/
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Fixes: 7d2eba0557c1 ("mm: add tracepoint for scanning pages")
Cc: stable@vger.kernel.org
Signed-off-by: Vernon Yang <yanglincheng@kylinos.cn>
---
include/trace/events/huge_memory.h | 6 +++---
mm/khugepaged.c | 17 ++++++++++++++---
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/include/trace/events/huge_memory.h b/include/trace/events/huge_memory.h
index 7b526528f85b..fa828967e1fb 100644
--- a/include/trace/events/huge_memory.h
+++ b/include/trace/events/huge_memory.h
@@ -55,10 +55,10 @@ SCAN_STATUS
TRACE_EVENT(mm_khugepaged_scan_pmd,
- TP_PROTO(struct mm_struct *mm, struct folio *folio,
+ TP_PROTO(struct mm_struct *mm, unsigned long pfn,
int referenced, int none_or_zero, int status, int unmapped),
- TP_ARGS(mm, folio, referenced, none_or_zero, status, unmapped),
+ TP_ARGS(mm, pfn, referenced, none_or_zero, status, unmapped),
TP_STRUCT__entry(
__field(struct mm_struct *, mm)
@@ -71,7 +71,7 @@ TRACE_EVENT(mm_khugepaged_scan_pmd,
TP_fast_assign(
__entry->mm = mm;
- __entry->pfn = folio ? folio_pfn(folio) : -1;
+ __entry->pfn = pfn;
__entry->referenced = referenced;
__entry->none_or_zero = none_or_zero;
__entry->status = status;
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index b597a3e68606..4d360ae87769 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1612,6 +1612,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
enum scan_result result = SCAN_FAIL;
struct page *page = NULL;
struct folio *folio = NULL;
+ unsigned long failed_pfn = -1;
unsigned long addr;
unsigned long enabled_orders;
spinlock_t *ptl;
@@ -1706,11 +1707,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
result = SCAN_PAGE_LAZYFREE;
+ failed_pfn = folio_pfn(folio);
goto out_unmap;
}
if (!folio_test_anon(folio)) {
result = SCAN_PAGE_ANON;
+ failed_pfn = folio_pfn(folio);
goto out_unmap;
}
@@ -1721,6 +1724,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
if (folio_maybe_mapped_shared(folio)) {
if (++shared > max_ptes_shared) {
result = SCAN_EXCEED_SHARED_PTE;
+ failed_pfn = folio_pfn(folio);
count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SHARED_PTE,
MTHP_STAT_COLLAPSE_EXCEED_SHARED);
goto out_unmap;
@@ -1738,15 +1742,18 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
node = folio_nid(folio);
if (collapse_scan_abort(node, cc)) {
result = SCAN_SCAN_ABORT;
+ failed_pfn = folio_pfn(folio);
goto out_unmap;
}
cc->node_load[node]++;
if (!folio_test_lru(folio)) {
result = SCAN_PAGE_LRU;
+ failed_pfn = folio_pfn(folio);
goto out_unmap;
}
if (folio_test_locked(folio)) {
result = SCAN_PAGE_LOCK;
+ failed_pfn = folio_pfn(folio);
goto out_unmap;
}
@@ -1759,6 +1766,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
*/
if (folio_expected_ref_count(folio) != folio_ref_count(folio)) {
result = SCAN_PAGE_COUNT;
+ failed_pfn = folio_pfn(folio);
goto out_unmap;
}
@@ -1782,10 +1790,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
unmapped, cc, enabled_orders);
/* mmap_lock was released above, set lock_dropped */
*lock_dropped = true;
- }
+ trace_mm_khugepaged_scan_pmd(mm, -1, referenced, none_or_zero,
+ SCAN_SUCCEED, unmapped);
+ } else {
out:
- trace_mm_khugepaged_scan_pmd(mm, folio, referenced,
- none_or_zero, result, unmapped);
+ trace_mm_khugepaged_scan_pmd(mm, failed_pfn, referenced,
+ none_or_zero, result, unmapped);
+ }
return result;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v4 3/3] mm: khugepaged: fix folio is used after folio_put/unlock()
2026-08-28 5:59 [PATCH v4 0/3] mm: khugepaged: fix tracepoint UAF Vernon Yang
2026-08-28 5:59 ` [PATCH v4 1/3] mm: khugepaged: fix swap entry value to folio_pfn() Vernon Yang
2026-08-28 5:59 ` [PATCH v4 2/3] mm: khugepaged: fix folio is used after pte_unmap_unlock() Vernon Yang
@ 2026-08-28 5:59 ` Vernon Yang
2 siblings, 0 replies; 4+ messages in thread
From: Vernon Yang @ 2026-08-28 5:59 UTC (permalink / raw)
To: akpm, david, ljs
Cc: nico.pache, ryan.roberts, dev.jain, baohua, lance.yang,
usama.arif, zokeefe, linux-kernel, linux-mm, stable, Vernon Yang
From: Vernon Yang <yanglincheng@kylinos.cn>
On the rollback path, folio_put() has already dropped the last reference
of new_folio. On the success path, new_folio is already unlocked and can
be freed concurrently. The trace_mm_khugepaged_collapse_file() is left
with a dangling folio pointer.
So using the folio_pfn() before dropping the reference, closing
use-after-free window.
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Fixes: 4c9473e87e75 ("mm/khugepaged: add tracepoint to collapse_file()")
Cc: stable@vger.kernel.org
Signed-off-by: Vernon Yang <yanglincheng@kylinos.cn>
---
include/trace/events/huge_memory.h | 6 +++---
mm/khugepaged.c | 4 +++-
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/include/trace/events/huge_memory.h b/include/trace/events/huge_memory.h
index fa828967e1fb..5fb4d92cfd84 100644
--- a/include/trace/events/huge_memory.h
+++ b/include/trace/events/huge_memory.h
@@ -211,10 +211,10 @@ TRACE_EVENT(mm_khugepaged_scan_file,
);
TRACE_EVENT(mm_khugepaged_collapse_file,
- TP_PROTO(struct mm_struct *mm, struct folio *new_folio, pgoff_t index,
+ TP_PROTO(struct mm_struct *mm, unsigned long new_pfn, pgoff_t index,
unsigned long addr, bool is_shmem, struct file *file,
int nr, int result),
- TP_ARGS(mm, new_folio, index, addr, is_shmem, file, nr, result),
+ TP_ARGS(mm, new_pfn, index, addr, is_shmem, file, nr, result),
TP_STRUCT__entry(
__field(struct mm_struct *, mm)
__field(unsigned long, hpfn)
@@ -228,7 +228,7 @@ TRACE_EVENT(mm_khugepaged_collapse_file,
TP_fast_assign(
__entry->mm = mm;
- __entry->hpfn = new_folio ? folio_pfn(new_folio) : -1;
+ __entry->hpfn = new_pfn;
__entry->index = index;
__entry->addr = addr;
__entry->is_shmem = is_shmem;
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 4d360ae87769..52b4476898d9 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2256,6 +2256,7 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
struct address_space *mapping = file->f_mapping;
struct page *dst;
struct folio *folio, *tmp, *new_folio;
+ unsigned long new_pfn = -1;
pgoff_t index = 0, end = start + HPAGE_PMD_NR;
LIST_HEAD(pagelist);
XA_STATE_ORDER(xas, &mapping->i_pages, start, HPAGE_PMD_ORDER);
@@ -2275,6 +2276,7 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
result = alloc_charge_folio(&new_folio, mm, cc, HPAGE_PMD_ORDER);
if (result != SCAN_SUCCEED)
goto out;
+ new_pfn = folio_pfn(new_folio);
mapping_set_update(&xas, mapping);
@@ -2678,7 +2680,7 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
folio_put(new_folio);
out:
VM_BUG_ON(!list_empty(&pagelist));
- trace_mm_khugepaged_collapse_file(mm, new_folio, index, addr, is_shmem, file, HPAGE_PMD_NR, result);
+ trace_mm_khugepaged_collapse_file(mm, new_pfn, index, addr, is_shmem, file, HPAGE_PMD_NR, result);
return result;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-28 6:00 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-28 5:59 [PATCH v4 0/3] mm: khugepaged: fix tracepoint UAF Vernon Yang
2026-08-28 5:59 ` [PATCH v4 1/3] mm: khugepaged: fix swap entry value to folio_pfn() Vernon Yang
2026-08-28 5:59 ` [PATCH v4 2/3] mm: khugepaged: fix folio is used after pte_unmap_unlock() Vernon Yang
2026-08-28 5:59 ` [PATCH v4 3/3] mm: khugepaged: fix folio is used after folio_put/unlock() Vernon Yang
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.