The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing
@ 2026-07-10 10:49 Lorenzo Stoakes
  2026-07-10 10:49 ` [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion Lorenzo Stoakes
                   ` (3 more replies)
  0 siblings, 4 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-10 10:49 UTC (permalink / raw)
  To: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon
  Cc: David Carlier, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, Lorenzo Stoakes, stable,
	syzbot+fd95a72470f5a44e464c

Kernel page table walkers fall into two broad categories - those ranges
where no exclusion is required via walk_kernel_page_table_range_lockless()
and those where exclusion is required via walk_kernel_page_table_range()
or walk_page_range_debug().

The former category is used only by arm64 arch code operating on ranges it
both wholly owns and does not concurrently write.

The latter category consists of kernel page table walkers operating on
ranges that are wholly owned (but which need exclusion against concurrent
writers).

The lock used for exclusion is the mmap lock, and for kernel ranges this
the mmap lock on init_mm.

ptdump is a special case being both the only user of
walk_page_range_debug(), and the only case in which it walks ranges it does
not own.

This presents a problem, as page tables may be freed under ptdump. And
indeed there is a use-after-free bug in the kernel as a result, which this
series addresses.

vmap promotes page tables to huge leaf entries where possible, freeing the
lower leaf page table when it does. It does this with no meaningful locks
held against concurrent ptdump walks.

As a result, use-after-free can currently occur. This series addresses the
issue by having the vmap huge promotion logic acquire the mmap read lock
while both setting the huge page table entry and freeing the prior leaf
page table.

The ptdump code already acquires the mmap write lock, so by doing so we
ensure that the ptdump walker only ever observes either the huge page table
entry or the existing page table entry, and nothing is freed underneath it.

A mitigation for this issue was already applied for arm64 in commit
a93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), which this series
has to deal with carefully.

This mitigation resolves the issue by acquiring the mmap read lock on
init_mm on vmap page table free if a ptdump is in progress.

However the fix in this series would cause a deadlock if we were to simply
apply it for arm64 without also reverting the change.

This is because vmap may acquire the read lock before ptdump attempts to
acquire the write lock, which then gets queued, and rwsem starvation rules
mean that the (unacknowledged) nested mmap read lock in the arm64 code
would also block, meaning the original read lock is never released and thus
deadlock.

This series works around this by #ifndef CONFIG_ARM64'ing the mmap read
lock in vmap logic, then partially reverting commit
a93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), keeping the
enablement of huge vmap support, and removing the ifdeffery with the
partial revert patch.

Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
---
Lorenzo Stoakes (2):
      mm/vmalloc: acquire init_mm read lock on huge vmap promotion
      Revert "arm64: Enable vmalloc-huge with ptdump"

 arch/arm64/include/asm/ptdump.h |  2 --
 arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
 arch/arm64/mm/ptdump.c          | 11 ++---------
 include/linux/mmap_lock.h       |  1 +
 mm/pagewalk.c                   | 22 +++++++++++----------
 mm/vmalloc.c                    | 41 ++++++++++++++++++++++++++++++---------
 6 files changed, 51 insertions(+), 69 deletions(-)
---
base-commit: a635d6748234582ea287c5ffeae28b9b23f91c7e
change-id: 20260710-series-vmap-race-fix-2a4cac988938

Cheers,
-- 
Lorenzo Stoakes <ljs@kernel.org>


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-10 10:49 [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing Lorenzo Stoakes
@ 2026-07-10 10:49 ` Lorenzo Stoakes
  2026-07-10 12:57   ` Lorenzo Stoakes
                     ` (2 more replies)
  2026-07-10 10:49 ` [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump" Lorenzo Stoakes
                   ` (2 subsequent siblings)
  3 siblings, 3 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-10 10:49 UTC (permalink / raw)
  To: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon
  Cc: David Carlier, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, Lorenzo Stoakes, stable,
	syzbot+fd95a72470f5a44e464c

Currently there is a nasty race between ptdump and vmap when attempting to
map a huge P4D, PMD or PUD entry.

ptdump is invoked by arch code to walk kernel or EFI page tables, either to
output it for debugging purposes, or to assert that there are no
W+X (i.e. executable writable pages) exposed in these ranges.

The feature is enabled generally via CONFIG_PTDUMP (whose implementation is
in mm/ptdump.c), and expose a debugfs interface for it if
CONFIG_PTDUMP_DEBUGFS is defined.

If CONFIG_PTDUMP is enabled, then /sys/kernel/debug/check_wx_pages is
enabled which checks kernel ranges to perform the W+X check. If
CONFIG_DEBUG_WX is enabled, this is done on boot.

(Note that arm32 implements its own page table walker and uses
CONFIG_ARM_DEBUG_WX and CONFIG_ARM_PTDUMP_DEBUGFS for this.)

The EFI implementations vary by architecture, but are not relevant to the
bug, as the issue is when kernel page ranges are walked.

ptdump_walk_pgd() holds both the mem hotplug lock and the mmap write lock
before invoking walk_page_range_debug(), however this runs into an issue
with vmalloc ranges.

When vmap maps a P4D, PUD or a PMD sized range and encounters an existing
P4d/PUD/PMD entry pointing to a PUD/PMD/PTE page table, it invokes
vmap_try_huge_[p4d,pud,pmd]() to try to convert it to a huge page table
mapping if possible.

However, when it does this, it holds no meaningful locks against other
kernel page table walkers, invoking [p4d,pud,pmd]_free_[pud,pmd,pte]_page()
which calls pagetable_free() and pagetable_free_kernel() in
turn (pte_fragment_free() for powerpc).

This means that a use-after-free becomes possible if the ptdump page table
walker happens to be walking a PUD, PMD or PTE page table after it has been
freed.

Since commit 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page
tables"), if CONFIG_ASYNC_KERNEL_PGTABLE_FREE is set,
pagetable_free_kernel() will batch the page table freeing operation,
otherwise it frees the page table directly.

While the KASAN report that syzbot highlighted indicated that the issue
arose in a workqueue introduced by this change, this is coincidental and
the commit did not alter the race which has existed for quite some time.

This patch resolves the issue by simply having
vmap_try_huge_[p4d,pud,pmd]() hold the mmap read lock on init_mm while
invoking [p4d,pud,pmd]_free_[pud,pmd,pte]_page() and
[p4d,pud,pmd]_set_huge().

This way, page table walkers either observe a newly promoted huge
P4D/PUD/PMD leaf entry or the prior PUD/PMD/PTE entry and never get passed
a dangling pointer, whether the page is freed asynchronously or not.

All other kernel page table walkers that touch vmalloc ranges either
exclusively own the memory walked or acquire the mmap lock, so this
correctly excludes those walkers.

We acquire the mmap read lock as a trylock, as this is an optimisation that
is permitted not to succeed, a race is very unlikely, and doing so
eliminates latency sleeping on the lock would have otherwise caused.

We also define a guard class for mmap_read_trylock() so we can use
cleanup.h to make the scope handling cleaner in the implementation.

One wrinkle here is commit fa93b45fd397 ("arm64: Enable vmalloc-huge with
ptdump"), which addresses the issue for arm64 only by explicitly acquiring
the mmap read lock on kernel page table freeing should a concurrent ptdump
be in progress.

This is problematic as vmap may acquire the mmap read lock prior to ptdump
attempting to acquire an mmap write lock, leading to a deadlock when the
mmap read lock is slept upon on page table freeing due to rwsem
anti-starvation.

We work around this by predicating the mmap lock being taken on
!CONFIG_ARM64 for the time being.

With this patch applied, a follow up will partially revert commit
fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump") and at that stage
remove the arm64 ifdeffery.

We also update walk_page_range_debug() to assert the mmap write lock
unconditionally and update the comment here to reflect this change.

The issue has existed as long as ptdump was available and vmap freed page
tables when promoting to a huge leaf entry, that is, since commit
b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") for
huge ioremap, and commit 121e6f3258fe ("mm/vmalloc: hugepage vmalloc
mappings") for huge vmalloc.

Since the former is the earlier of the two we choose that for our Fixes
tag.

This patch is based on work by David Carlier (linked), with gratitude!

Fixes: b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table")
Cc: <stable@vger.kernel.org>
Reported-by: syzbot+fd95a72470f5a44e464c@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/all/6a287988.39669fcc.33b062.00a0.GAE@google.com/T/
Link: https://lore.kernel.org/linux-mm/20260706203128.162335-1-devnexen@gmail.com/
Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
---
 include/linux/mmap_lock.h |  1 +
 mm/pagewalk.c             | 22 +++++++++++----------
 mm/vmalloc.c              | 50 ++++++++++++++++++++++++++++++++++++++---------
 3 files changed, 54 insertions(+), 19 deletions(-)

diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h
index 04b8f61ece5d..6b5c2390cc30 100644
--- a/include/linux/mmap_lock.h
+++ b/include/linux/mmap_lock.h
@@ -621,6 +621,7 @@ static inline void mmap_read_unlock(struct mm_struct *mm)
 
 DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
 	     mmap_read_lock(_T), mmap_read_unlock(_T))
+DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T))
 
 static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)
 {
diff --git a/mm/pagewalk.c b/mm/pagewalk.c
index 3ae2586ff45b..bbcfd68d0907 100644
--- a/mm/pagewalk.c
+++ b/mm/pagewalk.c
@@ -678,6 +678,8 @@ int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end
  * will also not lock the PTEs for the pte_entry() callback.
  *
  * This is for debugging purposes ONLY.
+ *
+ * The mmap write lock must be held.
  */
 int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
 			  unsigned long end, const struct mm_walk_ops *ops,
@@ -691,6 +693,16 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
 		.no_vma		= true
 	};
 
+	/*
+	 * When walking userland page tables, an mmap write lock must be held to
+	 * account for munmap() downgrading to an mmap read lock when tearing
+	 * down page tables.
+	 *
+	 * When walking kernel page tables, an mmap write lock must also be held
+	 * to account for page table freeing on vmap huge page mapping.
+	 */
+	mmap_assert_write_locked(mm);
+
 	/* For convenience, we allow traversal of kernel mappings. */
 	if (mm == &init_mm)
 		return walk_kernel_page_table_range(start, end, ops,
@@ -700,16 +712,6 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
 	if (!check_ops_safe(ops))
 		return -EINVAL;
 
-	/*
-	 * The mmap lock protects the page walker from changes to the page
-	 * tables during the walk.  However a read lock is insufficient to
-	 * protect those areas which don't have a VMA as munmap() detaches
-	 * the VMAs before downgrading to a read lock and actually tearing
-	 * down PTEs/page tables. In which case, the mmap write lock should
-	 * be held.
-	 */
-	mmap_assert_write_locked(mm);
-
 	return walk_pgd_range(start, end, &walk);
 }
 
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 1afca3568b9b..9d0f1fdd6af3 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -43,6 +43,7 @@
 #include <asm/tlbflush.h>
 #include <asm/shmparam.h>
 #include <linux/page_owner.h>
+#include <linux/cleanup.h>
 
 #define CREATE_TRACE_POINTS
 #include <trace/events/vmalloc.h>
@@ -158,10 +159,25 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
 	if (!IS_ALIGNED(phys_addr, PMD_SIZE))
 		return 0;
 
-	if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr))
-		return 0;
+	if (!pmd_present(*pmd))
+		return pmd_set_huge(pmd, phys_addr, prot);
 
-	return pmd_set_huge(pmd, phys_addr, prot);
+	/*
+	 * Kernel page table walkers either walk ranges they own exclusively
+	 * using the mmap lock for mutual exclusion, or hold the mmap write lock
+	 * on init_mm (ptdump being the motivating case).
+	 *
+	 * Therefore, acquire the mmap read lock to prevent use-after-free when
+	 * freeing page tables.
+	 */
+#ifndef CONFIG_ARM64
+	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
+#endif
+	{
+		if (!pmd_free_pte_page(pmd, addr))
+			return 0;
+		return pmd_set_huge(pmd, phys_addr, prot);
+	}
 }
 
 static int vmap_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
@@ -210,10 +226,18 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end,
 	if (!IS_ALIGNED(phys_addr, PUD_SIZE))
 		return 0;
 
-	if (pud_present(*pud) && !pud_free_pmd_page(pud, addr))
-		return 0;
+	if (!pud_present(*pud))
+		return pud_set_huge(pud, phys_addr, prot);
 
-	return pud_set_huge(pud, phys_addr, prot);
+	/* See comment in vmap_try_huge_pmd(). */
+#ifndef CONFIG_ARM64
+	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
+#endif
+	{
+		if (!pud_free_pmd_page(pud, addr))
+			return 0;
+		return pud_set_huge(pud, phys_addr, prot);
+	}
 }
 
 static int vmap_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
@@ -262,10 +286,18 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end,
 	if (!IS_ALIGNED(phys_addr, P4D_SIZE))
 		return 0;
 
-	if (p4d_present(*p4d) && !p4d_free_pud_page(p4d, addr))
-		return 0;
+	if (!p4d_present(*p4d))
+		return p4d_set_huge(p4d, phys_addr, prot);
 
-	return p4d_set_huge(p4d, phys_addr, prot);
+	/* See comment in vmap_try_huge_pmd(). */
+#ifndef CONFIG_ARM64
+	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
+#endif
+	{
+		if (!p4d_free_pud_page(p4d, addr))
+			return 0;
+		return p4d_set_huge(p4d, phys_addr, prot);
+	}
 }
 
 static int vmap_p4d_range(pgd_t *pgd, unsigned long addr, unsigned long end,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump"
  2026-07-10 10:49 [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing Lorenzo Stoakes
  2026-07-10 10:49 ` [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion Lorenzo Stoakes
@ 2026-07-10 10:49 ` Lorenzo Stoakes
  2026-07-11 10:15   ` Mike Rapoport
  2026-07-12  7:46   ` Dev Jain
  2026-07-10 11:44 ` [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing David CARLIER
  2026-07-12  7:20 ` Dev Jain
  3 siblings, 2 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-10 10:49 UTC (permalink / raw)
  To: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon
  Cc: David Carlier, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, Lorenzo Stoakes

This partially reverts commit fa93b45fd397 ("arm64: Enable vmalloc-huge
with ptdump"), retaining vmalloc-huge support but eliminating the now
redundant mitigation against a race between huge vmap page table freeing
and ptdump, as this issue has now been fixed at core.

We also simultaneously remove the arm64 ifdeffery when acquiring the mmap
read lock upon vmap huge page table promotion as it is no longer required.

Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
---
 arch/arm64/include/asm/ptdump.h |  2 --
 arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
 arch/arm64/mm/ptdump.c          | 11 ++---------
 mm/vmalloc.c                    | 15 +++-----------
 4 files changed, 9 insertions(+), 62 deletions(-)

diff --git a/arch/arm64/include/asm/ptdump.h b/arch/arm64/include/asm/ptdump.h
index 5b374a6ab34a..50a195eda8ed 100644
--- a/arch/arm64/include/asm/ptdump.h
+++ b/arch/arm64/include/asm/ptdump.h
@@ -7,8 +7,6 @@
 
 #include <linux/ptdump.h>
 
-DECLARE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
-
 #ifdef CONFIG_PTDUMP
 
 #include <linux/mm_types.h>
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index f2be501468ce..f723bcf68174 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -49,8 +49,6 @@
 #define NO_CONT_MAPPINGS	BIT(1)
 #define NO_EXEC_MAPPINGS	BIT(2)	/* assumes FEAT_HPDS is not used */
 
-DEFINE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
-
 u64 kimage_voffset __ro_after_init;
 EXPORT_SYMBOL(kimage_voffset);
 
@@ -1857,8 +1855,7 @@ int pmd_clear_huge(pmd_t *pmdp)
 	return 1;
 }
 
-static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
-			       bool acquire_mmap_lock)
+int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
 {
 	pte_t *table;
 	pmd_t pmd;
@@ -1870,25 +1867,13 @@ static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
 		return 1;
 	}
 
-	/* See comment in pud_free_pmd_page for static key logic */
 	table = pte_offset_kernel(pmdp, addr);
 	pmd_clear(pmdp);
 	__flush_tlb_kernel_pgtable(addr);
-	if (static_branch_unlikely(&arm64_ptdump_lock_key) && acquire_mmap_lock) {
-		mmap_read_lock(&init_mm);
-		mmap_read_unlock(&init_mm);
-	}
-
 	pte_free_kernel(NULL, table);
 	return 1;
 }
 
-int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
-{
-	/* If ptdump is walking the pagetables, acquire init_mm.mmap_lock */
-	return __pmd_free_pte_page(pmdp, addr, /* acquire_mmap_lock = */ true);
-}
-
 int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
 {
 	pmd_t *table;
@@ -1904,36 +1889,16 @@ int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
 	}
 
 	table = pmd_offset(pudp, addr);
-
-	/*
-	 * Our objective is to prevent ptdump from reading a PMD table which has
-	 * been freed. In this race, if pud_free_pmd_page observes the key on
-	 * (which got flipped by ptdump) then the mmap lock sequence here will,
-	 * as a result of the mmap write lock/unlock sequence in ptdump, give
-	 * us the correct synchronization. If not, this means that ptdump has
-	 * yet not started walking the pagetables - the sequence of barriers
-	 * issued by __flush_tlb_kernel_pgtable() guarantees that ptdump will
-	 * observe an empty PUD.
-	 */
-	pud_clear(pudp);
-	__flush_tlb_kernel_pgtable(addr);
-	if (static_branch_unlikely(&arm64_ptdump_lock_key)) {
-		mmap_read_lock(&init_mm);
-		mmap_read_unlock(&init_mm);
-	}
-
 	pmdp = table;
 	next = addr;
 	end = addr + PUD_SIZE;
 	do {
 		if (pmd_present(pmdp_get(pmdp)))
-			/*
-			 * PMD has been isolated, so ptdump won't see it. No
-			 * need to acquire init_mm.mmap_lock.
-			 */
-			__pmd_free_pte_page(pmdp, next, /* acquire_mmap_lock = */ false);
+			pmd_free_pte_page(pmdp, next);
 	} while (pmdp++, next += PMD_SIZE, next != end);
 
+	pud_clear(pudp);
+	__flush_tlb_kernel_pgtable(addr);
 	pmd_free(NULL, table);
 	return 1;
 }
diff --git a/arch/arm64/mm/ptdump.c b/arch/arm64/mm/ptdump.c
index 1c20144700d7..5a76c59b5ada 100644
--- a/arch/arm64/mm/ptdump.c
+++ b/arch/arm64/mm/ptdump.c
@@ -283,13 +283,6 @@ void note_page_flush(struct ptdump_state *pt_st)
 	note_page(pt_st, 0, -1, pte_val(pte_zero));
 }
 
-static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm)
-{
-	static_branch_inc(&arm64_ptdump_lock_key);
-	ptdump_walk_pgd(st, mm, NULL);
-	static_branch_dec(&arm64_ptdump_lock_key);
-}
-
 void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
 {
 	unsigned long end = ~0UL;
@@ -318,7 +311,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
 		}
 	};
 
-	arm64_ptdump_walk_pgd(&st.ptdump, info->mm);
+	ptdump_walk_pgd(&st.ptdump, info->mm, NULL);
 }
 
 static void __init ptdump_initialize(void)
@@ -360,7 +353,7 @@ bool ptdump_check_wx(void)
 		}
 	};
 
-	arm64_ptdump_walk_pgd(&st.ptdump, &init_mm);
+	ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
 
 	if (st.wx_pages || st.uxn_pages) {
 		pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 9d0f1fdd6af3..537ff7b3c412 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -170,10 +170,7 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
 	 * Therefore, acquire the mmap read lock to prevent use-after-free when
 	 * freeing page tables.
 	 */
-#ifndef CONFIG_ARM64
-	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
-#endif
-	{
+	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
 		if (!pmd_free_pte_page(pmd, addr))
 			return 0;
 		return pmd_set_huge(pmd, phys_addr, prot);
@@ -230,10 +227,7 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end,
 		return pud_set_huge(pud, phys_addr, prot);
 
 	/* See comment in vmap_try_huge_pmd(). */
-#ifndef CONFIG_ARM64
-	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
-#endif
-	{
+	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
 		if (!pud_free_pmd_page(pud, addr))
 			return 0;
 		return pud_set_huge(pud, phys_addr, prot);
@@ -290,10 +284,7 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end,
 		return p4d_set_huge(p4d, phys_addr, prot);
 
 	/* See comment in vmap_try_huge_pmd(). */
-#ifndef CONFIG_ARM64
-	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
-#endif
-	{
+	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
 		if (!p4d_free_pud_page(p4d, addr))
 			return 0;
 		return p4d_set_huge(p4d, phys_addr, prot);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing
  2026-07-10 10:49 [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing Lorenzo Stoakes
  2026-07-10 10:49 ` [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion Lorenzo Stoakes
  2026-07-10 10:49 ` [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump" Lorenzo Stoakes
@ 2026-07-10 11:44 ` David CARLIER
  2026-07-10 11:57   ` Lorenzo Stoakes
  2026-07-12  7:20 ` Dev Jain
  3 siblings, 1 reply; 21+ messages in thread
From: David CARLIER @ 2026-07-10 11:44 UTC (permalink / raw)
  To: Lorenzo Stoakes
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c

Hi Lorenzo,

On Fri, 10 Jul 2026 at 11:50, Lorenzo Stoakes <ljs@kernel.org> wrote:
>
> Kernel page table walkers fall into two broad categories - those ranges
> where no exclusion is required via walk_kernel_page_table_range_lockless()
> and those where exclusion is required via walk_kernel_page_table_range()
> or walk_page_range_debug().
>
> The former category is used only by arm64 arch code operating on ranges it
> both wholly owns and does not concurrently write.
>
> The latter category consists of kernel page table walkers operating on
> ranges that are wholly owned (but which need exclusion against concurrent
> writers).
>
> The lock used for exclusion is the mmap lock, and for kernel ranges this
> the mmap lock on init_mm.
>
> ptdump is a special case being both the only user of
> walk_page_range_debug(), and the only case in which it walks ranges it does
> not own.
>
> This presents a problem, as page tables may be freed under ptdump. And
> indeed there is a use-after-free bug in the kernel as a result, which this
> series addresses.
>
> vmap promotes page tables to huge leaf entries where possible, freeing the
> lower leaf page table when it does. It does this with no meaningful locks
> held against concurrent ptdump walks.
>
> As a result, use-after-free can currently occur. This series addresses the
> issue by having the vmap huge promotion logic acquire the mmap read lock
> while both setting the huge page table entry and freeing the prior leaf
> page table.
>
> The ptdump code already acquires the mmap write lock, so by doing so we
> ensure that the ptdump walker only ever observes either the huge page table
> entry or the existing page table entry, and nothing is freed underneath it.
>
> A mitigation for this issue was already applied for arm64 in commit
> a93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), which this series

seems it should be fa93b45fd397.

Cheers.
> has to deal with carefully.
>
> This mitigation resolves the issue by acquiring the mmap read lock on
> init_mm on vmap page table free if a ptdump is in progress.
>
> However the fix in this series would cause a deadlock if we were to simply
> apply it for arm64 without also reverting the change.
>
> This is because vmap may acquire the read lock before ptdump attempts to
> acquire the write lock, which then gets queued, and rwsem starvation rules
> mean that the (unacknowledged) nested mmap read lock in the arm64 code
> would also block, meaning the original read lock is never released and thus
> deadlock.
>
> This series works around this by #ifndef CONFIG_ARM64'ing the mmap read
> lock in vmap logic, then partially reverting commit
> a93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), keeping the
> enablement of huge vmap support, and removing the ifdeffery with the
> partial revert patch.
>
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> Lorenzo Stoakes (2):
>       mm/vmalloc: acquire init_mm read lock on huge vmap promotion
>       Revert "arm64: Enable vmalloc-huge with ptdump"
>
>  arch/arm64/include/asm/ptdump.h |  2 --
>  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
>  arch/arm64/mm/ptdump.c          | 11 ++---------
>  include/linux/mmap_lock.h       |  1 +
>  mm/pagewalk.c                   | 22 +++++++++++----------
>  mm/vmalloc.c                    | 41 ++++++++++++++++++++++++++++++---------
>  6 files changed, 51 insertions(+), 69 deletions(-)
> ---
> base-commit: a635d6748234582ea287c5ffeae28b9b23f91c7e
> change-id: 20260710-series-vmap-race-fix-2a4cac988938
>
> Cheers,
> --
> Lorenzo Stoakes <ljs@kernel.org>
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing
  2026-07-10 11:44 ` [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing David CARLIER
@ 2026-07-10 11:57   ` Lorenzo Stoakes
  0 siblings, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-10 11:57 UTC (permalink / raw)
  To: David CARLIER
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c

On Fri, Jul 10, 2026 at 12:44:20PM +0100, David CARLIER wrote:
> Hi Lorenzo,
>
> On Fri, 10 Jul 2026 at 11:50, Lorenzo Stoakes <ljs@kernel.org> wrote:
> >
> > Kernel page table walkers fall into two broad categories - those ranges
> > where no exclusion is required via walk_kernel_page_table_range_lockless()
> > and those where exclusion is required via walk_kernel_page_table_range()
> > or walk_page_range_debug().
> >
> > The former category is used only by arm64 arch code operating on ranges it
> > both wholly owns and does not concurrently write.
> >
> > The latter category consists of kernel page table walkers operating on
> > ranges that are wholly owned (but which need exclusion against concurrent
> > writers).
> >
> > The lock used for exclusion is the mmap lock, and for kernel ranges this
> > the mmap lock on init_mm.
> >
> > ptdump is a special case being both the only user of
> > walk_page_range_debug(), and the only case in which it walks ranges it does
> > not own.
> >
> > This presents a problem, as page tables may be freed under ptdump. And
> > indeed there is a use-after-free bug in the kernel as a result, which this
> > series addresses.
> >
> > vmap promotes page tables to huge leaf entries where possible, freeing the
> > lower leaf page table when it does. It does this with no meaningful locks
> > held against concurrent ptdump walks.
> >
> > As a result, use-after-free can currently occur. This series addresses the
> > issue by having the vmap huge promotion logic acquire the mmap read lock
> > while both setting the huge page table entry and freeing the prior leaf
> > page table.
> >
> > The ptdump code already acquires the mmap write lock, so by doing so we
> > ensure that the ptdump walker only ever observes either the huge page table
> > entry or the existing page table entry, and nothing is freed underneath it.
> >
> > A mitigation for this issue was already applied for arm64 in commit
> > a93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), which this series
>
> seems it should be fa93b45fd397.

Yeah oops, I typo'd that.

Andrew - could you fix that up for me? Thanks!

>
> Cheers.
> > has to deal with carefully.
> >
> > This mitigation resolves the issue by acquiring the mmap read lock on
> > init_mm on vmap page table free if a ptdump is in progress.
> >
> > However the fix in this series would cause a deadlock if we were to simply
> > apply it for arm64 without also reverting the change.
> >
> > This is because vmap may acquire the read lock before ptdump attempts to
> > acquire the write lock, which then gets queued, and rwsem starvation rules
> > mean that the (unacknowledged) nested mmap read lock in the arm64 code
> > would also block, meaning the original read lock is never released and thus
> > deadlock.
> >
> > This series works around this by #ifndef CONFIG_ARM64'ing the mmap read
> > lock in vmap logic, then partially reverting commit
> > a93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), keeping the
> > enablement of huge vmap support, and removing the ifdeffery with the
> > partial revert patch.
> >
> > Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> > ---
> > Lorenzo Stoakes (2):
> >       mm/vmalloc: acquire init_mm read lock on huge vmap promotion
> >       Revert "arm64: Enable vmalloc-huge with ptdump"
> >
> >  arch/arm64/include/asm/ptdump.h |  2 --
> >  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
> >  arch/arm64/mm/ptdump.c          | 11 ++---------
> >  include/linux/mmap_lock.h       |  1 +
> >  mm/pagewalk.c                   | 22 +++++++++++----------
> >  mm/vmalloc.c                    | 41 ++++++++++++++++++++++++++++++---------
> >  6 files changed, 51 insertions(+), 69 deletions(-)
> > ---
> > base-commit: a635d6748234582ea287c5ffeae28b9b23f91c7e
> > change-id: 20260710-series-vmap-race-fix-2a4cac988938
> >
> > Cheers,
> > --
> > Lorenzo Stoakes <ljs@kernel.org>
> >

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-10 10:49 ` [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion Lorenzo Stoakes
@ 2026-07-10 12:57   ` Lorenzo Stoakes
  2026-07-10 13:24     ` Lorenzo Stoakes
  2026-07-11 10:23   ` Mike Rapoport
  2026-07-12  7:43   ` Dev Jain
  2 siblings, 1 reply; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-10 12:57 UTC (permalink / raw)
  To: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon
  Cc: David Carlier, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c

As per Sashiko, we need to account for x86's slightly bat... interesting
ptdump of non-init_mm page tables.

The fix is to take a nested init_mm lock in ptdump_walk_pgd(), which is
safe as nothing depends on an init_mm lock prior to acquiring an arbitrary
mm's lock.

I think this could potentially be sent as an entirely separate patch, as it
doesn't change what this protects against, nor the x86 CPA patch, both of
which stand on their own.

Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-10 12:57   ` Lorenzo Stoakes
@ 2026-07-10 13:24     ` Lorenzo Stoakes
  0 siblings, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-10 13:24 UTC (permalink / raw)
  To: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon
  Cc: David Carlier, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c

On Fri, Jul 10, 2026 at 01:57:46PM +0100, Lorenzo Stoakes wrote:
> As per Sashiko, we need to account for x86's slightly bat... interesting
> ptdump of non-init_mm page tables.

Well it turns out arm64 also traverses EFI ranges which is kinda the saner end
of it ;)

Patch incoming in any case. Will send separately as each patch stands alone and
this series is fine as-is.

>
> The fix is to take a nested init_mm lock in ptdump_walk_pgd(), which is
> safe as nothing depends on an init_mm lock prior to acquiring an arbitrary
> mm's lock.
>
> I think this could potentially be sent as an entirely separate patch, as it
> doesn't change what this protects against, nor the x86 CPA patch, both of
> which stand on their own.
>
> Cheers, Lorenzo

Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump"
  2026-07-10 10:49 ` [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump" Lorenzo Stoakes
@ 2026-07-11 10:15   ` Mike Rapoport
  2026-07-12  8:27     ` Lorenzo Stoakes
  2026-07-12  7:46   ` Dev Jain
  1 sibling, 1 reply; 21+ messages in thread
From: Mike Rapoport @ 2026-07-11 10:15 UTC (permalink / raw)
  To: Lorenzo Stoakes
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Michal Hocko,
	Uladzislau Rezki, Toshi Kani, Catalin Marinas, Will Deacon,
	David Carlier, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel

On Fri, Jul 10, 2026 at 11:49:19AM +0100, Lorenzo Stoakes wrote:
> Subject: [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump"

Without reading the full changelog this sounds like you are disabling
vmalloc-huge with ptdump ;-)

How about "remove redundant locking guarding ptdump against vmalloc-huge"?

> This partially reverts commit fa93b45fd397 ("arm64: Enable vmalloc-huge
> with ptdump"), retaining vmalloc-huge support but eliminating the now
> redundant mitigation against a race between huge vmap page table freeing
> and ptdump, as this issue has now been fixed at core.
> 
> We also simultaneously remove the arm64 ifdeffery when acquiring the mmap
> read lock upon vmap huge page table promotion as it is no longer required.
> 
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>B

Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

> ---
>  arch/arm64/include/asm/ptdump.h |  2 --
>  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
>  arch/arm64/mm/ptdump.c          | 11 ++---------
>  mm/vmalloc.c                    | 15 +++-----------
>  4 files changed, 9 insertions(+), 62 deletions(-)

-- 
Sincerely yours,
Mike.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-10 10:49 ` [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion Lorenzo Stoakes
  2026-07-10 12:57   ` Lorenzo Stoakes
@ 2026-07-11 10:23   ` Mike Rapoport
  2026-07-12  7:43   ` Dev Jain
  2 siblings, 0 replies; 21+ messages in thread
From: Mike Rapoport @ 2026-07-11 10:23 UTC (permalink / raw)
  To: Lorenzo Stoakes
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Michal Hocko,
	Uladzislau Rezki, Toshi Kani, Catalin Marinas, Will Deacon,
	David Carlier, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c

On Fri, Jul 10, 2026 at 11:49:18AM +0100, Lorenzo Stoakes wrote:
> Currently there is a nasty race between ptdump and vmap when attempting to
> map a huge P4D, PMD or PUD entry.
>
> This patch resolves the issue by simply having
> vmap_try_huge_[p4d,pud,pmd]() hold the mmap read lock on init_mm while
> invoking [p4d,pud,pmd]_free_[pud,pmd,pte]_page() and
> [p4d,pud,pmd]_set_huge().
> 
> Fixes: b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table")
> Cc: <stable@vger.kernel.org>
> Reported-by: syzbot+fd95a72470f5a44e464c@syzkaller.appspotmail.com
> Closes: https://lore.kernel.org/all/6a287988.39669fcc.33b062.00a0.GAE@google.com/T/
> Link: https://lore.kernel.org/linux-mm/20260706203128.162335-1-devnexen@gmail.com/
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>

Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

> ---
>  include/linux/mmap_lock.h |  1 +
>  mm/pagewalk.c             | 22 +++++++++++----------
>  mm/vmalloc.c              | 50 ++++++++++++++++++++++++++++++++++++++---------
>  3 files changed, 54 insertions(+), 19 deletions(-)

-- 
Sincerely yours,
Mike.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing
  2026-07-10 10:49 [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing Lorenzo Stoakes
                   ` (2 preceding siblings ...)
  2026-07-10 11:44 ` [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing David CARLIER
@ 2026-07-12  7:20 ` Dev Jain
  2026-07-12  8:46   ` Lorenzo Stoakes
  3 siblings, 1 reply; 21+ messages in thread
From: Dev Jain @ 2026-07-12  7:20 UTC (permalink / raw)
  To: Lorenzo Stoakes, Andrew Morton, Suren Baghdasaryan,
	Liam R. Howlett, Vlastimil Babka, Shakeel Butt, David Hildenbrand,
	Mike Rapoport, Michal Hocko, Uladzislau Rezki, Toshi Kani,
	Catalin Marinas, Will Deacon
  Cc: David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c



On 10/07/26 4:19 pm, Lorenzo Stoakes wrote:
> Kernel page table walkers fall into two broad categories - those ranges
> where no exclusion is required via walk_kernel_page_table_range_lockless()
> and those where exclusion is required via walk_kernel_page_table_range()
> or walk_page_range_debug().
> 
> The former category is used only by arm64 arch code operating on ranges it
> both wholly owns and does not concurrently write.
> 
> The latter category consists of kernel page table walkers operating on
> ranges that are wholly owned (but which need exclusion against concurrent
> writers).
> 
> The lock used for exclusion is the mmap lock, and for kernel ranges this
> the mmap lock on init_mm.
> 
> ptdump is a special case being both the only user of
> walk_page_range_debug(), and the only case in which it walks ranges it does
> not own.
> 
> This presents a problem, as page tables may be freed under ptdump. And
> indeed there is a use-after-free bug in the kernel as a result, which this
> series addresses.
> 
> vmap promotes page tables to huge leaf entries where possible, freeing the
> lower leaf page table when it does. It does this with no meaningful locks
> held against concurrent ptdump walks.
> 
> As a result, use-after-free can currently occur. This series addresses the
> issue by having the vmap huge promotion logic acquire the mmap read lock
> while both setting the huge page table entry and freeing the prior leaf
> page table.
> 
> The ptdump code already acquires the mmap write lock, so by doing so we
> ensure that the ptdump walker only ever observes either the huge page table
> entry or the existing page table entry, and nothing is freed underneath it.
> 
> A mitigation for this issue was already applied for arm64 in commit
> a93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), which this series
> has to deal with carefully.
> 
> This mitigation resolves the issue by acquiring the mmap read lock on
> init_mm on vmap page table free if a ptdump is in progress.
> 
> However the fix in this series would cause a deadlock if we were to simply
> apply it for arm64 without also reverting the change.
> 
> This is because vmap may acquire the read lock before ptdump attempts to
> acquire the write lock, which then gets queued, and rwsem starvation rules
> mean that the (unacknowledged) nested mmap read lock in the arm64 code
> would also block, meaning the original read lock is never released and thus
> deadlock.
> 
> This series works around this by #ifndef CONFIG_ARM64'ing the mmap read
> lock in vmap logic, then partially reverting commit
> a93b45fd397 ("arm64: Enable vmalloc-huge with ptdump"), keeping the
> enablement of huge vmap support, and removing the ifdeffery with the
> partial revert patch.
> 
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> ---

Will Deacon had pushed back on a similar approach:
https://lore.kernel.org/all/20250530123527.GA30463@willie-the-truck/

Although now when I read back that thread, it feels more so like my
incompetency to convince :) because:

1. I don't think this pmd_free_pte_page() path is a hot path at all

2. We are doing a try lock which is almost guaranteed to succeed,
   so it's not like we are losing out on block mappings

3. Any overhead from the try lock will get dominated by the pgtable
   page free/TLB flush

I guess you did not take the RCU approach because that would put code
into the generic kernel pgtable freeing path.

I liked the RCU approach because I hate the fact that ptdump takes
an mmap_write_lock when it is literally only reading the pgtables.
But your approach is simpler and fixes the problem at the particular spot
and not hammers the fix into a generic path. So overall, ACK.


> Lorenzo Stoakes (2):
>       mm/vmalloc: acquire init_mm read lock on huge vmap promotion
>       Revert "arm64: Enable vmalloc-huge with ptdump"
> 
>  arch/arm64/include/asm/ptdump.h |  2 --
>  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
>  arch/arm64/mm/ptdump.c          | 11 ++---------
>  include/linux/mmap_lock.h       |  1 +
>  mm/pagewalk.c                   | 22 +++++++++++----------
>  mm/vmalloc.c                    | 41 ++++++++++++++++++++++++++++++---------
>  6 files changed, 51 insertions(+), 69 deletions(-)
> ---
> base-commit: a635d6748234582ea287c5ffeae28b9b23f91c7e
> change-id: 20260710-series-vmap-race-fix-2a4cac988938
> 
> Cheers,


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-10 10:49 ` [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion Lorenzo Stoakes
  2026-07-10 12:57   ` Lorenzo Stoakes
  2026-07-11 10:23   ` Mike Rapoport
@ 2026-07-12  7:43   ` Dev Jain
  2026-07-12  8:01     ` Greg KH
  2026-07-12  8:22     ` Lorenzo Stoakes
  2 siblings, 2 replies; 21+ messages in thread
From: Dev Jain @ 2026-07-12  7:43 UTC (permalink / raw)
  To: Lorenzo Stoakes, Andrew Morton, Suren Baghdasaryan,
	Liam R. Howlett, Vlastimil Babka, Shakeel Butt, David Hildenbrand,
	Mike Rapoport, Michal Hocko, Uladzislau Rezki, Toshi Kani,
	Catalin Marinas, Will Deacon
  Cc: David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c



[-----]

> We also define a guard class for mmap_read_trylock() so we can use
> cleanup.h to make the scope handling cleaner in the implementation.
> 

Will this cause backport problems, I think this scoped guard thingy is
not that old?


> One wrinkle here is commit fa93b45fd397 ("arm64: Enable vmalloc-huge with
> ptdump"), which addresses the issue for arm64 only by explicitly acquiring
> the mmap read lock on kernel page table freeing should a concurrent ptdump
> be in progress.
> 
> This is problematic as vmap may acquire the mmap read lock prior to ptdump
> attempting to acquire an mmap write lock, leading to a deadlock when the
> mmap read lock is slept upon on page table freeing due to rwsem
> anti-starvation.
> 
> We work around this by predicating the mmap lock being taken on
> !CONFIG_ARM64 for the time being.
> 
> With this patch applied, a follow up will partially revert commit
> fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump") and at that stage
> remove the arm64 ifdeffery.
> 
> We also update walk_page_range_debug() to assert the mmap write lock
> unconditionally and update the comment here to reflect this change.
> 
> The issue has existed as long as ptdump was available and vmap freed page
> tables when promoting to a huge leaf entry, that is, since commit
> b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") for
> huge ioremap, and commit 121e6f3258fe ("mm/vmalloc: hugepage vmalloc
> mappings") for huge vmalloc.
> 
> Since the former is the earlier of the two we choose that for our Fixes
> tag.
> 
> This patch is based on work by David Carlier (linked), with gratitude!
> 
> Fixes: b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table")
> Cc: <stable@vger.kernel.org>
> Reported-by: syzbot+fd95a72470f5a44e464c@syzkaller.appspotmail.com
> Closes: https://lore.kernel.org/all/6a287988.39669fcc.33b062.00a0.GAE@google.com/T/
> Link: https://lore.kernel.org/linux-mm/20260706203128.162335-1-devnexen@gmail.com/
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
>  include/linux/mmap_lock.h |  1 +
>  mm/pagewalk.c             | 22 +++++++++++----------
>  mm/vmalloc.c              | 50 ++++++++++++++++++++++++++++++++++++++---------
>  3 files changed, 54 insertions(+), 19 deletions(-)
> 
> diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h
> index 04b8f61ece5d..6b5c2390cc30 100644
> --- a/include/linux/mmap_lock.h
> +++ b/include/linux/mmap_lock.h
> @@ -621,6 +621,7 @@ static inline void mmap_read_unlock(struct mm_struct *mm)
>  
>  DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
>  	     mmap_read_lock(_T), mmap_read_unlock(_T))
> +DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T))
>  
>  static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)
>  {
> diff --git a/mm/pagewalk.c b/mm/pagewalk.c
> index 3ae2586ff45b..bbcfd68d0907 100644
> --- a/mm/pagewalk.c
> +++ b/mm/pagewalk.c
> @@ -678,6 +678,8 @@ int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end
>   * will also not lock the PTEs for the pte_entry() callback.
>   *
>   * This is for debugging purposes ONLY.
> + *
> + * The mmap write lock must be held.
>   */
>  int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
>  			  unsigned long end, const struct mm_walk_ops *ops,
> @@ -691,6 +693,16 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
>  		.no_vma		= true
>  	};
>  
> +	/*
> +	 * When walking userland page tables, an mmap write lock must be held to
> +	 * account for munmap() downgrading to an mmap read lock when tearing
> +	 * down page tables.
> +	 *
> +	 * When walking kernel page tables, an mmap write lock must also be held
> +	 * to account for page table freeing on vmap huge page mapping.
> +	 */
> +	mmap_assert_write_locked(mm);
> +
>  	/* For convenience, we allow traversal of kernel mappings. */
>  	if (mm == &init_mm)
>  		return walk_kernel_page_table_range(start, end, ops,
> @@ -700,16 +712,6 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
>  	if (!check_ops_safe(ops))
>  		return -EINVAL;
>  
> -	/*
> -	 * The mmap lock protects the page walker from changes to the page
> -	 * tables during the walk.  However a read lock is insufficient to
> -	 * protect those areas which don't have a VMA as munmap() detaches
> -	 * the VMAs before downgrading to a read lock and actually tearing
> -	 * down PTEs/page tables. In which case, the mmap write lock should
> -	 * be held.
> -	 */
> -	mmap_assert_write_locked(mm);
> -
>  	return walk_pgd_range(start, end, &walk);
>  }
>  
> diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> index 1afca3568b9b..9d0f1fdd6af3 100644
> --- a/mm/vmalloc.c
> +++ b/mm/vmalloc.c
> @@ -43,6 +43,7 @@
>  #include <asm/tlbflush.h>
>  #include <asm/shmparam.h>
>  #include <linux/page_owner.h>
> +#include <linux/cleanup.h>
>  
>  #define CREATE_TRACE_POINTS
>  #include <trace/events/vmalloc.h>
> @@ -158,10 +159,25 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
>  	if (!IS_ALIGNED(phys_addr, PMD_SIZE))
>  		return 0;
>  
> -	if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr))
> -		return 0;
> +	if (!pmd_present(*pmd))
> +		return pmd_set_huge(pmd, phys_addr, prot);
>  
> -	return pmd_set_huge(pmd, phys_addr, prot);
> +	/*
> +	 * Kernel page table walkers either walk ranges they own exclusively
> +	 * using the mmap lock for mutual exclusion, or hold the mmap write lock
> +	 * on init_mm (ptdump being the motivating case).
> +	 *
> +	 * Therefore, acquire the mmap read lock to prevent use-after-free when
> +	 * freeing page tables.
> +	 */
> +#ifndef CONFIG_ARM64
> +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
> +#endif
> +	{
> +		if (!pmd_free_pte_page(pmd, addr))
> +			return 0;
> +		return pmd_set_huge(pmd, phys_addr, prot);
> +	}
>  }
>  
Note that we do not need to take the lock around pmd_set_huge - we don't
care if ptdump observes a temporarily cleared pmd entry. So how about keeping
this outside the guard block. Otherwise right now we have an inconsistency:
for !pmd_present() we do pmd_set_huge() without locking, but for pmd_present()
we do pmd_set_huge() with locking.



^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump"
  2026-07-10 10:49 ` [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump" Lorenzo Stoakes
  2026-07-11 10:15   ` Mike Rapoport
@ 2026-07-12  7:46   ` Dev Jain
  2026-07-12  8:28     ` Lorenzo Stoakes
  1 sibling, 1 reply; 21+ messages in thread
From: Dev Jain @ 2026-07-12  7:46 UTC (permalink / raw)
  To: Lorenzo Stoakes, Andrew Morton, Suren Baghdasaryan,
	Liam R. Howlett, Vlastimil Babka, Shakeel Butt, David Hildenbrand,
	Mike Rapoport, Michal Hocko, Uladzislau Rezki, Toshi Kani,
	Catalin Marinas, Will Deacon
  Cc: David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel



On 10/07/26 4:19 pm, Lorenzo Stoakes wrote:
> This partially reverts commit fa93b45fd397 ("arm64: Enable vmalloc-huge
> with ptdump"), retaining vmalloc-huge support but eliminating the now
> redundant mitigation against a race between huge vmap page table freeing
> and ptdump, as this issue has now been fixed at core.
> 
> We also simultaneously remove the arm64 ifdeffery when acquiring the mmap
> read lock upon vmap huge page table promotion as it is no longer required.
> 
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> ---

As pointed out by Mike, the subject line doesn't fit. Perhaps just start with
"Partially revert ..."


Reviewed-by: Dev Jain <dev.jain@arm.com>

>  arch/arm64/include/asm/ptdump.h |  2 --
>  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
>  arch/arm64/mm/ptdump.c          | 11 ++---------
>  mm/vmalloc.c                    | 15 +++-----------
>  4 files changed, 9 insertions(+), 62 deletions(-)
> 
> diff --git a/arch/arm64/include/asm/ptdump.h b/arch/arm64/include/asm/ptdump.h
> index 5b374a6ab34a..50a195eda8ed 100644
> --- a/arch/arm64/include/asm/ptdump.h
> +++ b/arch/arm64/include/asm/ptdump.h
> @@ -7,8 +7,6 @@
>  
>  #include <linux/ptdump.h>
>  
> -DECLARE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
> -
>  #ifdef CONFIG_PTDUMP
>  
>  #include <linux/mm_types.h>
> diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
> index f2be501468ce..f723bcf68174 100644
> --- a/arch/arm64/mm/mmu.c
> +++ b/arch/arm64/mm/mmu.c
> @@ -49,8 +49,6 @@
>  #define NO_CONT_MAPPINGS	BIT(1)
>  #define NO_EXEC_MAPPINGS	BIT(2)	/* assumes FEAT_HPDS is not used */
>  
> -DEFINE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
> -
>  u64 kimage_voffset __ro_after_init;
>  EXPORT_SYMBOL(kimage_voffset);
>  
> @@ -1857,8 +1855,7 @@ int pmd_clear_huge(pmd_t *pmdp)
>  	return 1;
>  }
>  
> -static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
> -			       bool acquire_mmap_lock)
> +int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
>  {
>  	pte_t *table;
>  	pmd_t pmd;
> @@ -1870,25 +1867,13 @@ static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
>  		return 1;
>  	}
>  
> -	/* See comment in pud_free_pmd_page for static key logic */
>  	table = pte_offset_kernel(pmdp, addr);
>  	pmd_clear(pmdp);
>  	__flush_tlb_kernel_pgtable(addr);
> -	if (static_branch_unlikely(&arm64_ptdump_lock_key) && acquire_mmap_lock) {
> -		mmap_read_lock(&init_mm);
> -		mmap_read_unlock(&init_mm);
> -	}
> -
>  	pte_free_kernel(NULL, table);
>  	return 1;
>  }
>  
> -int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
> -{
> -	/* If ptdump is walking the pagetables, acquire init_mm.mmap_lock */
> -	return __pmd_free_pte_page(pmdp, addr, /* acquire_mmap_lock = */ true);
> -}
> -
>  int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
>  {
>  	pmd_t *table;
> @@ -1904,36 +1889,16 @@ int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
>  	}
>  
>  	table = pmd_offset(pudp, addr);
> -
> -	/*
> -	 * Our objective is to prevent ptdump from reading a PMD table which has
> -	 * been freed. In this race, if pud_free_pmd_page observes the key on
> -	 * (which got flipped by ptdump) then the mmap lock sequence here will,
> -	 * as a result of the mmap write lock/unlock sequence in ptdump, give
> -	 * us the correct synchronization. If not, this means that ptdump has
> -	 * yet not started walking the pagetables - the sequence of barriers
> -	 * issued by __flush_tlb_kernel_pgtable() guarantees that ptdump will
> -	 * observe an empty PUD.
> -	 */
> -	pud_clear(pudp);
> -	__flush_tlb_kernel_pgtable(addr);
> -	if (static_branch_unlikely(&arm64_ptdump_lock_key)) {
> -		mmap_read_lock(&init_mm);
> -		mmap_read_unlock(&init_mm);
> -	}
> -
>  	pmdp = table;
>  	next = addr;
>  	end = addr + PUD_SIZE;
>  	do {
>  		if (pmd_present(pmdp_get(pmdp)))
> -			/*
> -			 * PMD has been isolated, so ptdump won't see it. No
> -			 * need to acquire init_mm.mmap_lock.
> -			 */
> -			__pmd_free_pte_page(pmdp, next, /* acquire_mmap_lock = */ false);
> +			pmd_free_pte_page(pmdp, next);
>  	} while (pmdp++, next += PMD_SIZE, next != end);
>  
> +	pud_clear(pudp);
> +	__flush_tlb_kernel_pgtable(addr);
>  	pmd_free(NULL, table);
>  	return 1;
>  }
> diff --git a/arch/arm64/mm/ptdump.c b/arch/arm64/mm/ptdump.c
> index 1c20144700d7..5a76c59b5ada 100644
> --- a/arch/arm64/mm/ptdump.c
> +++ b/arch/arm64/mm/ptdump.c
> @@ -283,13 +283,6 @@ void note_page_flush(struct ptdump_state *pt_st)
>  	note_page(pt_st, 0, -1, pte_val(pte_zero));
>  }
>  
> -static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm)
> -{
> -	static_branch_inc(&arm64_ptdump_lock_key);
> -	ptdump_walk_pgd(st, mm, NULL);
> -	static_branch_dec(&arm64_ptdump_lock_key);
> -}
> -
>  void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
>  {
>  	unsigned long end = ~0UL;
> @@ -318,7 +311,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
>  		}
>  	};
>  
> -	arm64_ptdump_walk_pgd(&st.ptdump, info->mm);
> +	ptdump_walk_pgd(&st.ptdump, info->mm, NULL);
>  }
>  
>  static void __init ptdump_initialize(void)
> @@ -360,7 +353,7 @@ bool ptdump_check_wx(void)
>  		}
>  	};
>  
> -	arm64_ptdump_walk_pgd(&st.ptdump, &init_mm);
> +	ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
>  
>  	if (st.wx_pages || st.uxn_pages) {
>  		pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
> diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> index 9d0f1fdd6af3..537ff7b3c412 100644
> --- a/mm/vmalloc.c
> +++ b/mm/vmalloc.c
> @@ -170,10 +170,7 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
>  	 * Therefore, acquire the mmap read lock to prevent use-after-free when
>  	 * freeing page tables.
>  	 */
> -#ifndef CONFIG_ARM64
> -	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
> -#endif
> -	{
> +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
>  		if (!pmd_free_pte_page(pmd, addr))
>  			return 0;
>  		return pmd_set_huge(pmd, phys_addr, prot);
> @@ -230,10 +227,7 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end,
>  		return pud_set_huge(pud, phys_addr, prot);
>  
>  	/* See comment in vmap_try_huge_pmd(). */
> -#ifndef CONFIG_ARM64
> -	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
> -#endif
> -	{
> +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
>  		if (!pud_free_pmd_page(pud, addr))
>  			return 0;
>  		return pud_set_huge(pud, phys_addr, prot);
> @@ -290,10 +284,7 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end,
>  		return p4d_set_huge(p4d, phys_addr, prot);
>  
>  	/* See comment in vmap_try_huge_pmd(). */
> -#ifndef CONFIG_ARM64
> -	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
> -#endif
> -	{
> +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
>  		if (!p4d_free_pud_page(p4d, addr))
>  			return 0;
>  		return p4d_set_huge(p4d, phys_addr, prot);
> 


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-12  7:43   ` Dev Jain
@ 2026-07-12  8:01     ` Greg KH
  2026-07-12  9:27       ` Lorenzo Stoakes
  2026-07-12 11:15       ` Dev Jain
  2026-07-12  8:22     ` Lorenzo Stoakes
  1 sibling, 2 replies; 21+ messages in thread
From: Greg KH @ 2026-07-12  8:01 UTC (permalink / raw)
  To: Dev Jain
  Cc: Lorenzo Stoakes, Andrew Morton, Suren Baghdasaryan,
	Liam R. Howlett, Vlastimil Babka, Shakeel Butt, David Hildenbrand,
	Mike Rapoport, Michal Hocko, Uladzislau Rezki, Toshi Kani,
	Catalin Marinas, Will Deacon, David Carlier, Ryan Roberts,
	linux-mm, linux-kernel, linux-arm-kernel, stable,
	syzbot+fd95a72470f5a44e464c

On Sun, Jul 12, 2026 at 01:13:12PM +0530, Dev Jain wrote:
> 
> 
> [-----]
> 
> > We also define a guard class for mmap_read_trylock() so we can use
> > cleanup.h to make the scope handling cleaner in the implementation.
> > 
> 
> Will this cause backport problems, I think this scoped guard thingy is
> not that old?

Don't worry about stable issues until after the fact.  Fix things
properly in Linus's tree first.

And scoped guard has been backported to many of the LTS branches
already.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-12  7:43   ` Dev Jain
  2026-07-12  8:01     ` Greg KH
@ 2026-07-12  8:22     ` Lorenzo Stoakes
  2026-07-12 11:16       ` Dev Jain
  1 sibling, 1 reply; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-12  8:22 UTC (permalink / raw)
  To: Dev Jain
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon, David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c

On Sun, Jul 12, 2026 at 01:13:12PM +0530, Dev Jain wrote:
>
>
> [-----]
>
> > We also define a guard class for mmap_read_trylock() so we can use
> > cleanup.h to make the scope handling cleaner in the implementation.
> >
>
> Will this cause backport problems, I think this scoped guard thingy is
> not that old?

I intentionally used it because it's the best way to solve this problem.

If there's any issue I'll fix them up in the stable backports myself.

I will likely resend this as a 4 patch series and just do the stable
backports manually anyway.

>
>
> > One wrinkle here is commit fa93b45fd397 ("arm64: Enable vmalloc-huge with
> > ptdump"), which addresses the issue for arm64 only by explicitly acquiring
> > the mmap read lock on kernel page table freeing should a concurrent ptdump
> > be in progress.
> >
> > This is problematic as vmap may acquire the mmap read lock prior to ptdump
> > attempting to acquire an mmap write lock, leading to a deadlock when the
> > mmap read lock is slept upon on page table freeing due to rwsem
> > anti-starvation.
> >
> > We work around this by predicating the mmap lock being taken on
> > !CONFIG_ARM64 for the time being.
> >
> > With this patch applied, a follow up will partially revert commit
> > fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump") and at that stage
> > remove the arm64 ifdeffery.
> >
> > We also update walk_page_range_debug() to assert the mmap write lock
> > unconditionally and update the comment here to reflect this change.
> >
> > The issue has existed as long as ptdump was available and vmap freed page
> > tables when promoting to a huge leaf entry, that is, since commit
> > b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") for
> > huge ioremap, and commit 121e6f3258fe ("mm/vmalloc: hugepage vmalloc
> > mappings") for huge vmalloc.
> >
> > Since the former is the earlier of the two we choose that for our Fixes
> > tag.
> >
> > This patch is based on work by David Carlier (linked), with gratitude!
> >
> > Fixes: b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table")
> > Cc: <stable@vger.kernel.org>
> > Reported-by: syzbot+fd95a72470f5a44e464c@syzkaller.appspotmail.com
> > Closes: https://lore.kernel.org/all/6a287988.39669fcc.33b062.00a0.GAE@google.com/T/
> > Link: https://lore.kernel.org/linux-mm/20260706203128.162335-1-devnexen@gmail.com/
> > Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> > ---
> >  include/linux/mmap_lock.h |  1 +
> >  mm/pagewalk.c             | 22 +++++++++++----------
> >  mm/vmalloc.c              | 50 ++++++++++++++++++++++++++++++++++++++---------
> >  3 files changed, 54 insertions(+), 19 deletions(-)
> >
> > diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h
> > index 04b8f61ece5d..6b5c2390cc30 100644
> > --- a/include/linux/mmap_lock.h
> > +++ b/include/linux/mmap_lock.h
> > @@ -621,6 +621,7 @@ static inline void mmap_read_unlock(struct mm_struct *mm)
> >
> >  DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
> >  	     mmap_read_lock(_T), mmap_read_unlock(_T))
> > +DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T))
> >
> >  static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)
> >  {
> > diff --git a/mm/pagewalk.c b/mm/pagewalk.c
> > index 3ae2586ff45b..bbcfd68d0907 100644
> > --- a/mm/pagewalk.c
> > +++ b/mm/pagewalk.c
> > @@ -678,6 +678,8 @@ int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end
> >   * will also not lock the PTEs for the pte_entry() callback.
> >   *
> >   * This is for debugging purposes ONLY.
> > + *
> > + * The mmap write lock must be held.
> >   */
> >  int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
> >  			  unsigned long end, const struct mm_walk_ops *ops,
> > @@ -691,6 +693,16 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
> >  		.no_vma		= true
> >  	};
> >
> > +	/*
> > +	 * When walking userland page tables, an mmap write lock must be held to
> > +	 * account for munmap() downgrading to an mmap read lock when tearing
> > +	 * down page tables.
> > +	 *
> > +	 * When walking kernel page tables, an mmap write lock must also be held
> > +	 * to account for page table freeing on vmap huge page mapping.
> > +	 */
> > +	mmap_assert_write_locked(mm);
> > +
> >  	/* For convenience, we allow traversal of kernel mappings. */
> >  	if (mm == &init_mm)
> >  		return walk_kernel_page_table_range(start, end, ops,
> > @@ -700,16 +712,6 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
> >  	if (!check_ops_safe(ops))
> >  		return -EINVAL;
> >
> > -	/*
> > -	 * The mmap lock protects the page walker from changes to the page
> > -	 * tables during the walk.  However a read lock is insufficient to
> > -	 * protect those areas which don't have a VMA as munmap() detaches
> > -	 * the VMAs before downgrading to a read lock and actually tearing
> > -	 * down PTEs/page tables. In which case, the mmap write lock should
> > -	 * be held.
> > -	 */
> > -	mmap_assert_write_locked(mm);
> > -
> >  	return walk_pgd_range(start, end, &walk);
> >  }
> >
> > diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> > index 1afca3568b9b..9d0f1fdd6af3 100644
> > --- a/mm/vmalloc.c
> > +++ b/mm/vmalloc.c
> > @@ -43,6 +43,7 @@
> >  #include <asm/tlbflush.h>
> >  #include <asm/shmparam.h>
> >  #include <linux/page_owner.h>
> > +#include <linux/cleanup.h>
> >
> >  #define CREATE_TRACE_POINTS
> >  #include <trace/events/vmalloc.h>
> > @@ -158,10 +159,25 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
> >  	if (!IS_ALIGNED(phys_addr, PMD_SIZE))
> >  		return 0;
> >
> > -	if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr))
> > -		return 0;
> > +	if (!pmd_present(*pmd))
> > +		return pmd_set_huge(pmd, phys_addr, prot);
> >
> > -	return pmd_set_huge(pmd, phys_addr, prot);
> > +	/*
> > +	 * Kernel page table walkers either walk ranges they own exclusively
> > +	 * using the mmap lock for mutual exclusion, or hold the mmap write lock
> > +	 * on init_mm (ptdump being the motivating case).
> > +	 *
> > +	 * Therefore, acquire the mmap read lock to prevent use-after-free when
> > +	 * freeing page tables.
> > +	 */
> > +#ifndef CONFIG_ARM64
> > +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
> > +#endif
> > +	{
> > +		if (!pmd_free_pte_page(pmd, addr))
> > +			return 0;
> > +		return pmd_set_huge(pmd, phys_addr, prot);
> > +	}
> >  }
> >
> Note that we do not need to take the lock around pmd_set_huge - we don't
> care if ptdump observes a temporarily cleared pmd entry. So how about keeping
> this outside the guard block. Otherwise right now we have an inconsistency:
> for !pmd_present() we do pmd_set_huge() without locking, but for pmd_present()
> we do pmd_set_huge() with locking.

As I said in the commit message I'm intentionally taking the lock around all of
it so a concurrent ptdump sees either the leaf entry or the non-leaf entry.

By doing that we can easiliy avoid the situation where a ptdump gives you
inconsistent output and it makes more sense logically.

So this is the opposite of inconsistent - if !pmd_present() the ptdump may
observe the _genuine_ state of there not being an entry before. With
pmd_present() it either observes what was or what became, not something
inbetween :)

>
>

Thanks, Lorenzo

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump"
  2026-07-11 10:15   ` Mike Rapoport
@ 2026-07-12  8:27     ` Lorenzo Stoakes
  0 siblings, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-12  8:27 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Michal Hocko,
	Uladzislau Rezki, Toshi Kani, Catalin Marinas, Will Deacon,
	David Carlier, Dev Jain, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel

On Sat, Jul 11, 2026 at 01:15:45PM +0300, Mike Rapoport wrote:
> On Fri, Jul 10, 2026 at 11:49:19AM +0100, Lorenzo Stoakes wrote:
> > Subject: [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump"
>
> Without reading the full changelog this sounds like you are disabling
> vmalloc-huge with ptdump ;-)
>
> How about "remove redundant locking guarding ptdump against vmalloc-huge"?

I'm just following the standard convention when it comes to partial reverts, as
I revert the majority of the patch and I ran 'git revert' to do it ;)

But then again I do remove the arm64 ifdeffery in the same commit and
you're right it does suggest I revert more than I do so will fix it up!

>
> > This partially reverts commit fa93b45fd397 ("arm64: Enable vmalloc-huge
> > with ptdump"), retaining vmalloc-huge support but eliminating the now
> > redundant mitigation against a race between huge vmap page table freeing
> > and ptdump, as this issue has now been fixed at core.
> >
> > We also simultaneously remove the arm64 ifdeffery when acquiring the mmap
> > read lock upon vmap huge page table promotion as it is no longer required.
> >
> > Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>B
>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Thanks!

>
> > ---
> >  arch/arm64/include/asm/ptdump.h |  2 --
> >  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
> >  arch/arm64/mm/ptdump.c          | 11 ++---------
> >  mm/vmalloc.c                    | 15 +++-----------
> >  4 files changed, 9 insertions(+), 62 deletions(-)
>
> --
> Sincerely yours,
> Mike.

Thanks, Lorenzo

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump"
  2026-07-12  7:46   ` Dev Jain
@ 2026-07-12  8:28     ` Lorenzo Stoakes
  0 siblings, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-12  8:28 UTC (permalink / raw)
  To: Dev Jain
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon, David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel

On Sun, Jul 12, 2026 at 01:16:17PM +0530, Dev Jain wrote:
>
>
> On 10/07/26 4:19 pm, Lorenzo Stoakes wrote:
> > This partially reverts commit fa93b45fd397 ("arm64: Enable vmalloc-huge
> > with ptdump"), retaining vmalloc-huge support but eliminating the now
> > redundant mitigation against a race between huge vmap page table freeing
> > and ptdump, as this issue has now been fixed at core.
> >
> > We also simultaneously remove the arm64 ifdeffery when acquiring the mmap
> > read lock upon vmap huge page table promotion as it is no longer required.
> >
> > Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> > ---
>
> As pointed out by Mike, the subject line doesn't fit. Perhaps just start with
> "Partially revert ..."

Ack yeah will fix!

>
>
> Reviewed-by: Dev Jain <dev.jain@arm.com>

Thanks!

Cheers, Lorenzo

>
> >  arch/arm64/include/asm/ptdump.h |  2 --
> >  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
> >  arch/arm64/mm/ptdump.c          | 11 ++---------
> >  mm/vmalloc.c                    | 15 +++-----------
> >  4 files changed, 9 insertions(+), 62 deletions(-)
> >
> > diff --git a/arch/arm64/include/asm/ptdump.h b/arch/arm64/include/asm/ptdump.h
> > index 5b374a6ab34a..50a195eda8ed 100644
> > --- a/arch/arm64/include/asm/ptdump.h
> > +++ b/arch/arm64/include/asm/ptdump.h
> > @@ -7,8 +7,6 @@
> >
> >  #include <linux/ptdump.h>
> >
> > -DECLARE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
> > -
> >  #ifdef CONFIG_PTDUMP
> >
> >  #include <linux/mm_types.h>
> > diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
> > index f2be501468ce..f723bcf68174 100644
> > --- a/arch/arm64/mm/mmu.c
> > +++ b/arch/arm64/mm/mmu.c
> > @@ -49,8 +49,6 @@
> >  #define NO_CONT_MAPPINGS	BIT(1)
> >  #define NO_EXEC_MAPPINGS	BIT(2)	/* assumes FEAT_HPDS is not used */
> >
> > -DEFINE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
> > -
> >  u64 kimage_voffset __ro_after_init;
> >  EXPORT_SYMBOL(kimage_voffset);
> >
> > @@ -1857,8 +1855,7 @@ int pmd_clear_huge(pmd_t *pmdp)
> >  	return 1;
> >  }
> >
> > -static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
> > -			       bool acquire_mmap_lock)
> > +int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
> >  {
> >  	pte_t *table;
> >  	pmd_t pmd;
> > @@ -1870,25 +1867,13 @@ static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
> >  		return 1;
> >  	}
> >
> > -	/* See comment in pud_free_pmd_page for static key logic */
> >  	table = pte_offset_kernel(pmdp, addr);
> >  	pmd_clear(pmdp);
> >  	__flush_tlb_kernel_pgtable(addr);
> > -	if (static_branch_unlikely(&arm64_ptdump_lock_key) && acquire_mmap_lock) {
> > -		mmap_read_lock(&init_mm);
> > -		mmap_read_unlock(&init_mm);
> > -	}
> > -
> >  	pte_free_kernel(NULL, table);
> >  	return 1;
> >  }
> >
> > -int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
> > -{
> > -	/* If ptdump is walking the pagetables, acquire init_mm.mmap_lock */
> > -	return __pmd_free_pte_page(pmdp, addr, /* acquire_mmap_lock = */ true);
> > -}
> > -
> >  int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
> >  {
> >  	pmd_t *table;
> > @@ -1904,36 +1889,16 @@ int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
> >  	}
> >
> >  	table = pmd_offset(pudp, addr);
> > -
> > -	/*
> > -	 * Our objective is to prevent ptdump from reading a PMD table which has
> > -	 * been freed. In this race, if pud_free_pmd_page observes the key on
> > -	 * (which got flipped by ptdump) then the mmap lock sequence here will,
> > -	 * as a result of the mmap write lock/unlock sequence in ptdump, give
> > -	 * us the correct synchronization. If not, this means that ptdump has
> > -	 * yet not started walking the pagetables - the sequence of barriers
> > -	 * issued by __flush_tlb_kernel_pgtable() guarantees that ptdump will
> > -	 * observe an empty PUD.
> > -	 */
> > -	pud_clear(pudp);
> > -	__flush_tlb_kernel_pgtable(addr);
> > -	if (static_branch_unlikely(&arm64_ptdump_lock_key)) {
> > -		mmap_read_lock(&init_mm);
> > -		mmap_read_unlock(&init_mm);
> > -	}
> > -
> >  	pmdp = table;
> >  	next = addr;
> >  	end = addr + PUD_SIZE;
> >  	do {
> >  		if (pmd_present(pmdp_get(pmdp)))
> > -			/*
> > -			 * PMD has been isolated, so ptdump won't see it. No
> > -			 * need to acquire init_mm.mmap_lock.
> > -			 */
> > -			__pmd_free_pte_page(pmdp, next, /* acquire_mmap_lock = */ false);
> > +			pmd_free_pte_page(pmdp, next);
> >  	} while (pmdp++, next += PMD_SIZE, next != end);
> >
> > +	pud_clear(pudp);
> > +	__flush_tlb_kernel_pgtable(addr);
> >  	pmd_free(NULL, table);
> >  	return 1;
> >  }
> > diff --git a/arch/arm64/mm/ptdump.c b/arch/arm64/mm/ptdump.c
> > index 1c20144700d7..5a76c59b5ada 100644
> > --- a/arch/arm64/mm/ptdump.c
> > +++ b/arch/arm64/mm/ptdump.c
> > @@ -283,13 +283,6 @@ void note_page_flush(struct ptdump_state *pt_st)
> >  	note_page(pt_st, 0, -1, pte_val(pte_zero));
> >  }
> >
> > -static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm)
> > -{
> > -	static_branch_inc(&arm64_ptdump_lock_key);
> > -	ptdump_walk_pgd(st, mm, NULL);
> > -	static_branch_dec(&arm64_ptdump_lock_key);
> > -}
> > -
> >  void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
> >  {
> >  	unsigned long end = ~0UL;
> > @@ -318,7 +311,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
> >  		}
> >  	};
> >
> > -	arm64_ptdump_walk_pgd(&st.ptdump, info->mm);
> > +	ptdump_walk_pgd(&st.ptdump, info->mm, NULL);
> >  }
> >
> >  static void __init ptdump_initialize(void)
> > @@ -360,7 +353,7 @@ bool ptdump_check_wx(void)
> >  		}
> >  	};
> >
> > -	arm64_ptdump_walk_pgd(&st.ptdump, &init_mm);
> > +	ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
> >
> >  	if (st.wx_pages || st.uxn_pages) {
> >  		pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
> > diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> > index 9d0f1fdd6af3..537ff7b3c412 100644
> > --- a/mm/vmalloc.c
> > +++ b/mm/vmalloc.c
> > @@ -170,10 +170,7 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
> >  	 * Therefore, acquire the mmap read lock to prevent use-after-free when
> >  	 * freeing page tables.
> >  	 */
> > -#ifndef CONFIG_ARM64
> > -	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
> > -#endif
> > -	{
> > +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
> >  		if (!pmd_free_pte_page(pmd, addr))
> >  			return 0;
> >  		return pmd_set_huge(pmd, phys_addr, prot);
> > @@ -230,10 +227,7 @@ static int vmap_try_huge_pud(pud_t *pud, unsigned long addr, unsigned long end,
> >  		return pud_set_huge(pud, phys_addr, prot);
> >
> >  	/* See comment in vmap_try_huge_pmd(). */
> > -#ifndef CONFIG_ARM64
> > -	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
> > -#endif
> > -	{
> > +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
> >  		if (!pud_free_pmd_page(pud, addr))
> >  			return 0;
> >  		return pud_set_huge(pud, phys_addr, prot);
> > @@ -290,10 +284,7 @@ static int vmap_try_huge_p4d(p4d_t *p4d, unsigned long addr, unsigned long end,
> >  		return p4d_set_huge(p4d, phys_addr, prot);
> >
> >  	/* See comment in vmap_try_huge_pmd(). */
> > -#ifndef CONFIG_ARM64
> > -	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
> > -#endif
> > -	{
> > +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
> >  		if (!p4d_free_pud_page(p4d, addr))
> >  			return 0;
> >  		return p4d_set_huge(p4d, phys_addr, prot);
> >
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing
  2026-07-12  7:20 ` Dev Jain
@ 2026-07-12  8:46   ` Lorenzo Stoakes
  2026-07-12 11:34     ` Dev Jain
  0 siblings, 1 reply; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-12  8:46 UTC (permalink / raw)
  To: Dev Jain
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon, David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c

On Sun, Jul 12, 2026 at 12:50:08PM +0530, Dev Jain wrote:
> Will Deacon had pushed back on a similar approach:
> https://lore.kernel.org/all/20250530123527.GA30463@willie-the-truck/
>
> Although now when I read back that thread, it feels more so like my
> incompetency to convince :) because:

No haha not so, I think more like this stuff is fiddly.

>
> 1. I don't think this pmd_free_pte_page() path is a hot path at all

Right, and we don't actually alter that path anyway

>
> 2. We are doing a try lock which is almost guaranteed to succeed,
>    so it's not like we are losing out on block mappings

Also it's specifically only on when vmap tries to make a mapping huge, and
this path is being inconsistent with a convention that already existed - if
you manipulate kernel page table mappings that can interact with other page
table walkers, you have to take the init_mm mmap lock.

>
> 3. Any overhead from the try lock will get dominated by the pgtable
>    page free/TLB flush

Yup.

>
> I guess you did not take the RCU approach because that would put code
> into the generic kernel pgtable freeing path.

Well a number of reasons:

* firstly yes it makes the code path always RCU only to suit a specific
  debug user as you say :)

* Importantly - we risk genuine RCU stall issues, because the ptdump then
  has to be RCU too over vast ranges.

  To work around that you have to shard the ptdump walk, make an assumption
  all callbacks are RCU-safe, and that the sharding suffices to avoid these
  stalls.

  It's a ton of complexity and assumptions to account for... vmalloc doing
  the wrong thing.

* It is an established precedent that we mmap lock init_mm for kernel page
  table walking as per mm/pagewalk.c. It'd require significant rework there
  and would disallow any future walkers like this if we were to require
  RCU.

* The mmap lock approach is simple, safe, and as you say is only actually
  required in code paths that manipulate page tables and thus are already
  not hotpaths.

* If there's future work to free vmalloc page tables upon vunmap()
  (currently it does not), we have a stable, established basis for doing so
  that again puts the weight of the work on the operation being performed
  rather than anything else.

>
> I liked the RCU approach because I hate the fact that ptdump takes
> an mmap_write_lock when it is literally only reading the pgtables.

Well you have to do that for the userland side, because there could be a
concurrent downgraded mmap read lock during an munmap, and the same goes
for non-VMA kernel ranges too, so it would have to keep doing that
regardless.

> But your approach is simpler and fixes the problem at the particular spot
> and not hammers the fix into a generic path. So overall, ACK.

Thanks!

>
>
> > Lorenzo Stoakes (2):
> >       mm/vmalloc: acquire init_mm read lock on huge vmap promotion
> >       Revert "arm64: Enable vmalloc-huge with ptdump"
> >
> >  arch/arm64/include/asm/ptdump.h |  2 --
> >  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
> >  arch/arm64/mm/ptdump.c          | 11 ++---------
> >  include/linux/mmap_lock.h       |  1 +
> >  mm/pagewalk.c                   | 22 +++++++++++----------
> >  mm/vmalloc.c                    | 41 ++++++++++++++++++++++++++++++---------
> >  6 files changed, 51 insertions(+), 69 deletions(-)
> > ---
> > base-commit: a635d6748234582ea287c5ffeae28b9b23f91c7e
> > change-id: 20260710-series-vmap-race-fix-2a4cac988938
> >
> > Cheers,
>

Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-12  8:01     ` Greg KH
@ 2026-07-12  9:27       ` Lorenzo Stoakes
  2026-07-12 11:15       ` Dev Jain
  1 sibling, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-12  9:27 UTC (permalink / raw)
  To: Greg KH
  Cc: Dev Jain, Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon, David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c

On Sun, Jul 12, 2026 at 10:01:37AM +0200, Greg KH wrote:
>
> And scoped guard has been backported to many of the LTS branches
> already.

That's really handy :)

>
> thanks,
>
> greg k-h

Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-12  8:01     ` Greg KH
  2026-07-12  9:27       ` Lorenzo Stoakes
@ 2026-07-12 11:15       ` Dev Jain
  1 sibling, 0 replies; 21+ messages in thread
From: Dev Jain @ 2026-07-12 11:15 UTC (permalink / raw)
  To: Greg KH
  Cc: Lorenzo Stoakes, Andrew Morton, Suren Baghdasaryan,
	Liam R. Howlett, Vlastimil Babka, Shakeel Butt, David Hildenbrand,
	Mike Rapoport, Michal Hocko, Uladzislau Rezki, Toshi Kani,
	Catalin Marinas, Will Deacon, David Carlier, Ryan Roberts,
	linux-mm, linux-kernel, linux-arm-kernel, stable,
	syzbot+fd95a72470f5a44e464c



On 12/07/26 1:31 pm, Greg KH wrote:
> On Sun, Jul 12, 2026 at 01:13:12PM +0530, Dev Jain wrote:
>>
>>
>> [-----]
>>
>>> We also define a guard class for mmap_read_trylock() so we can use
>>> cleanup.h to make the scope handling cleaner in the implementation.
>>>
>>
>> Will this cause backport problems, I think this scoped guard thingy is
>> not that old?
> 
> Don't worry about stable issues until after the fact.  Fix things
> properly in Linus's tree first.
> 
> And scoped guard has been backported to many of the LTS branches
> already.

Nice, thanks.


> 
> thanks,
> 
> greg k-h


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion
  2026-07-12  8:22     ` Lorenzo Stoakes
@ 2026-07-12 11:16       ` Dev Jain
  0 siblings, 0 replies; 21+ messages in thread
From: Dev Jain @ 2026-07-12 11:16 UTC (permalink / raw)
  To: Lorenzo Stoakes
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon, David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c



On 12/07/26 1:52 pm, Lorenzo Stoakes wrote:
> On Sun, Jul 12, 2026 at 01:13:12PM +0530, Dev Jain wrote:
>>
>>
>> [-----]
>>
>>> We also define a guard class for mmap_read_trylock() so we can use
>>> cleanup.h to make the scope handling cleaner in the implementation.
>>>
>>
>> Will this cause backport problems, I think this scoped guard thingy is
>> not that old?
> 
> I intentionally used it because it's the best way to solve this problem.
> 
> If there's any issue I'll fix them up in the stable backports myself.
> 
> I will likely resend this as a 4 patch series and just do the stable
> backports manually anyway.
> 
>>
>>
>>> One wrinkle here is commit fa93b45fd397 ("arm64: Enable vmalloc-huge with
>>> ptdump"), which addresses the issue for arm64 only by explicitly acquiring
>>> the mmap read lock on kernel page table freeing should a concurrent ptdump
>>> be in progress.
>>>
>>> This is problematic as vmap may acquire the mmap read lock prior to ptdump
>>> attempting to acquire an mmap write lock, leading to a deadlock when the
>>> mmap read lock is slept upon on page table freeing due to rwsem
>>> anti-starvation.
>>>
>>> We work around this by predicating the mmap lock being taken on
>>> !CONFIG_ARM64 for the time being.
>>>
>>> With this patch applied, a follow up will partially revert commit
>>> fa93b45fd397 ("arm64: Enable vmalloc-huge with ptdump") and at that stage
>>> remove the arm64 ifdeffery.
>>>
>>> We also update walk_page_range_debug() to assert the mmap write lock
>>> unconditionally and update the comment here to reflect this change.
>>>
>>> The issue has existed as long as ptdump was available and vmap freed page
>>> tables when promoting to a huge leaf entry, that is, since commit
>>> b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table") for
>>> huge ioremap, and commit 121e6f3258fe ("mm/vmalloc: hugepage vmalloc
>>> mappings") for huge vmalloc.
>>>
>>> Since the former is the earlier of the two we choose that for our Fixes
>>> tag.
>>>
>>> This patch is based on work by David Carlier (linked), with gratitude!
>>>
>>> Fixes: b6bdb7517c3d ("mm/vmalloc: add interfaces to free unmapped page table")
>>> Cc: <stable@vger.kernel.org>
>>> Reported-by: syzbot+fd95a72470f5a44e464c@syzkaller.appspotmail.com
>>> Closes: https://lore.kernel.org/all/6a287988.39669fcc.33b062.00a0.GAE@google.com/T/
>>> Link: https://lore.kernel.org/linux-mm/20260706203128.162335-1-devnexen@gmail.com/
>>> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
>>> ---
>>>  include/linux/mmap_lock.h |  1 +
>>>  mm/pagewalk.c             | 22 +++++++++++----------
>>>  mm/vmalloc.c              | 50 ++++++++++++++++++++++++++++++++++++++---------
>>>  3 files changed, 54 insertions(+), 19 deletions(-)
>>>
>>> diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h
>>> index 04b8f61ece5d..6b5c2390cc30 100644
>>> --- a/include/linux/mmap_lock.h
>>> +++ b/include/linux/mmap_lock.h
>>> @@ -621,6 +621,7 @@ static inline void mmap_read_unlock(struct mm_struct *mm)
>>>
>>>  DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
>>>  	     mmap_read_lock(_T), mmap_read_unlock(_T))
>>> +DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T))
>>>
>>>  static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)
>>>  {
>>> diff --git a/mm/pagewalk.c b/mm/pagewalk.c
>>> index 3ae2586ff45b..bbcfd68d0907 100644
>>> --- a/mm/pagewalk.c
>>> +++ b/mm/pagewalk.c
>>> @@ -678,6 +678,8 @@ int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end
>>>   * will also not lock the PTEs for the pte_entry() callback.
>>>   *
>>>   * This is for debugging purposes ONLY.
>>> + *
>>> + * The mmap write lock must be held.
>>>   */
>>>  int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
>>>  			  unsigned long end, const struct mm_walk_ops *ops,
>>> @@ -691,6 +693,16 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
>>>  		.no_vma		= true
>>>  	};
>>>
>>> +	/*
>>> +	 * When walking userland page tables, an mmap write lock must be held to
>>> +	 * account for munmap() downgrading to an mmap read lock when tearing
>>> +	 * down page tables.
>>> +	 *
>>> +	 * When walking kernel page tables, an mmap write lock must also be held
>>> +	 * to account for page table freeing on vmap huge page mapping.
>>> +	 */
>>> +	mmap_assert_write_locked(mm);
>>> +
>>>  	/* For convenience, we allow traversal of kernel mappings. */
>>>  	if (mm == &init_mm)
>>>  		return walk_kernel_page_table_range(start, end, ops,
>>> @@ -700,16 +712,6 @@ int walk_page_range_debug(struct mm_struct *mm, unsigned long start,
>>>  	if (!check_ops_safe(ops))
>>>  		return -EINVAL;
>>>
>>> -	/*
>>> -	 * The mmap lock protects the page walker from changes to the page
>>> -	 * tables during the walk.  However a read lock is insufficient to
>>> -	 * protect those areas which don't have a VMA as munmap() detaches
>>> -	 * the VMAs before downgrading to a read lock and actually tearing
>>> -	 * down PTEs/page tables. In which case, the mmap write lock should
>>> -	 * be held.
>>> -	 */
>>> -	mmap_assert_write_locked(mm);
>>> -
>>>  	return walk_pgd_range(start, end, &walk);
>>>  }
>>>
>>> diff --git a/mm/vmalloc.c b/mm/vmalloc.c
>>> index 1afca3568b9b..9d0f1fdd6af3 100644
>>> --- a/mm/vmalloc.c
>>> +++ b/mm/vmalloc.c
>>> @@ -43,6 +43,7 @@
>>>  #include <asm/tlbflush.h>
>>>  #include <asm/shmparam.h>
>>>  #include <linux/page_owner.h>
>>> +#include <linux/cleanup.h>
>>>
>>>  #define CREATE_TRACE_POINTS
>>>  #include <trace/events/vmalloc.h>
>>> @@ -158,10 +159,25 @@ static int vmap_try_huge_pmd(pmd_t *pmd, unsigned long addr, unsigned long end,
>>>  	if (!IS_ALIGNED(phys_addr, PMD_SIZE))
>>>  		return 0;
>>>
>>> -	if (pmd_present(*pmd) && !pmd_free_pte_page(pmd, addr))
>>> -		return 0;
>>> +	if (!pmd_present(*pmd))
>>> +		return pmd_set_huge(pmd, phys_addr, prot);
>>>
>>> -	return pmd_set_huge(pmd, phys_addr, prot);
>>> +	/*
>>> +	 * Kernel page table walkers either walk ranges they own exclusively
>>> +	 * using the mmap lock for mutual exclusion, or hold the mmap write lock
>>> +	 * on init_mm (ptdump being the motivating case).
>>> +	 *
>>> +	 * Therefore, acquire the mmap read lock to prevent use-after-free when
>>> +	 * freeing page tables.
>>> +	 */
>>> +#ifndef CONFIG_ARM64
>>> +	scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm)
>>> +#endif
>>> +	{
>>> +		if (!pmd_free_pte_page(pmd, addr))
>>> +			return 0;
>>> +		return pmd_set_huge(pmd, phys_addr, prot);
>>> +	}
>>>  }
>>>
>> Note that we do not need to take the lock around pmd_set_huge - we don't
>> care if ptdump observes a temporarily cleared pmd entry. So how about keeping
>> this outside the guard block. Otherwise right now we have an inconsistency:
>> for !pmd_present() we do pmd_set_huge() without locking, but for pmd_present()
>> we do pmd_set_huge() with locking.
> 
> As I said in the commit message I'm intentionally taking the lock around all of
> it so a concurrent ptdump sees either the leaf entry or the non-leaf entry.
> 
> By doing that we can easiliy avoid the situation where a ptdump gives you
> inconsistent output and it makes more sense logically.
> 
> So this is the opposite of inconsistent - if !pmd_present() the ptdump may
> observe the _genuine_ state of there not being an entry before. With
> pmd_present() it either observes what was or what became, not something
> inbetween :)

Yeah okay fair enough.


> 
>>
>>
> 
> Thanks, Lorenzo


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing
  2026-07-12  8:46   ` Lorenzo Stoakes
@ 2026-07-12 11:34     ` Dev Jain
  0 siblings, 0 replies; 21+ messages in thread
From: Dev Jain @ 2026-07-12 11:34 UTC (permalink / raw)
  To: Lorenzo Stoakes
  Cc: Andrew Morton, Suren Baghdasaryan, Liam R. Howlett,
	Vlastimil Babka, Shakeel Butt, David Hildenbrand, Mike Rapoport,
	Michal Hocko, Uladzislau Rezki, Toshi Kani, Catalin Marinas,
	Will Deacon, David Carlier, Ryan Roberts, linux-mm, linux-kernel,
	linux-arm-kernel, stable, syzbot+fd95a72470f5a44e464c



On 12/07/26 2:16 pm, Lorenzo Stoakes wrote:
> On Sun, Jul 12, 2026 at 12:50:08PM +0530, Dev Jain wrote:
>> Will Deacon had pushed back on a similar approach:
>> https://lore.kernel.org/all/20250530123527.GA30463@willie-the-truck/
>>
>> Although now when I read back that thread, it feels more so like my
>> incompetency to convince :) because:
> 
> No haha not so, I think more like this stuff is fiddly.
> 
>>
>> 1. I don't think this pmd_free_pte_page() path is a hot path at all
> 
> Right, and we don't actually alter that path anyway
> 
>>
>> 2. We are doing a try lock which is almost guaranteed to succeed,
>>    so it's not like we are losing out on block mappings
> 
> Also it's specifically only on when vmap tries to make a mapping huge, and
> this path is being inconsistent with a convention that already existed - if
> you manipulate kernel page table mappings that can interact with other page
> table walkers, you have to take the init_mm mmap lock.
> 
>>
>> 3. Any overhead from the try lock will get dominated by the pgtable
>>    page free/TLB flush
> 
> Yup.
> 
>>
>> I guess you did not take the RCU approach because that would put code
>> into the generic kernel pgtable freeing path.
> 
> Well a number of reasons:
> 
> * firstly yes it makes the code path always RCU only to suit a specific
>   debug user as you say :)
> 
> * Importantly - we risk genuine RCU stall issues, because the ptdump then
>   has to be RCU too over vast ranges.
> 
>   To work around that you have to shard the ptdump walk, make an assumption
>   all callbacks are RCU-safe, and that the sharding suffices to avoid these
>   stalls.
> 
>   It's a ton of complexity and assumptions to account for... vmalloc doing
>   the wrong thing.
> 
> * It is an established precedent that we mmap lock init_mm for kernel page
>   table walking as per mm/pagewalk.c. It'd require significant rework there
>   and would disallow any future walkers like this if we were to require
>   RCU.
> 
> * The mmap lock approach is simple, safe, and as you say is only actually
>   required in code paths that manipulate page tables and thus are already
>   not hotpaths.
> 
> * If there's future work to free vmalloc page tables upon vunmap()
>   (currently it does not), we have a stable, established basis for doing so
>   that again puts the weight of the work on the operation being performed
>   rather than anything else.
> 
>>
>> I liked the RCU approach because I hate the fact that ptdump takes
>> an mmap_write_lock when it is literally only reading the pgtables.
> 
> Well you have to do that for the userland side, because there could be a
> concurrent downgraded mmap read lock during an munmap, and the same goes
> for non-VMA kernel ranges too, so it would have to keep doing that
> regardless.

Oh right, I didn't know x86 was using ptdump for user tables too.


> 
>> But your approach is simpler and fixes the problem at the particular spot
>> and not hammers the fix into a generic path. So overall, ACK.
> 
> Thanks!
> 
>>
>>
>>> Lorenzo Stoakes (2):
>>>       mm/vmalloc: acquire init_mm read lock on huge vmap promotion
>>>       Revert "arm64: Enable vmalloc-huge with ptdump"
>>>
>>>  arch/arm64/include/asm/ptdump.h |  2 --
>>>  arch/arm64/mm/mmu.c             | 43 ++++-------------------------------------
>>>  arch/arm64/mm/ptdump.c          | 11 ++---------
>>>  include/linux/mmap_lock.h       |  1 +
>>>  mm/pagewalk.c                   | 22 +++++++++++----------
>>>  mm/vmalloc.c                    | 41 ++++++++++++++++++++++++++++++---------
>>>  6 files changed, 51 insertions(+), 69 deletions(-)
>>> ---
>>> base-commit: a635d6748234582ea287c5ffeae28b9b23f91c7e
>>> change-id: 20260710-series-vmap-race-fix-2a4cac988938
>>>
>>> Cheers,
>>
> 
> Cheers, Lorenzo


^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2026-07-12 11:34 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 10:49 [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing Lorenzo Stoakes
2026-07-10 10:49 ` [PATCH 1/2] mm/vmalloc: acquire init_mm read lock on huge vmap promotion Lorenzo Stoakes
2026-07-10 12:57   ` Lorenzo Stoakes
2026-07-10 13:24     ` Lorenzo Stoakes
2026-07-11 10:23   ` Mike Rapoport
2026-07-12  7:43   ` Dev Jain
2026-07-12  8:01     ` Greg KH
2026-07-12  9:27       ` Lorenzo Stoakes
2026-07-12 11:15       ` Dev Jain
2026-07-12  8:22     ` Lorenzo Stoakes
2026-07-12 11:16       ` Dev Jain
2026-07-10 10:49 ` [PATCH 2/2] Revert "arm64: Enable vmalloc-huge with ptdump" Lorenzo Stoakes
2026-07-11 10:15   ` Mike Rapoport
2026-07-12  8:27     ` Lorenzo Stoakes
2026-07-12  7:46   ` Dev Jain
2026-07-12  8:28     ` Lorenzo Stoakes
2026-07-10 11:44 ` [PATCH 0/2] mm: fix UAF caused by race between ptdump and vmap pgtable freeing David CARLIER
2026-07-10 11:57   ` Lorenzo Stoakes
2026-07-12  7:20 ` Dev Jain
2026-07-12  8:46   ` Lorenzo Stoakes
2026-07-12 11:34     ` Dev Jain

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox