Skip to content

[PW_SID:1122667] [v8] mm: pgtable: free kernel page tables via RCU to fix ptdump UAF#2231

Closed
linux-riscv-bot wants to merge 2 commits into
workflow__riscv__fixesfrom
pw1122667
Closed

[PW_SID:1122667] [v8] mm: pgtable: free kernel page tables via RCU to fix ptdump UAF#2231
linux-riscv-bot wants to merge 2 commits into
workflow__riscv__fixesfrom
pw1122667

Conversation

@linux-riscv-bot

Copy link
Copy Markdown

PR for series 1122667 applied to workflow__riscv__fixes

Name: [v8] mm: pgtable: free kernel page tables via RCU to fix ptdump UAF
URL: https://patchwork.kernel.org/project/linux-riscv/list/?series=1122667
Version: 8

Linux RISC-V bot and others added 2 commits July 1, 2026 03:48
ptdump_walk_pgd() walks the kernel page tables under get_online_mems().
That does not stop vmalloc from freeing a kernel PTE page underneath the
walk.

When vmap_try_huge_pmd() promotes a range to a huge PMD it collapses the
existing PTE table and frees it via pmd_free_pte_page(). On x86, riscv and
powerpc this runs without the init_mm mmap lock; only arm64 takes it, and
not on the block-split path. So ptdump can dereference a just-freed PTE
page, which is the use after free syzbot hit in ptdump_pte_entry().

The race is not new. ptdump walks the whole kernel address space, including
ranges other code is actively mapping, so it reads page tables it does not
own. Commit 5ba2f0a ("mm: introduce deferred freeing for kernel page
tables") only widened the window; the Fixes tag points there for that
reason.

Every other walker works on a range it owns and is the only one mutating
it: set_memory() on arm64/riscv/loongarch, the arm64 block-split path, the
openrisc DMA path and the hugetlb_vmemmap remap. Nothing frees those ranges
concurrently, so they cannot race and do not need RCU. ptdump is the only
walker that traverses ranges it does not own.

Defer the free by an RCU grace period. pagetable_free_kernel() now frees
via call_rcu() in both the async and non-async configs. The async path
still flushes the TLB first, then queues the per-page RCU free. The page
stays valid until any walk that may have observed it drops its RCU read
lock.

x86, arm64 and riscv reach pagetable_free_kernel() for the collapsed PTE
page (the ptdesc carries PT_kernel), so the deferral covers them. powerpc
uses its own fragment allocator: pte_free_kernel() there frees the page
synchronously via pte_fragment_free() and the ptdesc is not flagged kernel,
so defer the kernel case in pte_fragment_free() as well. arm64 also takes
init_mm.mmap_lock around the free under a static key; that is now redundant
with the RCU deferral but left in place.

On the read side walk_page_range_debug() walks the init_mm range in bounded
chunks, taking rcu_read_lock() around each chunk and calling cond_resched()
between them. It uses the lockless walker as the mmap lock is no longer
held, and pgd_addr_end() to bound each chunk without overflowing at the top
of the address space. A walker either sees the cleared PMD and skips, or
keeps the page alive until it drops the lock. The owned-range walkers are
unchanged.

Stop taking mmap_write_lock() in ptdump_walk_pgd() for init_mm. It never
guarded against this free -- most architectures free the collapsed PTE
table without it -- and RCU now provides the synchronisation. efi_mm and
current->mm page tables are not RCU-freed, so they keep the mmap write
lock.

ptdump callbacks run under RCU within a chunk, so they must not sleep. The
arch note_page() and effective_prot() callbacks only format into the
preallocated seq_file buffer; the only GFP_KERNEL marker setup runs before
the walk, and cond_resched() happens between chunks, outside the read lock.

Fixes: 5ba2f0a ("mm: introduce deferred freeing for kernel page tables")
Reported-by: syzbot+fd95a72470f5a44e464c@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/all/6a287988.39669fcc.33b062.00a0.GAE@google.com/T/
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: David Carlier <devnexen@gmail.com>
Signed-off-by: Linux RISC-V bot <linux.riscv.bot@gmail.com>
@linux-riscv-bot
linux-riscv-bot force-pushed the workflow__riscv__fixes branch 7 times, most recently from 869345e to f54dc6c Compare July 13, 2026 20:05
@linux-riscv-bot
linux-riscv-bot deleted the pw1122667 branch July 14, 2026 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants