* [PATCH 01/10] mm/vma: don't leak the commit charge on mmap failure
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 17:54 ` Pedro Falcato
2026-10-15 16:07 ` [PATCH 02/10] mm/hugetlb: fix use-after-free of a new anon folio David Hildenbrand (Arm)
` (8 subsequent siblings)
9 siblings, 1 reply; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), stable
From: Josef Bacik <josef@toxicpanda.com>
When an accountable mapping replaces existing mappings (MAP_FIXED),
__mmap_setup() only charges the difference between the size of the new
mapping and the VM_ACCOUNT pages it replaces. It then zeroes
vms->nr_accounted so that unmapping the old VMAs does not return their
charge, which now backs the new mapping.
If the operation fails after that point, the error path returns
map->charged, i.e. only the difference. By then vms_clean_up_area() has
zapped the page tables and closed the old VMAs, so
vms_abort_munmap_vmas() cannot reattach them. It leaves a gap and
finishes the unmap with vms_complete_munmap_vmas(), which uncharges
vms->nr_accounted == 0 pages. The old VMAs are gone, the new one was
never created, and the charge of the old ones is never returned, not
even when the process exits.
No privilege is needed to hit this, only a file whose mmap hook fails,
for example a socket (sock_no_mmap() returns -ENODEV):
p = mmap(NULL, len, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
mmap(p, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED,
socket_fd, 0);
Every iteration adds len to Committed_AS for good. With
vm.overcommit_memory=2 this lets any user make all further accountable
allocations in the system fail with -ENOMEM.
Once the new mapping has taken over the charge of the mappings it
replaces, it owns the charge for all of its pages. Record that in
map->charged, so that the error path returns all of it.
A failure in __mmap_setup() itself is not affected: it happens before
the charge is taken over and the old VMAs are reattached with their
charge intact.
Original fix generated by an LLM and sent in private by Josef.
Fixes: 4f87153e82c4 ("mm: change failure of MAP_FIXED to restoring the gap on failure")
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
[ david: call variable "uncharged" and make it const, adjust/simplify comments ]
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/vma.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/mm/vma.c b/mm/vma.c
index 6c35f0d775ab6..81e2c93a1cc52 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -2577,14 +2577,17 @@ static int __mmap_setup(struct mmap_state *map, struct vm_area_desc *desc,
/* Private writable mapping: check memory availability. */
if (accountable_mapping(map)) {
- map->charged = map->pglen;
- map->charged -= vms->nr_accounted;
- if (map->charged) {
- error = security_vm_enough_memory_mm(map->mm, map->charged);
+ const unsigned long uncharged = map->pglen - vms->nr_accounted;
+
+ /* Charge only what's not already charged by mappings we replace. */
+ if (uncharged) {
+ error = security_vm_enough_memory_mm(map->mm, uncharged);
if (error)
return error;
}
+ /* Our new mapping owns the entire charge now. */
+ map->charged = map->pglen;
vms->nr_accounted = 0;
vma_flags_set(&map->vma_flags, VMA_ACCOUNT_BIT);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 01/10] mm/vma: don't leak the commit charge on mmap failure
2026-10-15 16:07 ` [PATCH 01/10] mm/vma: don't leak the commit charge on mmap failure David Hildenbrand (Arm)
@ 2026-10-15 17:54 ` Pedro Falcato
2026-10-15 18:53 ` David Hildenbrand (Arm)
0 siblings, 1 reply; 17+ messages in thread
From: Pedro Falcato @ 2026-10-15 17:54 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft), Josef Bacik,
linux-mm, linux-kernel, stable
On Thu, Oct 15, 2026 at 06:07:07PM +0200, David Hildenbrand (Arm) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> When an accountable mapping replaces existing mappings (MAP_FIXED),
> __mmap_setup() only charges the difference between the size of the new
> mapping and the VM_ACCOUNT pages it replaces. It then zeroes
> vms->nr_accounted so that unmapping the old VMAs does not return their
> charge, which now backs the new mapping.
>
> If the operation fails after that point, the error path returns
> map->charged, i.e. only the difference. By then vms_clean_up_area() has
> zapped the page tables and closed the old VMAs, so
> vms_abort_munmap_vmas() cannot reattach them. It leaves a gap and
> finishes the unmap with vms_complete_munmap_vmas(), which uncharges
> vms->nr_accounted == 0 pages. The old VMAs are gone, the new one was
> never created, and the charge of the old ones is never returned, not
> even when the process exits.
>
> No privilege is needed to hit this, only a file whose mmap hook fails,
> for example a socket (sock_no_mmap() returns -ENODEV):
>
> p = mmap(NULL, len, PROT_READ | PROT_WRITE,
> MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> mmap(p, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED,
> socket_fd, 0);
>
> Every iteration adds len to Committed_AS for good. With
> vm.overcommit_memory=2 this lets any user make all further accountable
> allocations in the system fail with -ENOMEM.
I don't think is urgent nor does it have security implications. You can
already broadly screw up the system from any user when overcommit_memory=2.
This just goes around the single-process user_reserve_kbytes AFAICT.
Am I missing something?
Just looks like a good old bug to me.
>
> Once the new mapping has taken over the charge of the mappings it
> replaces, it owns the charge for all of its pages. Record that in
> map->charged, so that the error path returns all of it.
>
> A failure in __mmap_setup() itself is not affected: it happens before
> the charge is taken over and the old VMAs are reattached with their
> charge intact.
>
> Original fix generated by an LLM and sent in private by Josef.
>
> Fixes: 4f87153e82c4 ("mm: change failure of MAP_FIXED to restoring the gap on failure")
> Cc: stable@vger.kernel.org
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> [ david: call variable "uncharged" and make it const, adjust/simplify comments ]
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Pedro Falcato <pfalcato@suse.de>
--
Pedro
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 01/10] mm/vma: don't leak the commit charge on mmap failure
2026-10-15 17:54 ` Pedro Falcato
@ 2026-10-15 18:53 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 18:53 UTC (permalink / raw)
To: Pedro Falcato
Cc: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft), Josef Bacik,
linux-mm, linux-kernel, stable
On 10/15/26 19:54, Pedro Falcato wrote:
> On Thu, Oct 15, 2026 at 06:07:07PM +0200, David Hildenbrand (Arm) wrote:
>> From: Josef Bacik <josef@toxicpanda.com>
>>
>> When an accountable mapping replaces existing mappings (MAP_FIXED),
>> __mmap_setup() only charges the difference between the size of the new
>> mapping and the VM_ACCOUNT pages it replaces. It then zeroes
>> vms->nr_accounted so that unmapping the old VMAs does not return their
>> charge, which now backs the new mapping.
>>
>> If the operation fails after that point, the error path returns
>> map->charged, i.e. only the difference. By then vms_clean_up_area() has
>> zapped the page tables and closed the old VMAs, so
>> vms_abort_munmap_vmas() cannot reattach them. It leaves a gap and
>> finishes the unmap with vms_complete_munmap_vmas(), which uncharges
>> vms->nr_accounted == 0 pages. The old VMAs are gone, the new one was
>> never created, and the charge of the old ones is never returned, not
>> even when the process exits.
>>
>> No privilege is needed to hit this, only a file whose mmap hook fails,
>> for example a socket (sock_no_mmap() returns -ENODEV):
>>
>> p = mmap(NULL, len, PROT_READ | PROT_WRITE,
>> MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>> mmap(p, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED,
>> socket_fd, 0);
>>
>> Every iteration adds len to Committed_AS for good. With
>> vm.overcommit_memory=2 this lets any user make all further accountable
>> allocations in the system fail with -ENOMEM.
>
> I don't think is urgent nor does it have security implications. You can
> already broadly screw up the system from any user when overcommit_memory=2.
> This just goes around the single-process user_reserve_kbytes AFAICT.
> Am I missing something?
>
> Just looks like a good old bug to me.
Yes, most of the things here are just good old bugs with limited criticality (as
I expressed in the cover letter and the "urgent?" highlight in the subject).
Things like use-after-free or deadlocks are rather bad, but they are mostly
hugetlb specific, requiring actual allocated hugetlb pages in the pool. So there
is limited attack surface in practice.
All valid things we should be fixing, though.
I suspect we have a flood of MM fixes coming our way with differing criticality,
so this here is one attempt to figure out how well we can deal with the
mostly-trivial ones in a fairly controlled (e.g., proper human review) yet
efficient way.
Anything less trivial or shaky needs proper time to settle, as always.
--
Cheers,
David
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 02/10] mm/hugetlb: fix use-after-free of a new anon folio
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
2026-10-15 16:07 ` [PATCH 01/10] mm/vma: don't leak the commit charge on mmap failure David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 16:07 ` [PATCH 03/10] mm/hugetlb: fix use-after-free in hugetlb_mfill_atomic_pte() David Hildenbrand (Arm)
` (7 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), stable
From: Josef Bacik <josef@toxicpanda.com>
For a fault in a private mapping with nothing in the page cache,
hugetlb_no_page() allocates a folio, locks it and maps it. The folio has
one reference, which belongs to the page table entry from then on. After
dropping the page table lock the function still sets the migratable flag
of the folio and unlocks it.
Whoever zaps the entry in between frees the folio. The fault holds the
fault mutex and the lock of the vma. MADV_DONTNEED and the first unmap of
a hole punch, hugetlb_vmdelete_list(), do not take the fault mutex, and a
private vma only has a lock if it owns a reservation map: not with
MAP_NORESERVE, and not in the child after fork().
Then the flag is set in, and folio_unlock() is called on, a folio that is
free or has a new owner. A surplus page goes back to the page allocator
while it is locked:
BUG: Bad page state in process init pfn:108e00
flags: 0x200000000000059(locked|uptodate|dirty|head|node=0|zone=2)
page dumped because: PAGE_FLAGS_CHECK_AT_FREE flag(s) set
A page of the pool can be given to the next user, of any file, who locks
it and loses the lock to the late folio_unlock():
kernel BUG at mm/filemap.c:1532!
hugetlb_no_page+0x111b/0x23e0
hugetlb_fault+0x3a9/0x14a0
An unprivileged process is enough: threads that write to a MAP_PRIVATE |
MAP_NORESERVE hugetlb mapping, threads that call MADV_DONTNEED on it, and
one that takes the freed pages for another file.
Until commit 3a47c54f09c4 ("hugetlbfs: revert use i_mmap_rwsem for more
pmd sharing synchronization") the fault held i_mmap_rwsem for reading,
which everybody who zaps takes for writing. The lock that commit
bf4916922c60 ("hugetlbfs: extend hugetlb_vma_lock to private VMAs") gave
to private vmas closed the window again, but only for those with a
reservation map.
Be done with the folio before the page table lock is dropped, as
hugetlb_wp() is with its copy. The function already unlocks page cache
folios under that lock.
Original fix generated by an LLM and sent in private by Josef.
Fixes: 3a47c54f09c4 ("hugetlbfs: revert use i_mmap_rwsem for more pmd sharing synchronization")
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
[ david: simplify+move comment ]
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/hugetlb.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index cea25773a6c95..ce9e87228ec62 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -5949,8 +5949,6 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
ret = hugetlb_wp(vmf);
}
- spin_unlock(vmf->ptl);
-
/*
* Only set hugetlb_migratable in newly allocated pages. Existing pages
* found in the pagecache may not have hugetlb_migratable if they have
@@ -5961,6 +5959,12 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
if (folio_locked)
folio_unlock(folio);
+
+ /*
+ * Hold the PTL until we're done with the folio: the page table owns the
+ * folio reference now.
+ */
+ spin_unlock(vmf->ptl);
out:
hugetlb_vma_unlock_read(vma);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 03/10] mm/hugetlb: fix use-after-free in hugetlb_mfill_atomic_pte()
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
2026-10-15 16:07 ` [PATCH 01/10] mm/vma: don't leak the commit charge on mmap failure David Hildenbrand (Arm)
2026-10-15 16:07 ` [PATCH 02/10] mm/hugetlb: fix use-after-free of a new anon folio David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 16:07 ` [PATCH 04/10] mm/hugetlb: unmap the page where the other VMA maps it David Hildenbrand (Arm)
` (6 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), stable
Let's fix another instance where we use a folio after dropping the PTL,
where someone else could e.g., MADV_DONTNEED it once we drop the PTL,
making us trigger a use-after-free on the folio.
Fixes: cb6acd01e2e4 ("hugetlbfs: fix races and page leaks during migration")
Fixes: 1c9e8def43a3 ("userfaultfd: hugetlbfs: add UFFDIO_COPY support for shared mappings")
Cc: stable@vger.kernel.org
Reported-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/hugetlb.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index ce9e87228ec62..86928d76ef12d 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -6478,11 +6478,16 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
/* No need to invalidate - it was non-present before */
update_mmu_cache(dst_vma, dst_addr, dst_pte);
- spin_unlock(ptl);
if (!is_continue)
folio_set_hugetlb_migratable(folio);
if (vm_shared || is_continue)
folio_unlock(folio);
+
+ /*
+ * Hold the PTL until we're done with the folio: the page table owns the
+ * folio reference now.
+ */
+ spin_unlock(ptl);
ret = 0;
out:
return ret;
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 04/10] mm/hugetlb: unmap the page where the other VMA maps it
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
` (2 preceding siblings ...)
2026-10-15 16:07 ` [PATCH 03/10] mm/hugetlb: fix use-after-free in hugetlb_mfill_atomic_pte() David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 16:07 ` [PATCH 05/10] mm/hugetlb: don't clear the resv_map of a VMA whose lock is held David Hildenbrand (Arm)
` (5 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), Chris Mason,
stable
From: Josef Bacik <josef@toxicpanda.com>
If the owner of a MAP_PRIVATE hugetlb mapping fails to allocate a page
for CoW, unmap_ref_private() unmaps the old page from all other private
VMAs that have no reservation of their own. It finds them by the offset
in the file, but then zaps each of them at the address of the faulting
VMA. Nothing says that another VMA maps the offset at that address: a
child can have moved its copy of the VMA with mremap(), and a second
mapping of the file is wherever mmap() has put it.
So __unmap_hugepage_range() walks the page tables of the other mm at an
address outside of iter_vma, where i_mmap_rwsem of this file protects
nothing and where anything can be mapped, and the page stays mapped where
it really is.
All of the following was seen on v7.3-rc6+ (g6c377d19d4a5) in KVM guests,
as a user without any privilege. It takes a pool of 2 MiB pages with no
free page that is not reserved, so that the allocation fails: the user
can take or reserve what is free itself.
- The child moves its VMA away. The page is never unmapped, the
mapcount stays 2, and the owner goes round retry_avoidcopy in
hugetlb_wp() until the child is gone or a page becomes free. SIGKILL
to the owner does not end it. 5 of 5 runs.
- The owner's mm has a second private mapping of the offset, made with
MAP_NORESERVE. The owner zaps its own entry and reads zeroes where
its data was. 5 of 5 runs.
- The child moves its VMA away and maps a 1 GiB hugetlb page at the old
address, so this one takes a pool with a 1 GiB page as well.
huge_pte_offset() for the 2 MiB size steps through the PUD
leaf, so the "PMD table" is the 1 GiB page itself and its "page table
lock" is folio->private of that page, which holds 2. Kernels with
lock debugging oops in __unmap_hugepage_range(), 7 of 7 runs. Others
spin on that word for ever with preemption disabled, 5 of 5 runs:
watchdog: BUG: soft lockup - CPU#0 stuck for 26s! [u_det3:130]
RIP: 0010:queued_spin_lock_slowpath+0x8a/0x290
RAX: 0000000000000002 RBX: ffffea0001000028
Call Trace:
__unmap_hugepage_range+0x154/0x6d0
unmap_hugepage_range+0xc1/0x140
hugetlb_wp+0x55c/0x700
hugetlb_fault+0x5f3/0x8f0
RCU stalls follow, and killing all tasks of the user changes nothing.
Four such owners, whose children share a single 1 GiB page, took down
the whole guest with its four CPUs, 2 of 2 runs: one reported soft
lockups on all four, the other gave no sign of life any more.
i_mmap_rwsem stays write locked. With a MAP_PRIVATE mapping of a
hugetlbfs file that belongs to another user, and that the attacker
can only read, the next mmap() of that file by its owner sleeps
uninterruptibly in __mmap_region(), 4 of 4 runs.
Compute the address from the offset for each VMA. The tree walk returns
only VMAs that cover pgoff, and vm_start, vm_end and vm_pgoff of a hugetlb
VMA are aligned to the huge page size, as pgoff is, so the whole range
lies inside iter_vma. Its page tables can not go away there, since the
VMA is unlinked from the file under i_mmap_rwsem first.
With the right address the page is unmapped from all VMAs but the
owner's, so the retry in hugetlb_wp() finds a mapcount of 1 and reuses the
page.
A child that has moved its copy of the VMA now loses the page, and gets
SIGBUS when it faults on a missing page of that VMA. That is what the
comment above the function has always promised to those who do not own
the reservation, and what already happens to a child that has left the
VMA in place.
The address has been wrong from the start, and the second case needs
only mmap() and fork(). mremap() of hugetlb VMAs, which the other cases
use, came with commit 550a7d60bd5e ("mm, hugepages: add mremap() support
for hugepage backed vma") in v5.16.
Original fix generated by an LLM and sent in private by Josef.
Fixes: 04f2cbe35699 ("hugetlb: guarantee that COW faults for a process that called mmap(MAP_PRIVATE) on hugetlbfs will succeed")
Reported-by: Chris Mason <mason@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
[ david: switch to vma_filebacked_address, add VM_WARN_ON_ONCE, refine comments]
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/hugetlb.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 86928d76ef12d..86fafd0782987 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -5440,6 +5440,8 @@ static void unmap_ref_private(struct mm_struct *mm, struct vm_area_struct *vma,
struct address_space *mapping;
pgoff_t pgoff;
+ VM_WARN_ON_ONCE(!folio_test_anon(folio));
+
/*
* vm_pgoff is in PAGE_SIZE units, hence the different calculation
* from page cache lookup which is in HPAGE_SIZE units.
@@ -5474,10 +5476,17 @@ static void unmap_ref_private(struct mm_struct *mm, struct vm_area_struct *vma,
* could insert a zeroed page instead of the data existing
* from the time of fork. This would look like data corruption
*/
- if (!is_vma_resv_set(iter_vma, HPAGE_RESV_OWNER))
- unmap_hugepage_range(iter_vma, address,
- address + huge_page_size(h),
- folio, 0);
+ if (is_vma_resv_set(iter_vma, HPAGE_RESV_OWNER))
+ continue;
+
+ /*
+ * The address in other VMAs can differ. Even though our old
+ * folio is anon, we always operate on the underlying hugetlb
+ * file.
+ */
+ address = vma_filebacked_address(iter_vma, pgoff, 1);
+ unmap_hugepage_range(iter_vma, address,
+ address + huge_page_size(h), folio, 0);
}
i_mmap_unlock_write(mapping);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 05/10] mm/hugetlb: don't clear the resv_map of a VMA whose lock is held
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
` (3 preceding siblings ...)
2026-10-15 16:07 ` [PATCH 04/10] mm/hugetlb: unmap the page where the other VMA maps it David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 16:07 ` [PATCH 06/10] mm/hugetlb: fix endless write fault on a leftover uffd bit David Hildenbrand (Arm)
` (4 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), Chris Mason,
stable
From: Josef Bacik <josef@toxicpanda.com>
Since commit bf4916922c60 ("hugetlbfs: extend hugetlb_vma_lock to private
VMAs") the lock of a private hugetlb VMA that owns a reservation is the
rw_sema of its resv_map. The hugetlb_vma_*lock*() helpers find it through
vma->vm_private_data, anew for every lock and every unlock.
At the end of mremap() the old VMA has to let go of the resv_map, which
now belongs to the new VMA: clear_vma_resv_huge_pages() stores NULL into
vm_private_data. mremap() holds mmap_lock for writing and has write
locked the VMA, which keeps away everybody who comes through the mm. It
does not keep away those who find the VMA through the file. For hole
punch and truncation hugetlb_vmdelete_list() does
hugetlb_vma_trylock_write(vma); locks resv_map->rw_sema
unmap_hugepage_range(vma, ...);
vm_private_data = NULL
hugetlb_vma_unlock_write(vma); no resv_map: does nothing
and the rw_sema stays write locked for ever:
WARNING: lock held when returning to user space!
mremap_punch/116 is leaving the kernel with locks still held!
locks held by mremap_punch/116: 1, last CPU#1:
#0: (&resv_map->rw_sema){+.+.}-{4:4}, at:
hugetlb_vmdelete_list+0x46/0x160
The next fault in the new VMA sleeps in hugetlb_vma_lock_read(),
uninterruptibly, and it holds its entry of hugetlb_fault_mutex_table[].
That table is global, so a fault of any other process on a page that
hashes to the same entry sleeps uninterruptibly as well.
All those who take the lock of a private VMA without taking the mmap/vma
lock are affected: rmap walks. So hold i_mmap_rwsem around the clearing
of the resv_map.
No change of behaviour, other than that mremap() of a hugetlb VMA takes
i_mmap_rwsem once more.
Original fix generated by an LLM and sent in private by Josef.
Fixes: bf4916922c60 ("hugetlbfs: extend hugetlb_vma_lock to private VMAs")
Reported-by: Chris Mason <mason@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
[ david: avoid local variable, move and refine comment, description cleanups+simplifications ]
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/hugetlb.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 86fafd0782987..f8ad2efd472cb 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -7467,6 +7467,15 @@ void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
*/
void fixup_hugetlb_reservations(struct vm_area_struct *vma)
{
- if (is_vm_hugetlb_page(vma))
- clear_vma_resv_huge_pages(vma);
+ if (!is_vm_hugetlb_page(vma))
+ return;
+
+ /*
+ * Rmap walks might still find this VMA and take the lock
+ * inside the reservation map for __vma_private_lock(). Don't clear
+ * the reservation map while that is possible.
+ */
+ i_mmap_lock_write(vma->vm_file->f_mapping);
+ clear_vma_resv_huge_pages(vma);
+ i_mmap_unlock_write(vma->vm_file->f_mapping);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 06/10] mm/hugetlb: fix endless write fault on a leftover uffd bit
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
` (4 preceding siblings ...)
2026-10-15 16:07 ` [PATCH 05/10] mm/hugetlb: don't clear the resv_map of a VMA whose lock is held David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 20:10 ` Lorenzo Stoakes (ARM)
2026-10-15 16:07 ` [PATCH 07/10] mm/hugetlb: tolerate uffd bits set without uffd for PROT_NONE PTEs David Hildenbrand (Arm)
` (3 subsequent siblings)
9 siblings, 1 reply; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), Chris Mason,
stable
From: Josef Bacik <josef@toxicpanda.com>
hugetlb_fault() resolves the uffd bit on a write fault only if the VMA is
registered for userfaultfd write protection, and hugetlb_wp() bails out
for any entry with the uffd bit set assuming hugetlb_fault() has already
resolved it.
When a read-only hugetlb entry with the uffd bit set is mapped in a VMA
that is not registered for uffd-wp, a write fault to it never makes
progress. If the access comes from the kernel, e.g. read(2) from
/dev/zero into such page, the task spins in the page fault handler,
ignores SIGKILL and keeps holding the locks taken by the system call.
Such entries should not exist, but an unprivileged user can create them,
for example with MADV_DONTNEED of a page protected with UFFDIO_RWPROTECT
or with UFFDIO_COPY in MODE_WP racing with UFFDIO_UNREGISTER.
Clear the uffd bit when the VMA is not registered for uffd-wp and let the
fault proceed to hugetlb_wp(), like do_wp_page() does for regular pages.
Note that a FOLL_FORCE write to a page protected with UFFDIO_RWPROTECT in
a private VMA that was made PROT_NONE now drops the RWP protection of
that page.
Original fix generated by an LLM and sent in private by Josef.
Fixes: 60d5b473d61b ("mm/hugetlb: fix uffd wr-protection for CoW optimization path")
Reported-by: Chris Mason <mason@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
[rppt: massage commit message]
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
[ david: convert comment into a TODO ]
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/hugetlb.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index f8ad2efd472cb..c3823f1958568 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -6184,15 +6184,19 @@ vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
goto out_ptl;
/* Handle userfault-wp first, before trying to lock more pages */
- if (userfaultfd_wp(vma) && huge_pte_uffd(huge_ptep_get(mm, vmf.address, vmf.pte)) &&
+ if (huge_pte_uffd(huge_ptep_get(mm, vmf.address, vmf.pte)) &&
(flags & FAULT_FLAG_WRITE) && !huge_pte_write(vmf.orig_pte)) {
- if (!userfaultfd_wp_async(vma)) {
+ if (userfaultfd_wp(vma) && !userfaultfd_wp_async(vma)) {
spin_unlock(vmf.ptl);
hugetlb_vma_unlock_read(vma);
mutex_unlock(&hugetlb_fault_mutex_table[hash]);
return handle_userfault(&vmf, VM_UFFD_WP);
}
+ /*
+ * TODO: fix all cases where uffd PTE bits remain in VMAs that
+ * are no longer registered with uffd.
+ */
vmf.orig_pte = huge_pte_clear_uffd(vmf.orig_pte);
set_huge_pte_at(mm, vmf.address, vmf.pte, vmf.orig_pte,
huge_page_size(hstate_vma(vma)));
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 06/10] mm/hugetlb: fix endless write fault on a leftover uffd bit
2026-10-15 16:07 ` [PATCH 06/10] mm/hugetlb: fix endless write fault on a leftover uffd bit David Hildenbrand (Arm)
@ 2026-10-15 20:10 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 17+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 20:10 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Andrew Morton, Liam R. Howlett, Vlastimil Babka, Jann Horn,
Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft), Josef Bacik,
linux-mm, linux-kernel, Chris Mason, stable
On Thu, Oct 15, 2026 at 06:07:12PM +0200, David Hildenbrand (Arm) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> hugetlb_fault() resolves the uffd bit on a write fault only if the VMA is
> registered for userfaultfd write protection, and hugetlb_wp() bails out
> for any entry with the uffd bit set assuming hugetlb_fault() has already
> resolved it.
>
> When a read-only hugetlb entry with the uffd bit set is mapped in a VMA
> that is not registered for uffd-wp, a write fault to it never makes
> progress. If the access comes from the kernel, e.g. read(2) from
> /dev/zero into such page, the task spins in the page fault handler,
> ignores SIGKILL and keeps holding the locks taken by the system call.
>
> Such entries should not exist, but an unprivileged user can create them,
> for example with MADV_DONTNEED of a page protected with UFFDIO_RWPROTECT
> or with UFFDIO_COPY in MODE_WP racing with UFFDIO_UNREGISTER.
Ugh yuck. Uffd is another gift that keeps giving...
>
> Clear the uffd bit when the VMA is not registered for uffd-wp and let the
> fault proceed to hugetlb_wp(), like do_wp_page() does for regular pages.
>
> Note that a FOLL_FORCE write to a page protected with UFFDIO_RWPROTECT in
> a private VMA that was made PROT_NONE now drops the RWP protection of
> that page.
>
> Original fix generated by an LLM and sent in private by Josef.
>
> Fixes: 60d5b473d61b ("mm/hugetlb: fix uffd wr-protection for CoW optimization path")
> Reported-by: Chris Mason <mason@kernel.org>
> Cc: stable@vger.kernel.org
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> [rppt: massage commit message]
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> [ david: convert comment into a TODO ]
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Looks sensible to me so:
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> mm/hugetlb.c | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index f8ad2efd472cb..c3823f1958568 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -6184,15 +6184,19 @@ vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
> goto out_ptl;
>
> /* Handle userfault-wp first, before trying to lock more pages */
> - if (userfaultfd_wp(vma) && huge_pte_uffd(huge_ptep_get(mm, vmf.address, vmf.pte)) &&
> + if (huge_pte_uffd(huge_ptep_get(mm, vmf.address, vmf.pte)) &&
> (flags & FAULT_FLAG_WRITE) && !huge_pte_write(vmf.orig_pte)) {
> - if (!userfaultfd_wp_async(vma)) {
> + if (userfaultfd_wp(vma) && !userfaultfd_wp_async(vma)) {
> spin_unlock(vmf.ptl);
> hugetlb_vma_unlock_read(vma);
> mutex_unlock(&hugetlb_fault_mutex_table[hash]);
> return handle_userfault(&vmf, VM_UFFD_WP);
> }
>
> + /*
> + * TODO: fix all cases where uffd PTE bits remain in VMAs that
> + * are no longer registered with uffd.
> + */
> vmf.orig_pte = huge_pte_clear_uffd(vmf.orig_pte);
> set_huge_pte_at(mm, vmf.address, vmf.pte, vmf.orig_pte,
> huge_page_size(hstate_vma(vma)));
>
> --
> 2.43.0
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 07/10] mm/hugetlb: tolerate uffd bits set without uffd for PROT_NONE PTEs
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
` (5 preceding siblings ...)
2026-10-15 16:07 ` [PATCH 06/10] mm/hugetlb: fix endless write fault on a leftover uffd bit David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 20:13 ` Lorenzo Stoakes (ARM)
2026-10-15 16:07 ` [PATCH 08/10] mm/hugetlb: tolerate UFFD_WP markers outside uffd-wp VMAs David Hildenbrand (Arm)
` (2 subsequent siblings)
9 siblings, 1 reply; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), stable
The whole uffd handling when a VMA is unregistered from UFFD is shaky:
there are still some scenarios where we might have stale uffd PTE bits
in VMAs no longer registered for UFFD, which confuses other code.
Let's just tolerate that for now and simply clear the PTE bit. Add a
TODO to really clean up any uffd leftovers when unregistering a VMA from
userfaultfd.
The handling is now more similar to what we have in handle_pte_fault().
Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
Cc: stable@vger.kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/hugetlb.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index c3823f1958568..ba6860b9b7f4c 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -6127,16 +6127,20 @@ vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
* NUMA hinting), so let normal hugetlb fault handling proceed.
*/
if (pte_protnone(vmf.orig_pte) && vma_is_accessible(vma) &&
- userfaultfd_rwp(vma) && huge_pte_uffd(vmf.orig_pte)) {
+ huge_pte_uffd(vmf.orig_pte)) {
spinlock_t *ptl;
pte_t pte;
/* Sync: drop hugetlb locks before blocking in handle_userfault() */
- if (!userfaultfd_rwp_async(vma))
+ if (userfaultfd_rwp(vma) && !userfaultfd_rwp_async(vma))
return hugetlb_handle_userfault(&vmf, mapping, VM_UFFD_RWP);
ptl = huge_pte_lock(h, mm, vmf.pte);
pte = huge_ptep_get(mm, vmf.address, vmf.pte);
+ /*
+ * TODO: fix all cases where uffd PTE bits remain in VMAs that
+ * are no longer registered with uffd.
+ */
if (pte_protnone(pte) && huge_pte_uffd(pte)) {
unsigned int shift = huge_page_shift(h);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 07/10] mm/hugetlb: tolerate uffd bits set without uffd for PROT_NONE PTEs
2026-10-15 16:07 ` [PATCH 07/10] mm/hugetlb: tolerate uffd bits set without uffd for PROT_NONE PTEs David Hildenbrand (Arm)
@ 2026-10-15 20:13 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 17+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 20:13 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Andrew Morton, Liam R. Howlett, Vlastimil Babka, Jann Horn,
Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft), Josef Bacik,
linux-mm, linux-kernel, stable
On Thu, Oct 15, 2026 at 06:07:13PM +0200, David Hildenbrand (Arm) wrote:
> The whole uffd handling when a VMA is unregistered from UFFD is shaky:
> there are still some scenarios where we might have stale uffd PTE bits
> in VMAs no longer registered for UFFD, which confuses other code.
>
> Let's just tolerate that for now and simply clear the PTE bit. Add a
> TODO to really clean up any uffd leftovers when unregistering a VMA from
> userfaultfd.
>
> The handling is now more similar to what we have in handle_pte_fault().
>
> Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
> Cc: stable@vger.kernel.org
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Ugh what a mess. It seems like bad code makes any edits going forward
problematic because of the labyrinth of bailing wire and chewing gum left
behind.
Anyway seems sensible to me so:
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> mm/hugetlb.c | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index c3823f1958568..ba6860b9b7f4c 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -6127,16 +6127,20 @@ vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
> * NUMA hinting), so let normal hugetlb fault handling proceed.
> */
> if (pte_protnone(vmf.orig_pte) && vma_is_accessible(vma) &&
> - userfaultfd_rwp(vma) && huge_pte_uffd(vmf.orig_pte)) {
> + huge_pte_uffd(vmf.orig_pte)) {
> spinlock_t *ptl;
> pte_t pte;
>
> /* Sync: drop hugetlb locks before blocking in handle_userfault() */
> - if (!userfaultfd_rwp_async(vma))
> + if (userfaultfd_rwp(vma) && !userfaultfd_rwp_async(vma))
> return hugetlb_handle_userfault(&vmf, mapping, VM_UFFD_RWP);
>
> ptl = huge_pte_lock(h, mm, vmf.pte);
> pte = huge_ptep_get(mm, vmf.address, vmf.pte);
> + /*
> + * TODO: fix all cases where uffd PTE bits remain in VMAs that
> + * are no longer registered with uffd.
> + */
> if (pte_protnone(pte) && huge_pte_uffd(pte)) {
> unsigned int shift = huge_page_shift(h);
>
>
> --
> 2.43.0
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 08/10] mm/hugetlb: tolerate UFFD_WP markers outside uffd-wp VMAs
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
` (6 preceding siblings ...)
2026-10-15 16:07 ` [PATCH 07/10] mm/hugetlb: tolerate uffd bits set without uffd for PROT_NONE PTEs David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 19:47 ` Lorenzo Stoakes (ARM)
2026-10-15 16:07 ` [PATCH 09/10] mm/hugetlb: don't leave a uffd-wp marker when zapping an RWP entry David Hildenbrand (Arm)
2026-10-15 16:07 ` [PATCH 10/10] mm/mremap: fix calling fixup_hugetlb_reservations() on the wrong VMA David Hildenbrand (Arm)
9 siblings, 1 reply; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), stable
UFFD PTE bit handling is a bit like swiss cheese, in particular with
recently added RWP mode. Having uffd-wp markers outside uffd-wp VMAs
already confused this code in the past and resulted in uffd-pte bits
confusing other code later.
Let's play safe and just ignore these markers in hugetlb_no_page(), not
silently propagating the uffd-wp PTE bit.
Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
Cc: stable@vger.kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/hugetlb.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index ba6860b9b7f4c..1548a6156206b 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -5939,8 +5939,11 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
/*
* If this pte was previously wr-protected, keep it wr-protected even
* if populated.
+ *
+ * TODO: fix all cases where uffd-wp markers remain in VMAs that are no
+ * longer registered with uffd.
*/
- if (unlikely(pte_is_uffd_wp_marker(vmf->orig_pte)))
+ if (unlikely(userfaultfd_wp(vma) && pte_is_uffd_wp_marker(vmf->orig_pte)))
new_pte = huge_pte_mkuffd(new_pte);
set_huge_pte_at(mm, vmf->address, vmf->pte, new_pte, huge_page_size(h));
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 08/10] mm/hugetlb: tolerate UFFD_WP markers outside uffd-wp VMAs
2026-10-15 16:07 ` [PATCH 08/10] mm/hugetlb: tolerate UFFD_WP markers outside uffd-wp VMAs David Hildenbrand (Arm)
@ 2026-10-15 19:47 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 17+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 19:47 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Andrew Morton, Liam R. Howlett, Vlastimil Babka, Jann Horn,
Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft), Josef Bacik,
linux-mm, linux-kernel, stable
On Thu, Oct 15, 2026 at 06:07:14PM +0200, David Hildenbrand (Arm) wrote:
> UFFD PTE bit handling is a bit like swiss cheese, in particular with
> recently added RWP mode. Having uffd-wp markers outside uffd-wp VMAs
> already confused this code in the past and resulted in uffd-pte bits
> confusing other code later.
>
> Let's play safe and just ignore these markers in hugetlb_no_page(), not
> silently propagating the uffd-wp PTE bit.
>
> Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
> Cc: stable@vger.kernel.org
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Seems sensible so:
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> mm/hugetlb.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index ba6860b9b7f4c..1548a6156206b 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -5939,8 +5939,11 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
> /*
> * If this pte was previously wr-protected, keep it wr-protected even
> * if populated.
> + *
> + * TODO: fix all cases where uffd-wp markers remain in VMAs that are no
> + * longer registered with uffd.
> */
> - if (unlikely(pte_is_uffd_wp_marker(vmf->orig_pte)))
> + if (unlikely(userfaultfd_wp(vma) && pte_is_uffd_wp_marker(vmf->orig_pte)))
> new_pte = huge_pte_mkuffd(new_pte);
> set_huge_pte_at(mm, vmf->address, vmf->pte, new_pte, huge_page_size(h));
>
>
> --
> 2.43.0
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 09/10] mm/hugetlb: don't leave a uffd-wp marker when zapping an RWP entry
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
` (7 preceding siblings ...)
2026-10-15 16:07 ` [PATCH 08/10] mm/hugetlb: tolerate UFFD_WP markers outside uffd-wp VMAs David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
2026-10-15 19:45 ` Lorenzo Stoakes (ARM)
2026-10-15 16:07 ` [PATCH 10/10] mm/mremap: fix calling fixup_hugetlb_reservations() on the wrong VMA David Hildenbrand (Arm)
9 siblings, 1 reply; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), stable,
Chris Mason
From: Josef Bacik <josef@toxicpanda.com>
An entry that is protected with UFFDIO_RWPROTECT is PAGE_NONE with the
uffd bit set, the bit that userfaultfd write protection uses too. When
__unmap_hugepage_range() zaps an entry with that bit it leaves a
PTE_MARKER_UFFD_WP behind, whatever the VMA is registered for.
In a VMA registered with UFFDIO_REGISTER_MODE_RWP that marker means
nothing. The next fault has hugetlb_no_page() turn it into a present,
read-only entry with the uffd bit. That is not an RWP entry, since it is
not PAGE_NONE, and the VMA has no VM_UFFD_WP (the two modes can not be
registered together). An earlier fix solved some side effects, now just
stop the zap from wrongly installing a marker.
Leave a marker only if the VMA is registered for write protection, both
for present and for non-present entries. The zap of ordinary page
tables has the same test, in cond_install_uffd_wp_ptes(), and
hugetlb_change_protection() says that "uffd_rwp does not install markers".
After a zap the range of an RWP VMA is simply empty, as it is for
anonymous memory and shmem. Nothing changes for VMAs with VM_UFFD_WP. In
a VMA that is not registered at all a leftover uffd bit or marker is now
dropped by the zap and no longer kept alive.
Original fix generated by an LLM and sent in private by Josef.
Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
Cc: stable@vger.kernel.org
Reported-by: Chris Mason <mason@kernel.org>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
[ david: a bit of description overhaul+simplification ]
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/hugetlb.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 1548a6156206b..fb51f7d02cebf 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -5267,7 +5267,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
* drop the uffd-wp bit in this zap, then replace the
* pte with a marker.
*/
- if (pte_swp_uffd_any(pte) &&
+ if (userfaultfd_wp(vma) && pte_swp_uffd_any(pte) &&
!(zap_flags & ZAP_FLAG_DROP_MARKER))
set_huge_pte_at(mm, address, ptep,
make_pte_marker(PTE_MARKER_UFFD_WP),
@@ -5303,7 +5303,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
if (huge_pte_dirty(pte))
folio_mark_dirty(folio);
/* Leave a uffd-wp pte marker if needed */
- if (huge_pte_uffd(pte) &&
+ if (userfaultfd_wp(vma) && huge_pte_uffd(pte) &&
!(zap_flags & ZAP_FLAG_DROP_MARKER))
set_huge_pte_at(mm, address, ptep,
make_pte_marker(PTE_MARKER_UFFD_WP),
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 09/10] mm/hugetlb: don't leave a uffd-wp marker when zapping an RWP entry
2026-10-15 16:07 ` [PATCH 09/10] mm/hugetlb: don't leave a uffd-wp marker when zapping an RWP entry David Hildenbrand (Arm)
@ 2026-10-15 19:45 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 17+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 19:45 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Andrew Morton, Liam R. Howlett, Vlastimil Babka, Jann Horn,
Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft), Josef Bacik,
linux-mm, linux-kernel, stable, Chris Mason
On Thu, Oct 15, 2026 at 06:07:15PM +0200, David Hildenbrand (Arm) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
>
> An entry that is protected with UFFDIO_RWPROTECT is PAGE_NONE with the
> uffd bit set, the bit that userfaultfd write protection uses too. When
> __unmap_hugepage_range() zaps an entry with that bit it leaves a
> PTE_MARKER_UFFD_WP behind, whatever the VMA is registered for.
>
> In a VMA registered with UFFDIO_REGISTER_MODE_RWP that marker means
> nothing. The next fault has hugetlb_no_page() turn it into a present,
> read-only entry with the uffd bit. That is not an RWP entry, since it is
> not PAGE_NONE, and the VMA has no VM_UFFD_WP (the two modes can not be
> registered together). An earlier fix solved some side effects, now just
> stop the zap from wrongly installing a marker.
>
> Leave a marker only if the VMA is registered for write protection, both
> for present and for non-present entries. The zap of ordinary page
> tables has the same test, in cond_install_uffd_wp_ptes(), and
> hugetlb_change_protection() says that "uffd_rwp does not install markers".
>
> After a zap the range of an RWP VMA is simply empty, as it is for
> anonymous memory and shmem. Nothing changes for VMAs with VM_UFFD_WP. In
> a VMA that is not registered at all a leftover uffd bit or marker is now
> dropped by the zap and no longer kept alive.
>
> Original fix generated by an LLM and sent in private by Josef.
>
> Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
> Cc: stable@vger.kernel.org
> Reported-by: Chris Mason <mason@kernel.org>
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> [ david: a bit of description overhaul+simplification ]
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Seems sensible to me so:
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> mm/hugetlb.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 1548a6156206b..fb51f7d02cebf 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -5267,7 +5267,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
> * drop the uffd-wp bit in this zap, then replace the
> * pte with a marker.
> */
> - if (pte_swp_uffd_any(pte) &&
> + if (userfaultfd_wp(vma) && pte_swp_uffd_any(pte) &&
> !(zap_flags & ZAP_FLAG_DROP_MARKER))
> set_huge_pte_at(mm, address, ptep,
> make_pte_marker(PTE_MARKER_UFFD_WP),
> @@ -5303,7 +5303,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
> if (huge_pte_dirty(pte))
> folio_mark_dirty(folio);
> /* Leave a uffd-wp pte marker if needed */
> - if (huge_pte_uffd(pte) &&
> + if (userfaultfd_wp(vma) && huge_pte_uffd(pte) &&
> !(zap_flags & ZAP_FLAG_DROP_MARKER))
> set_huge_pte_at(mm, address, ptep,
> make_pte_marker(PTE_MARKER_UFFD_WP),
>
> --
> 2.43.0
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 10/10] mm/mremap: fix calling fixup_hugetlb_reservations() on the wrong VMA
2026-10-15 16:07 [PATCH 00/10] mm: various (urgent?) fixes David Hildenbrand (Arm)
` (8 preceding siblings ...)
2026-10-15 16:07 ` [PATCH 09/10] mm/hugetlb: don't leave a uffd-wp marker when zapping an RWP entry David Hildenbrand (Arm)
@ 2026-10-15 16:07 ` David Hildenbrand (Arm)
9 siblings, 0 replies; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-15 16:07 UTC (permalink / raw)
To: Andrew Morton, Liam R. Howlett, Lorenzo Stoakes, Vlastimil Babka,
Jann Horn, Pedro Falcato, Muchun Song, Oscar Salvador, Miaohe Lin,
Naoya Horiguchi, Adam Litke, Mel Gorman, Rik van Riel, Peter Xu,
Kiryl Shutsemau (Meta), Mike Rapoport (Microsoft),
Liam R. Howlett, Josef Bacik
Cc: linux-mm, linux-kernel, David Hildenbrand (Arm), stable
In case we fail to move the page tables, for example, because we fail
to allocate a page table, we actually keep the old VMA.
Calling fixup_hugetlb_reservations() on the old VMA is wrong. Fix it
by just calling it on the VMA that will be removed.
The user-visible impact is that the VMA loses its reservations, meaning
that the existing reservations get stranded (leaked) and new hugetlb folios
that are getting allocated in the VMA cannot make use of the
reservations, failing these allocations.
Fixes: b714ccb02a76 ("mm/mremap: complete refactor of move_vma()")
Cc: stable@vger.kernel.org
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/mremap.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/mremap.c b/mm/mremap.c
index 444f37d7cd28e..ace0c6a343b1b 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -1345,7 +1345,7 @@ static int copy_vma_and_data(struct vma_remap_struct *vrm,
mremap_userfaultfd_prep(new_vma, vrm->uf);
}
- fixup_hugetlb_reservations(vma);
+ fixup_hugetlb_reservations(vrm->vma);
*new_vma_ptr = new_vma;
return err;
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread