* [PATCH 0/6] userfaultfd/pagemap: pre-existing fixes
@ 2026-05-29 17:23 Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 1/6] fs/proc/task_mmu: fix make_uffd_wp_huge_pte() prot-update race Kiryl Shutsemau (Meta)
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Kiryl Shutsemau (Meta) @ 2026-05-29 17:23 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes, Mike Rapoport,
David Hildenbrand, Kiryl Shutsemau (Meta)
These are pre-existing bug fixes that were carried at the front of the
userfaultfd RWP working-set-tracking series up to v5 [1]. Per review
feedback that fixes should not sit in the middle of a feature series,
they are split out and sent on their own; the RWP series is reposted
rebased on top of this.
All six were flagged by the Sashiko AI review of the RWP series and
carry Reported-by: Sashiko AI review <sashiko-bot@kernel.org>. They are
independent of RWP, apply to mm-new directly, and carry Cc: stable@.
1: fs/proc/task_mmu: a missing huge_ptep_modify_prot_start() in
make_uffd_wp_huge_pte() can lose hardware Dirty/Accessed updates
when PAGEMAP_SCAN write-protects a hugetlb PTE.
2: fs/proc/task_mmu: pagemap_scan_hugetlb_entry() compares the range
against HPAGE_SIZE rather than the hstate page size, so it never
write-protects gigantic hugetlb pages.
3: fs/proc/task_mmu: PAGEMAP_SCAN with PM_SCAN_WP_MATCHING over an
unpopulated hugetlb range self-deadlocks -- pagemap_scan_pte_hole()
calls uffd_wp_range() while walk_hugetlb_range() holds the hugetlb
vma lock for read, and hugetlb_change_protection() then takes it
for write. Install the marker inline instead.
4: mm/huge_memory: change_non_present_huge_pmd() drops pmd_swp_uffd_wp
on a device-private PMD permission downgrade, silently losing the
uffd-wp marker.
5: userfaultfd: must_wait() applies pte_write() to a locklessly read
PTE without checking pte_present(), so swap/migration entries
decode random offset bits and a thread can stay parked on a stale
fault.
6: userfaultfd: __VMA_UFFD_FLAGS feeds VMA_UFFD_MINOR_BIT (41) to
mk_vma_flags() unconditionally, an out-of-bounds write into the
single-word vma_flags_t on 32-bit. Build the mask from config-gated
per-mode masks so an unavailable bit is never materialised.
[1] https://lore.kernel.org/all/20260526130509.2748441-1-kirill@shutemov.name/
Kiryl Shutsemau (Meta) (6):
fs/proc/task_mmu: fix make_uffd_wp_huge_pte() prot-update race
fs/proc/task_mmu: use huge_page_size() in pagemap_scan_hugetlb_entry()
fs/proc/task_mmu: fix hugetlb self-deadlock in pagemap_scan_pte_hole()
mm/huge_memory: preserve pmd_swp_uffd_wp on device-private PMD
downgrade
userfaultfd: gate must_wait writability check on pte_present()
userfaultfd: build __VMA_UFFD_FLAGS from config-gated masks
fs/proc/task_mmu.c | 73 ++++++++++++++++++++++++++++++++---
include/linux/mm.h | 39 +++++++++++++++++++
include/linux/userfaultfd_k.h | 4 +-
mm/huge_memory.c | 2 +
mm/userfaultfd.c | 20 ++++++++++
5 files changed, 130 insertions(+), 8 deletions(-)
base-commit: 449a5df98f8dffa9b037e3b6838fc5af327df072
--
2.54.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/6] fs/proc/task_mmu: fix make_uffd_wp_huge_pte() prot-update race
2026-05-29 17:23 [PATCH 0/6] userfaultfd/pagemap: pre-existing fixes Kiryl Shutsemau (Meta)
@ 2026-05-29 17:23 ` Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 2/6] fs/proc/task_mmu: use huge_page_size() in pagemap_scan_hugetlb_entry() Kiryl Shutsemau (Meta)
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Kiryl Shutsemau (Meta) @ 2026-05-29 17:23 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes, Mike Rapoport,
David Hildenbrand, Kiryl Shutsemau (Meta), stable,
Sashiko AI review, Liam R. Howlett, Vlastimil Babka, Jann Horn,
Pedro Falcato, Michał Mirosław, Muhammad Usama Anjum,
Stephen Rothwell, Arnd Bergmann, linux-fsdevel
make_uffd_wp_huge_pte() arms the UFFD_WP bit on a present HugeTLB PTE by
calling huge_ptep_modify_prot_commit() with a ptent snapshot that was
fetched without the corresponding huge_ptep_modify_prot_start(). The
start helper is what atomically clears the entry so the kernel-owned
snapshot stays consistent until the commit; without it, the hardware
may set Dirty or Accessed in the live PTE between the original read
and the commit, and huge_ptep_modify_prot_commit() (whose generic
implementation just calls set_huge_pte_at()) then writes the stale
snapshot back over the live hardware bits, losing the update.
The non-hugetlb sibling make_uffd_wp_pte() does this correctly via
ptep_modify_prot_start() / ptep_modify_prot_commit(). Mirror that
pattern for the present-PTE branch. The migration case stays as-is --
migration entries are non-present, so there's no hardware update to
race against.
Fixes: 52526ca7fdb9 ("fs/proc/task_mmu: implement IOCTL to get and optionally clear info about PTEs")
Cc: stable@vger.kernel.org
Reported-by: Sashiko AI review <sashiko-bot@kernel.org>
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
---
fs/proc/task_mmu.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index 1e3a15bf46f4..e21a38ac745b 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -2610,12 +2610,16 @@ static void make_uffd_wp_huge_pte(struct vm_area_struct *vma,
if (softleaf_is_hwpoison(entry) || softleaf_is_marker(entry))
return;
- if (softleaf_is_migration(entry))
+ if (softleaf_is_migration(entry)) {
set_huge_pte_at(vma->vm_mm, addr, ptep,
pte_swp_mkuffd_wp(ptent), psize);
- else
- huge_ptep_modify_prot_commit(vma, addr, ptep, ptent,
- huge_pte_mkuffd_wp(ptent));
+ } else {
+ pte_t old_pte, new_pte;
+
+ old_pte = huge_ptep_modify_prot_start(vma, addr, ptep);
+ new_pte = huge_pte_mkuffd_wp(old_pte);
+ huge_ptep_modify_prot_commit(vma, addr, ptep, old_pte, new_pte);
+ }
}
#endif /* CONFIG_HUGETLB_PAGE */
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/6] fs/proc/task_mmu: use huge_page_size() in pagemap_scan_hugetlb_entry()
2026-05-29 17:23 [PATCH 0/6] userfaultfd/pagemap: pre-existing fixes Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 1/6] fs/proc/task_mmu: fix make_uffd_wp_huge_pte() prot-update race Kiryl Shutsemau (Meta)
@ 2026-05-29 17:23 ` Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 3/6] fs/proc/task_mmu: fix hugetlb self-deadlock in pagemap_scan_pte_hole() Kiryl Shutsemau (Meta)
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Kiryl Shutsemau (Meta) @ 2026-05-29 17:23 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes, Mike Rapoport,
David Hildenbrand, Kiryl Shutsemau (Meta), stable,
Sashiko AI review, Liam R. Howlett, Vlastimil Babka, Jann Horn,
Pedro Falcato, Michał Mirosław, Muhammad Usama Anjum,
Arnd Bergmann, Andrei Vagin, linux-fsdevel
The partial-page check compares against HPAGE_SIZE (PMD_SIZE), which
is wrong for gigantic hugetlb hstates (e.g. 1G). The walker hands the
callback a huge_page_size()-sized range, never start + HPAGE_SIZE, so
the comparison always declares it partial and aborts the WP. Compare
against the actual hstate's page size.
Fixes: 52526ca7fdb9 ("fs/proc/task_mmu: implement IOCTL to get and optionally clear info about PTEs")
Cc: stable@vger.kernel.org
Reported-by: Sashiko AI review <sashiko-bot@kernel.org>
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
---
fs/proc/task_mmu.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index e21a38ac745b..1489c67e88f7 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -2960,7 +2960,7 @@ static int pagemap_scan_hugetlb_entry(pte_t *ptep, unsigned long hmask,
if (~categories & PAGE_IS_WRITTEN)
goto out_unlock;
- if (end != start + HPAGE_SIZE) {
+ if (end != start + huge_page_size(hstate_vma(vma))) {
/* Partial HugeTLB page WP isn't possible. */
pagemap_scan_backout_range(p, start, end);
p->arg.walk_end = start;
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/6] fs/proc/task_mmu: fix hugetlb self-deadlock in pagemap_scan_pte_hole()
2026-05-29 17:23 [PATCH 0/6] userfaultfd/pagemap: pre-existing fixes Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 1/6] fs/proc/task_mmu: fix make_uffd_wp_huge_pte() prot-update race Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 2/6] fs/proc/task_mmu: use huge_page_size() in pagemap_scan_hugetlb_entry() Kiryl Shutsemau (Meta)
@ 2026-05-29 17:23 ` Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 4/6] mm/huge_memory: preserve pmd_swp_uffd_wp on device-private PMD downgrade Kiryl Shutsemau (Meta)
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Kiryl Shutsemau (Meta) @ 2026-05-29 17:23 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes, Mike Rapoport,
David Hildenbrand, Kiryl Shutsemau (Meta), stable,
Sashiko AI review, Liam R. Howlett, Vlastimil Babka, Jann Horn,
Pedro Falcato, Michał Mirosław, Muhammad Usama Anjum,
Andrei Vagin, Stephen Rothwell, linux-fsdevel
A PAGEMAP_SCAN ioctl requesting PM_SCAN_WP_MATCHING on a hugetlb VMA hangs
the calling thread, unkillably, as soon as the scan reaches an unpopulated
part of the range:
do_pagemap_scan()
walk_page_range()
walk_hugetlb_range()
hugetlb_vma_lock_read() # take the vma lock for read ...
pagemap_scan_pte_hole() # ... ->pte_hole() for a hole
uffd_wp_range()
change_protection()
hugetlb_change_protection()
hugetlb_vma_lock_write() # ... and block taking it for write
walk_hugetlb_range() holds the hugetlb vma lock for read across the whole
walk. A present entry goes to ->hugetlb_entry(); an unpopulated one goes
to ->pte_hole(), i.e. pagemap_scan_pte_hole(). To write-protect the hole
that handler calls uffd_wp_range(), which on a hugetlb VMA reaches
hugetlb_change_protection() and takes the same vma lock for write. The
thread then blocks in down_write() waiting for the read lock it is itself
holding.
The populated path avoids this: pagemap_scan_hugetlb_entry() write-protects
the entry inline under the page-table lock and never enters
hugetlb_change_protection().
Do the same for holes. Fault in the page table and install the uffd-wp
marker directly with make_uffd_wp_huge_pte() under the page-table lock,
rather than routing through uffd_wp_range(). That is the same sequence
hugetlb_change_protection() runs for an unpopulated entry, minus the vma
write lock -- which is safe to skip because PMD sharing is disabled on
uffd-wp VMAs (hugetlb_unshare_all_pmds() runs at registration), leaving
nothing for that lock to serialise against.
Fixes: 52526ca7fdb9 ("fs/proc/task_mmu: implement IOCTL to get and optionally clear info about PTEs")
Cc: stable@vger.kernel.org
Reported-by: Sashiko AI review <sashiko-bot@kernel.org>
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
Assisted-by: Claude:claude-opus-4-8
---
fs/proc/task_mmu.c | 59 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index 1489c67e88f7..06fb94a965ff 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -2977,8 +2977,62 @@ static int pagemap_scan_hugetlb_entry(pte_t *ptep, unsigned long hmask,
return ret;
}
+
+/*
+ * Write-protect the unpopulated hugetlb entries covering [addr, end) by
+ * installing uffd-wp markers inline, exactly as pagemap_scan_hugetlb_entry()
+ * does for populated entries.
+ *
+ * walk_hugetlb_range() currently calls ->pte_hole() once per huge page, so the
+ * loop normally runs a single iteration; it is written to cover the full range
+ * in case the walker ever coalesces adjacent holes.
+ *
+ * The obvious route -- uffd_wp_range() -> hugetlb_change_protection() --
+ * cannot be used here: it takes hugetlb_vma_lock_write(), but the page-table
+ * walker (walk_hugetlb_range()) already holds hugetlb_vma_lock_read() on the
+ * same VMA, so the scanning thread would deadlock against itself. PMD sharing
+ * is disabled on uffd-wp VMAs (hugetlb_unshare_all_pmds() at registration), so
+ * the vma lock guards nothing that matters for these entries anyway.
+ */
+static int pagemap_scan_hugetlb_hole_wp(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end)
+{
+ struct hstate *h = hstate_vma(vma);
+ unsigned long psize = huge_page_size(h);
+ struct mm_struct *mm = vma->vm_mm;
+ spinlock_t *ptl;
+ pte_t *ptep;
+ pte_t pte;
+
+ for (addr = ALIGN_DOWN(addr, psize); addr < end; addr += psize) {
+ ptep = huge_pte_alloc(mm, vma, addr, psize);
+ if (!ptep)
+ return -ENOMEM;
+
+ i_mmap_lock_write(vma->vm_file->f_mapping);
+ ptl = huge_pte_lock(h, mm, ptep);
+ pte = huge_ptep_get(mm, addr, ptep);
+ make_uffd_wp_huge_pte(vma, addr, ptep, pte);
+ /*
+ * A none entry has no cached translation, so installing the
+ * marker needs no TLB flush. Flush only if a fault populated
+ * the entry between huge_pte_alloc() and the page table lock.
+ */
+ if (!huge_pte_none(pte))
+ flush_hugetlb_tlb_range(vma, addr, addr + psize);
+ spin_unlock(ptl);
+ i_mmap_unlock_write(vma->vm_file->f_mapping);
+ }
+
+ return 0;
+}
#else
#define pagemap_scan_hugetlb_entry NULL
+static int pagemap_scan_hugetlb_hole_wp(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end)
+{
+ return 0;
+}
#endif
static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,
@@ -2998,7 +3052,10 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end,
if (~p->arg.flags & PM_SCAN_WP_MATCHING)
return ret;
- err = uffd_wp_range(vma, addr, end - addr, true);
+ if (is_vm_hugetlb_page(vma))
+ err = pagemap_scan_hugetlb_hole_wp(vma, addr, end);
+ else
+ err = uffd_wp_range(vma, addr, end - addr, true);
if (err < 0)
ret = err;
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 4/6] mm/huge_memory: preserve pmd_swp_uffd_wp on device-private PMD downgrade
2026-05-29 17:23 [PATCH 0/6] userfaultfd/pagemap: pre-existing fixes Kiryl Shutsemau (Meta)
` (2 preceding siblings ...)
2026-05-29 17:23 ` [PATCH 3/6] fs/proc/task_mmu: fix hugetlb self-deadlock in pagemap_scan_pte_hole() Kiryl Shutsemau (Meta)
@ 2026-05-29 17:23 ` Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 5/6] userfaultfd: gate must_wait writability check on pte_present() Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 6/6] userfaultfd: build __VMA_UFFD_FLAGS from config-gated masks Kiryl Shutsemau (Meta)
5 siblings, 0 replies; 7+ messages in thread
From: Kiryl Shutsemau (Meta) @ 2026-05-29 17:23 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes, Mike Rapoport,
David Hildenbrand, Kiryl Shutsemau (Meta), stable,
Sashiko AI review, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Balbir Singh, Matthew Brost
change_non_present_huge_pmd() rewrites a writable device-private PMD
swap entry into a readable one without carrying pmd_swp_uffd_wp()
across. The PTE-level change_softleaf_pte() does this correctly;
mirror that here, matching what copy_huge_pmd() does for the fork
path. Without the carry, a plain mprotect() over a UFFD_WP-marked
device-private THP strips the bit and the trap is bypassed on
swap-in.
Fixes: 368076f52ebe ("mm/huge_memory: add device-private THP support to PMD operations")
Cc: stable@vger.kernel.org
Reported-by: Sashiko AI review <sashiko-bot@kernel.org>
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
---
mm/huge_memory.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 42b86e8ab7c0..b7c895b1d366 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2663,6 +2663,8 @@ static void change_non_present_huge_pmd(struct mm_struct *mm,
} else if (softleaf_is_device_private_write(entry)) {
entry = make_readable_device_private_entry(swp_offset(entry));
newpmd = swp_entry_to_pmd(entry);
+ if (pmd_swp_uffd_wp(*pmd))
+ newpmd = pmd_swp_mkuffd_wp(newpmd);
} else {
newpmd = *pmd;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 5/6] userfaultfd: gate must_wait writability check on pte_present()
2026-05-29 17:23 [PATCH 0/6] userfaultfd/pagemap: pre-existing fixes Kiryl Shutsemau (Meta)
` (3 preceding siblings ...)
2026-05-29 17:23 ` [PATCH 4/6] mm/huge_memory: preserve pmd_swp_uffd_wp on device-private PMD downgrade Kiryl Shutsemau (Meta)
@ 2026-05-29 17:23 ` Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 6/6] userfaultfd: build __VMA_UFFD_FLAGS from config-gated masks Kiryl Shutsemau (Meta)
5 siblings, 0 replies; 7+ messages in thread
From: Kiryl Shutsemau (Meta) @ 2026-05-29 17:23 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes, Mike Rapoport,
David Hildenbrand, Kiryl Shutsemau (Meta), stable,
Sashiko AI review, Peter Xu, Mike Kravetz, Andrea Arcangeli,
Jerome Glisse
userfaultfd_must_wait() and userfaultfd_huge_must_wait() read the PTE
without taking the page table lock and then apply pte_write() /
huge_pte_write() to it. Those accessors decode bits from the present
encoding only; on a swap or migration entry they read the offset bits
that happen to share the same position and return an undefined result.
The intent of the check is "is this fault still WP-blocked?". A
non-marker swap entry means the page is in transit -- the userfault
context the original fault delivered against is no longer the same,
and the swap-in or migration completion path will re-deliver a fresh
fault if userspace still needs to handle it. Worst case under the
current code the garbage write bit says "wait", and the thread stays
asleep until a UFFDIO_WAKE that may never arrive.
Gate the writability check on pte_present() so the lockless re-check
only inspects present-PTE bits when the entry is actually present.
The non-present, non-marker case returns "don't wait" and lets the
fault path retry.
Fixes: 369cd2121be4 ("userfaultfd: hugetlbfs: userfaultfd_huge_must_wait for hugepmd ranges")
Fixes: 63b2d4174c4a ("userfaultfd: wp: add the writeprotect API to userfaultfd ioctl")
Cc: stable@vger.kernel.org
Reported-by: Sashiko AI review <sashiko-bot@kernel.org>
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
---
mm/userfaultfd.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index 35b206cc9aa6..f6d2a1c67019 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -2535,6 +2535,15 @@ static inline bool userfaultfd_huge_must_wait(struct userfaultfd_ctx *ctx,
/* UFFD PTE markers require userspace to resolve the fault. */
if (pte_is_uffd_marker(pte))
return true;
+ /*
+ * Concurrent migration may have replaced the present PTE with a
+ * non-marker swap entry between fault delivery and this lockless
+ * re-check. huge_pte_write() on a swap entry decodes random offset
+ * bits, so gate it on pte_present(). The migration completion path
+ * will re-deliver the fault if it still needs userspace.
+ */
+ if (!pte_present(pte))
+ return false;
/*
* If VMA has UFFD WP faults enabled and WP fault, wait for userspace to
* resolve the fault.
@@ -2621,6 +2630,17 @@ static inline bool userfaultfd_must_wait(struct userfaultfd_ctx *ctx,
/* UFFD PTE markers require userspace to resolve the fault. */
if (pte_is_uffd_marker(ptent))
goto out;
+ /*
+ * Concurrent swap-out / migration may have replaced the present PTE
+ * with a non-marker swap entry between fault delivery and this
+ * lockless re-check. pte_write() on a swap entry decodes random
+ * offset bits, so gate it on pte_present(). The page-in path will
+ * re-deliver the fault if it still needs userspace.
+ */
+ if (!pte_present(ptent)) {
+ ret = false;
+ goto out;
+ }
/*
* If VMA has UFFD WP faults enabled and WP fault, wait for userspace to
* resolve the fault.
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 6/6] userfaultfd: build __VMA_UFFD_FLAGS from config-gated masks
2026-05-29 17:23 [PATCH 0/6] userfaultfd/pagemap: pre-existing fixes Kiryl Shutsemau (Meta)
` (4 preceding siblings ...)
2026-05-29 17:23 ` [PATCH 5/6] userfaultfd: gate must_wait writability check on pte_present() Kiryl Shutsemau (Meta)
@ 2026-05-29 17:23 ` Kiryl Shutsemau (Meta)
5 siblings, 0 replies; 7+ messages in thread
From: Kiryl Shutsemau (Meta) @ 2026-05-29 17:23 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes, Mike Rapoport,
David Hildenbrand, Kiryl Shutsemau (Meta), stable,
Sashiko AI review, Liam R. Howlett, Vlastimil Babka,
Suren Baghdasaryan, Michal Hocko, Peter Xu, Pedro Falcato,
Alice Ryhl
The VMA flags bitmap is a single word today: NUM_VMA_FLAG_BITS is
BITS_PER_LONG, so on 32-bit vma_flags_t holds only 32 bits. (The bitmap
type exists so this can grow past BITS_PER_LONG later; until it does,
anything declared above the first word is out of range on 32-bit.) The bit
enum nevertheless declares some bits unconditionally above BITS_PER_LONG --
VMA_UFFD_MINOR_BIT is 41, with VM_UFFD_MINOR == VM_NONE on 32-bit so no VMA
actually carries the bit.
__VMA_UFFD_FLAGS feeds VMA_UFFD_MINOR_BIT to mk_vma_flags() unconditionally.
On 32-bit that becomes __set_bit(41, &one_long), a write one word past the
end of the single-word bitmap. The compiler folds the out-of-bounds store
with wraparound (1UL << (41 % 32) == bit 9) into the first word; bit 9 is
already in __VMA_UFFD_FLAGS so the mask happens to come out right today, but
it is an out-of-bounds write all the same, and any high-numbered bit whose
mod-BITS_PER_LONG position is otherwise unused would silently OR an extra
bit into the mask.
Rather than feed bit numbers that may not exist on the current build to
mk_vma_flags(), build the mask from whole per-mode masks that collapse to
EMPTY_VMA_FLAGS when their feature is unavailable. Add
mk_vma_flags_from_masks() for that, and define VMA_UFFD_MISSING / _WP /
_MINOR alongside the VM_UFFD_* flags, gating VMA_UFFD_MINOR on the same
config as VM_UFFD_MINOR (which implies 64BIT, where bit 41 fits). An
out-of-range bit is then never materialised, on any arch, and the in-range
fast path stays a compile-time constant.
Fixes: 9ea35a25d51b ("mm: introduce VMA flags bitmap type")
Cc: stable@vger.kernel.org
Reported-by: Sashiko AI review <sashiko-bot@kernel.org>
Suggested-by: Lorenzo Stoakes <ljs@kernel.org>
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
Assisted-by: Claude:claude-opus-4-8
---
include/linux/mm.h | 39 +++++++++++++++++++++++++++++++++++
include/linux/userfaultfd_k.h | 4 ++--
2 files changed, 41 insertions(+), 2 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 0f2612a70fb1..485df9c2dbdd 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -496,6 +496,21 @@ enum {
#else
#define VM_UFFD_MINOR VM_NONE
#endif
+
+/*
+ * vma_flags_t masks for the userfaultfd VMA flags. VMA_UFFD_MINOR is gated on
+ * the same config as VM_UFFD_MINOR -- which implies 64BIT, where the bit fits
+ * -- so an out-of-range bit is never fed to mk_vma_flags() on a build whose
+ * bitmap cannot hold it.
+ */
+#define VMA_UFFD_MISSING mk_vma_flags(VMA_UFFD_MISSING_BIT)
+#define VMA_UFFD_WP mk_vma_flags(VMA_UFFD_WP_BIT)
+#ifdef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR
+#define VMA_UFFD_MINOR mk_vma_flags(VMA_UFFD_MINOR_BIT)
+#else
+#define VMA_UFFD_MINOR EMPTY_VMA_FLAGS
+#endif
+
#ifdef CONFIG_64BIT
#define VM_ALLOW_ANY_UNCACHED INIT_VM_FLAG(ALLOW_ANY_UNCACHED)
#define VM_SEALED INIT_VM_FLAG(SEALED)
@@ -1238,6 +1253,30 @@ static __always_inline void vma_flags_set_mask(vma_flags_t *flags,
#define vma_flags_set(flags, ...) \
vma_flags_set_mask(flags, mk_vma_flags(__VA_ARGS__))
+static __always_inline vma_flags_t __mk_vma_flags_from_masks(size_t count,
+ const vma_flags_t *masks)
+{
+ vma_flags_t flags = EMPTY_VMA_FLAGS;
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ vma_flags_set_mask(&flags, masks[i]);
+ return flags;
+}
+
+/*
+ * Combine pre-computed vma_flags_t masks into one value, e.g.:
+ *
+ * vma_flags_t flags = mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_MINOR);
+ *
+ * Unlike mk_vma_flags(), which takes bit numbers, this takes whole masks --
+ * each of which may be EMPTY_VMA_FLAGS when its feature is unavailable -- so a
+ * bit that does not exist on the current build is never materialised.
+ */
+#define mk_vma_flags_from_masks(...) \
+ __mk_vma_flags_from_masks(COUNT_ARGS(__VA_ARGS__), \
+ (const vma_flags_t []){__VA_ARGS__})
+
/* Clear all of the to-clear flags in flags, non-atomically. */
static __always_inline void vma_flags_clear_mask(vma_flags_t *flags,
vma_flags_t to_clear)
diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
index 3ec8e1071673..68edac4dcd78 100644
--- a/include/linux/userfaultfd_k.h
+++ b/include/linux/userfaultfd_k.h
@@ -23,8 +23,8 @@
/* The set of all possible UFFD-related VM flags. */
#define __VM_UFFD_FLAGS (VM_UFFD_MISSING | VM_UFFD_WP | VM_UFFD_MINOR)
-#define __VMA_UFFD_FLAGS mk_vma_flags(VMA_UFFD_MISSING_BIT, VMA_UFFD_WP_BIT, \
- VMA_UFFD_MINOR_BIT)
+#define __VMA_UFFD_FLAGS mk_vma_flags_from_masks(VMA_UFFD_MISSING, VMA_UFFD_WP, \
+ VMA_UFFD_MINOR)
/*
* CAREFUL: Check include/uapi/asm-generic/fcntl.h when defining
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-05-29 17:24 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-29 17:23 [PATCH 0/6] userfaultfd/pagemap: pre-existing fixes Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 1/6] fs/proc/task_mmu: fix make_uffd_wp_huge_pte() prot-update race Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 2/6] fs/proc/task_mmu: use huge_page_size() in pagemap_scan_hugetlb_entry() Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 3/6] fs/proc/task_mmu: fix hugetlb self-deadlock in pagemap_scan_pte_hole() Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 4/6] mm/huge_memory: preserve pmd_swp_uffd_wp on device-private PMD downgrade Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 5/6] userfaultfd: gate must_wait writability check on pte_present() Kiryl Shutsemau (Meta)
2026-05-29 17:23 ` [PATCH 6/6] userfaultfd: build __VMA_UFFD_FLAGS from config-gated masks Kiryl Shutsemau (Meta)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox