Re: [PATCH mm-hotfixes v4 0/4] mm: fix UAF caused by race between ptdump and vmap pgtable freeing
From: Andrew Morton
Date: Thu Jul 16 2026 - 19:42:01 EST
On Thu, 16 Jul 2026 22:31:11 +0100 "Lorenzo Stoakes (ARM)" <ljs@xxxxxxxxxx> 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().
>
> ...
>
> This series works around this by #ifndef CONFIG_ARM64'ing the mmap read
> lock in vmap logic, then partially reverting commit fa93b45fd397 ("arm64:
> Enable vmalloc-huge with ptdump"), keeping the enablement of huge vmap
> support, and removing the ifdeffery with the partial revert patch.
Thanks, I've updated mm.git's mm-hotfixes-unstable branch to this
version.
> v4:
> * Rebased on latest master of Linus's tree.
> * Accumulated tags, thanks everybody!
> * Updated commit messages further as per Kiryl.
> * Took the mmap write lock across the whole CPA operation as per Will.
Here's how v4 altered mm.git:
arch/x86/mm/pat/set_memory.c | 25 +++++++++++++++----------
include/linux/mmap_lock.h | 2 ++
mm/vmalloc.c | 8 ++++----
3 files changed, 21 insertions(+), 14 deletions(-)
--- a/arch/x86/mm/pat/set_memory.c~b
+++ a/arch/x86/mm/pat/set_memory.c
@@ -411,7 +411,7 @@ static void __cpa_flush_tlb(void *data)
static int collapse_large_pages(unsigned long addr, struct list_head *pgtables);
-static void cpa_collapse_large_pages(struct cpa_data *cpa)
+static void __cpa_collapse_large_pages(struct cpa_data *cpa)
{
unsigned long start, addr, end;
struct ptdesc *ptdesc, *tmp;
@@ -437,17 +437,22 @@ static void cpa_collapse_large_pages(str
flush_tlb_all();
+ list_for_each_entry_safe(ptdesc, tmp, &pgtables, pt_list) {
+ list_del(&ptdesc->pt_list);
+ pagetable_free(ptdesc);
+ }
+}
+
+static void cpa_collapse_large_pages(struct cpa_data *cpa)
+{
/*
- * ptdump might read these page tables, so avoid a use-after-free by
- * acquiring the mmap read lock on init_mm (ptdump acquires the mmap
- * write lock).
+ * Take the mmap write lock on init_mm to:
+ * - Avoid a use-after-free if raced by ptdump (which takes its own
+ * write lock on init_mm).
+ * - Serialise concurrent CPA walkers.
*/
- scoped_guard(mmap_read_lock, &init_mm) {
- list_for_each_entry_safe(ptdesc, tmp, &pgtables, pt_list) {
- list_del(&ptdesc->pt_list);
- pagetable_free(ptdesc);
- }
- }
+ scoped_guard(mmap_write_lock, &init_mm)
+ __cpa_collapse_large_pages(cpa);
}
static void cpa_flush(struct cpa_data *cpa, int cache)
--- a/include/linux/mmap_lock.h~b
+++ a/include/linux/mmap_lock.h
@@ -621,6 +621,8 @@ static inline void mmap_read_unlock(stru
DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
mmap_read_lock(_T), mmap_read_unlock(_T))
+DEFINE_GUARD(mmap_write_lock, struct mm_struct *,
+ mmap_write_lock(_T), mmap_write_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)
--- a/mm/vmalloc.c~b
+++ a/mm/vmalloc.c
@@ -163,11 +163,11 @@ static int vmap_try_huge_pmd(pmd_t *pmd,
return pmd_set_huge(pmd, phys_addr, prot);
/*
- * Acquire the mmap read lock to exclude ptdump, which walks kernel
- * page tables it does not own under the mmap write lock.
+ * Acquire the mmap read lock to exclude ptdump, which walks
+ * kernel page tables it does not own under the mmap write lock.
*
- * Concurrent read lock holders are safe: each exclusively owns the
- * range it operates on and cannot reach this page table.
+ * Concurrent read lock holders are safe: each exclusively owns
+ * the range it operates on and cannot reach this page table.
*/
scoped_cond_guard(mmap_read_lock_try, return 0, &init_mm) {
if (!pmd_free_pte_page(pmd, addr))
_