[PATCH v4 2/3] mm: khugepaged: fix folio is used after pte_unmap_unlock()
From: Vernon Yang
Date: Fri Aug 28 2026 - 02:01:49 EST
From: Vernon Yang <yanglincheng@xxxxxxxxxx>
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@xxxxxxxxxx>
Fixes: 7d2eba0557c1 ("mm: add tracepoint for scanning pages")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Vernon Yang <yanglincheng@xxxxxxxxxx>
---
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