* [PATCH v3 1/3] mm/hugetlb: fix mremap move length truncation
2026-10-22 3:45 [PATCH v3 0/3] mm/hugetlb: fix mremap bugs Li Zhe
@ 2026-10-22 3:45 ` Li Zhe
2026-10-22 3:45 ` [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode Li Zhe
2026-10-22 3:45 ` [PATCH v3 3/3] mm/hugetlb: fix mremap deadlock when PMD sharing is possible Li Zhe
2 siblings, 0 replies; 8+ messages in thread
From: Li Zhe @ 2026-10-22 3:45 UTC (permalink / raw)
To: akpm, david, muchun.song, osalvador, peterx, rppt, azpijr
Cc: linux-kernel, linux-mm, Li Zhe, stable
move_hugetlb_page_tables() returns the number of bytes moved, but
its return type is int while the generic move_page_tables() path uses
unsigned long lengths.
A hugetlb mremap() move length above INT_MAX can therefore be
misreported to copy_vma_and_data(). The out-of-range conversion to int
is implementation-defined; on common 64-bit builds, for example, a
4 GiB move can be reported as zero, making the caller treat the move as
a failure and use the wrong length for rollback.
Return unsigned long from move_hugetlb_page_tables(), matching
move_page_tables() and preserving the full byte count.
Fixes: 550a7d60bd5e ("mm, hugepages: add mremap() support for hugepage backed vma")
Cc: stable@vger.kernel.org
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/hugetlb.h | 15 ++++++---------
mm/hugetlb.c | 7 +++----
2 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 16c4c4caa126c..d73e3cf7adf9f 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -121,10 +121,9 @@ void hugepage_put_subpool(struct hugepage_subpool *spool);
void hugetlb_dup_vma_private(struct vm_area_struct *vma);
void clear_vma_resv_huge_pages(struct vm_area_struct *vma);
-int move_hugetlb_page_tables(struct vm_area_struct *vma,
- struct vm_area_struct *new_vma,
- unsigned long old_addr, unsigned long new_addr,
- unsigned long len);
+unsigned long move_hugetlb_page_tables(struct vm_area_struct *vma,
+ struct vm_area_struct *new_vma, unsigned long old_addr,
+ unsigned long new_addr, unsigned long len);
int copy_hugetlb_page_range(struct mm_struct *, struct mm_struct *,
struct vm_area_struct *, struct vm_area_struct *);
void unmap_hugepage_range(struct vm_area_struct *,
@@ -339,11 +338,9 @@ static inline int copy_hugetlb_page_range(struct mm_struct *dst,
return 0;
}
-static inline int move_hugetlb_page_tables(struct vm_area_struct *vma,
- struct vm_area_struct *new_vma,
- unsigned long old_addr,
- unsigned long new_addr,
- unsigned long len)
+static inline unsigned long move_hugetlb_page_tables(struct vm_area_struct *vma,
+ struct vm_area_struct *new_vma, unsigned long old_addr,
+ unsigned long new_addr, unsigned long len)
{
BUG();
return 0;
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index cea25773a6c95..4a70716deaa41 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -5139,10 +5139,9 @@ static void move_huge_pte(struct vm_area_struct *vma, unsigned long old_addr,
spin_unlock(dst_ptl);
}
-int move_hugetlb_page_tables(struct vm_area_struct *vma,
- struct vm_area_struct *new_vma,
- unsigned long old_addr, unsigned long new_addr,
- unsigned long len)
+unsigned long move_hugetlb_page_tables(struct vm_area_struct *vma,
+ struct vm_area_struct *new_vma, unsigned long old_addr,
+ unsigned long new_addr, unsigned long len)
{
struct hstate *h = hstate_vma(vma);
struct address_space *mapping = vma->vm_file->f_mapping;
--
2.20.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode
2026-10-22 3:45 [PATCH v3 0/3] mm/hugetlb: fix mremap bugs Li Zhe
2026-10-22 3:45 ` [PATCH v3 1/3] mm/hugetlb: fix mremap move length truncation Li Zhe
@ 2026-10-22 3:45 ` Li Zhe
2026-10-22 6:50 ` Mike Rapoport
2026-10-22 16:36 ` sashiko-bot
2026-10-22 3:45 ` [PATCH v3 3/3] mm/hugetlb: fix mremap deadlock when PMD sharing is possible Li Zhe
2 siblings, 2 replies; 8+ messages in thread
From: Li Zhe @ 2026-10-22 3:45 UTC (permalink / raw)
To: akpm, david, muchun.song, osalvador, peterx, rppt, azpijr
Cc: linux-kernel, linux-mm, Li Zhe
From: "David Hildenbrand (Arm)" <david@kernel.org>
For UFFDIO_POISON to install a poison marker in a hugetlb mapping, it is
sufficient to have the VMA registered for missing mode.
However, UFFD missing mode does currently not block PMD table sharing, so
installing the marker will be visible to everyone using the shared PMD
table, which is wrong as UFFDIO_POISON must stay local to the process.
To fix it, make uffd_disable_huge_pmd_share() cover VM_UFFD_MISSING as
well. With missing mode covered, hugetlb PMD sharing is disabled for all
userfaultfd registered VMAs, so the helper can simply return whether the
VMA has a userfaultfd context instead of testing individual UFFD mode
flags. Add a debug check on the marker installation path.
The existing registration path will then unshare PMD tables, and
subsequent faults will not share them while the mapping remains
registered.
The alternative would be to unshare PMD tables selectively when
installing poison markers, which results in way more complexity. Or
alternatively, simply failing the marker installation, which might break
some use cases.
This also disables PMD sharing for userfaultfd registrations that never
use UFFDIO_POISON.
Suggested-by: Li Zhe <lizhe.67@bytedance.com>
Suggested-by: Mike Rapoport <rppt@kernel.org>
Fixes: 8a13897fb0da ("mm: userfaultfd: support UFFDIO_POISON for hugetlbfs")
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
include/linux/userfaultfd_k.h | 16 +++-------------
mm/hugetlb.c | 1 +
2 files changed, 4 insertions(+), 13 deletions(-)
diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
index a4351cffc60ce..224324dae017d 100644
--- a/include/linux/userfaultfd_k.h
+++ b/include/linux/userfaultfd_k.h
@@ -169,22 +169,12 @@ static inline bool is_mergeable_vm_userfaultfd_ctx(struct vm_area_struct *vma,
}
/*
- * Never enable huge pmd sharing on some uffd registered vmas:
- *
- * - VM_UFFD_WP and VM_UFFD_RWP VMAs, because the write protect / access
- * tracking information is per pgtable entry.
- *
- * - VM_UFFD_MINOR VMAs, because otherwise we would never get minor faults for
- * VMAs which share huge pmds. (If you have two mappings to the same
- * underlying pages, and fault in the non-UFFD-registered one with a write,
- * with huge pmd sharing this would *also* setup the second UFFD-registered
- * mapping, and we'd not get minor faults.)
+ * Never enable huge pmd sharing on userfaultfd registered VMAs, because
+ * userfaultfd state can be tracked per VMA or per page table entry.
*/
static inline bool uffd_disable_huge_pmd_share(struct vm_area_struct *vma)
{
- return vma_test_any_mask(vma,
- mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_RWP,
- VMA_UFFD_MINOR));
+ return vma->vm_userfaultfd_ctx.ctx;
}
/*
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 4a70716deaa41..3cc33ac85a15b 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -6303,6 +6303,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
return -EEXIST;
}
+ VM_WARN_ON_ONCE(size == PMD_SIZE && hugetlb_pmd_shared(dst_pte));
_dst_pte = make_pte_marker(PTE_MARKER_POISONED);
set_huge_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte, size);
--
2.20.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode
2026-10-22 3:45 ` [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode Li Zhe
@ 2026-10-22 6:50 ` Mike Rapoport
2026-10-22 16:36 ` sashiko-bot
1 sibling, 0 replies; 8+ messages in thread
From: Mike Rapoport @ 2026-10-22 6:50 UTC (permalink / raw)
To: Li Zhe
Cc: akpm, david, muchun.song, osalvador, peterx, azpijr, linux-kernel,
linux-mm
On Thu, Oct 22, 2026 at 11:45:42AM +0800, Li Zhe wrote:
> From: "David Hildenbrand (Arm)" <david@kernel.org>
>
> For UFFDIO_POISON to install a poison marker in a hugetlb mapping, it is
> sufficient to have the VMA registered for missing mode.
>
> However, UFFD missing mode does currently not block PMD table sharing, so
> installing the marker will be visible to everyone using the shared PMD
> table, which is wrong as UFFDIO_POISON must stay local to the process.
>
> To fix it, make uffd_disable_huge_pmd_share() cover VM_UFFD_MISSING as
> well. With missing mode covered, hugetlb PMD sharing is disabled for all
> userfaultfd registered VMAs, so the helper can simply return whether the
> VMA has a userfaultfd context instead of testing individual UFFD mode
> flags. Add a debug check on the marker installation path.
>
> The existing registration path will then unshare PMD tables, and
> subsequent faults will not share them while the mapping remains
> registered.
>
> The alternative would be to unshare PMD tables selectively when
> installing poison markers, which results in way more complexity. Or
> alternatively, simply failing the marker installation, which might break
> some use cases.
>
> This also disables PMD sharing for userfaultfd registrations that never
> use UFFDIO_POISON.
>
> Suggested-by: Li Zhe <lizhe.67@bytedance.com>
> Suggested-by: Mike Rapoport <rppt@kernel.org>
> Fixes: 8a13897fb0da ("mm: userfaultfd: support UFFDIO_POISON for hugetlbfs")
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
> include/linux/userfaultfd_k.h | 16 +++-------------
> mm/hugetlb.c | 1 +
> 2 files changed, 4 insertions(+), 13 deletions(-)
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode
2026-10-22 3:45 ` [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode Li Zhe
2026-10-22 6:50 ` Mike Rapoport
@ 2026-10-22 16:36 ` sashiko-bot
2026-10-23 4:09 ` Li Zhe
1 sibling, 1 reply; 8+ messages in thread
From: sashiko-bot @ 2026-10-22 16:36 UTC (permalink / raw)
To: Li Zhe, akpm, david, muchun.song, osalvador, peterx, rppt, azpijr
Cc: linux-mm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm/hugetlb: uffd_disable_huge_pmd_share() leaks poison markers on unregister
--
commit 5da87cbb6b8f2271fb75acc59e14e2d5860cb54c
Author: David Hildenbrand (Arm) <david@kernel.org>
mm/hugetlb: disable PMD sharing for UFFD missing mode
This commit disables hugetlb pmd page table sharing for all userfaultfd
registered vmas by checking vma->vm_userfaultfd_ctx.ctx in
uffd_disable_huge_pmd_share(), preventing poison markers installed under
missing mode from affecting shared pmd tables. It also adds a debug check
in hugetlb_mfill_atomic_pte() when installing a poison marker.
> diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
> index a14b8a9ffb7b1..ebd86677b052d 100644
> --- a/include/linux/userfaultfd_k.h
> +++ b/include/linux/userfaultfd_k.h
> @@ -168,22 +168,12 @@ static inline bool is_mergeable_vm_userfaultfd_ctx(struct vm_area_struct *vma,
> }
>
> /*
> - * Never enable huge pmd sharing on some uffd registered vmas:
> - *
> - * - VM_UFFD_WP and VM_UFFD_RWP VMAs, because the write protect / access
> - * tracking information is per pgtable entry.
> - *
> - * - VM_UFFD_MINOR VMAs, because otherwise we would never get minor faults for
> - * VMAs which share huge pmds. (If you have two mappings to the same
> - * underlying pages, and fault in the non-UFFD-registered one with a write,
> - * with huge pmd sharing this would *also* setup the second UFFD-registered
> - * mapping, and we'd not get minor faults.)
> + * Never enable huge pmd sharing on userfaultfd registered VMAs, because
> + * userfaultfd state can be tracked per VMA or per page table entry.
> */
> static inline bool uffd_disable_huge_pmd_share(struct vm_area_struct *vma)
> {
> - return vma_test_any_mask(vma,
> - mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_RWP,
> - VMA_UFFD_MINOR));
> + return vma->vm_userfaultfd_ctx.ctx;
> }
>
> /*
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 966d249469454..7566f2eed50b0 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -6399,6 +6399,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
> return -EEXIST;
> }
>
> + VM_WARN_ON_ONCE(size == PMD_SIZE && hugetlb_pmd_shared(dst_pte));
> _dst_pte = make_pte_marker(PTE_MARKER_POISONED);
> set_huge_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte, size);
>
[Severity: High]
Can a pmd table containing a poison marker still become shared across
processes after the vma is unregistered from userfaultfd?
When a process registers a shared hugetlbfs vma in missing mode and installs
a poison marker via hugetlb_mfill_atomic_pte(), the marker is written into the
process's private pmd table.
If the process later unregisters the vma or closes the userfaultfd descriptor
(via userfaultfd_unregister() -> userfaultfd_clear_vma() or
userfaultfd_release_all() -> userfaultfd_clear_vma(), or remaps via
mremap_userfaultfd_prep()), the userfaultfd flags and context are cleared
while the poison marker remains in the page table:
mm/userfaultfd.c:userfaultfd_clear_vma() {
...
ret = vma_modify_flags_uffd(vmi, prev, vma, start, end,
&new_vma_flags, NULL_VM_UFFD_CTX,
give_up_on_oom);
if (!IS_ERR(ret))
userfaultfd_reset_ctx(ret);
...
}
Unlike write-protect markers, which are resolved by change_protection() on
unregistration, poison markers intentionally persist after the userfaultfd
context is reset.
Once vma->vm_userfaultfd_ctx.ctx is cleared and the userfaultfd flags are
removed from vma->vm_flags, uffd_disable_huge_pmd_share() returns false and
page_table_shareable() in mm/hugetlb.c considers the vma eligible for sharing
again:
mm/hugetlb.c:huge_pmd_share() {
...
mapping_rmap_tree_foreach(svma, mapping, idx, idx) {
if (svma == vma)
continue;
saddr = page_table_shareable(svma, vma, addr, idx);
if (saddr) {
spte = hugetlb_walk(svma, saddr,
vma_mmu_pagesize(svma));
if (spte) {
ptdesc_pmd_pts_inc(virt_to_ptdesc(spte));
break;
}
}
}
...
}
When another process mapping the same shared hugetlbfs file faults in that
range (hugetlb_fault() -> huge_pte_alloc() -> want_pmd_share() ->
huge_pmd_share()), huge_pmd_share() walks the first process's vma, finds its
populated pmd table containing the poison marker, and shares it into the
second process.
That exposes the second process to the first process's poison marker. In
addition, if the first process subsequently unshares the pmd table (for
example via mprotect(), hugetlb_split(), or re-registering userfaultfd),
__huge_pmd_unshare() clears the pud entry in the first process, causing the
originating process to lose its own poison marker while leaving it installed
in the second process.
Should the vma or ptdesc record that a poison marker was installed so pmd
sharing remains disabled for it even after userfaultfd unregistration?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022034543.51226-1-lizhe.67@bytedance.com?part=2
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode
2026-10-22 16:36 ` sashiko-bot
@ 2026-10-23 4:09 ` Li Zhe
2026-10-25 12:47 ` David Hildenbrand (Arm)
0 siblings, 1 reply; 8+ messages in thread
From: Li Zhe @ 2026-10-23 4:09 UTC (permalink / raw)
To: sashiko-reviews, akpm, david, muchun.song, osalvador, peterx,
rppt, azpijr
Cc: linux-mm
On 10/23/26 12:36 AM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] mm/hugetlb: uffd_disable_huge_pmd_share() leaks poison markers on unregister
> --
>
> commit 5da87cbb6b8f2271fb75acc59e14e2d5860cb54c
> Author: David Hildenbrand (Arm) <david@kernel.org>
>
> mm/hugetlb: disable PMD sharing for UFFD missing mode
>
> This commit disables hugetlb pmd page table sharing for all userfaultfd
> registered vmas by checking vma->vm_userfaultfd_ctx.ctx in
> uffd_disable_huge_pmd_share(), preventing poison markers installed under
> missing mode from affecting shared pmd tables. It also adds a debug check
> in hugetlb_mfill_atomic_pte() when installing a poison marker.
>
>> diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
>> index a14b8a9ffb7b1..ebd86677b052d 100644
>> --- a/include/linux/userfaultfd_k.h
>> +++ b/include/linux/userfaultfd_k.h
>> @@ -168,22 +168,12 @@ static inline bool is_mergeable_vm_userfaultfd_ctx(struct vm_area_struct *vma,
>> }
>>
>> /*
>> - * Never enable huge pmd sharing on some uffd registered vmas:
>> - *
>> - * - VM_UFFD_WP and VM_UFFD_RWP VMAs, because the write protect / access
>> - * tracking information is per pgtable entry.
>> - *
>> - * - VM_UFFD_MINOR VMAs, because otherwise we would never get minor faults for
>> - * VMAs which share huge pmds. (If you have two mappings to the same
>> - * underlying pages, and fault in the non-UFFD-registered one with a write,
>> - * with huge pmd sharing this would *also* setup the second UFFD-registered
>> - * mapping, and we'd not get minor faults.)
>> + * Never enable huge pmd sharing on userfaultfd registered VMAs, because
>> + * userfaultfd state can be tracked per VMA or per page table entry.
>> */
>> static inline bool uffd_disable_huge_pmd_share(struct vm_area_struct *vma)
>> {
>> - return vma_test_any_mask(vma,
>> - mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_RWP,
>> - VMA_UFFD_MINOR));
>> + return vma->vm_userfaultfd_ctx.ctx;
>> }
>>
>> /*
>> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
>> index 966d249469454..7566f2eed50b0 100644
>> --- a/mm/hugetlb.c
>> +++ b/mm/hugetlb.c
>> @@ -6399,6 +6399,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
>> return -EEXIST;
>> }
>>
>> + VM_WARN_ON_ONCE(size == PMD_SIZE && hugetlb_pmd_shared(dst_pte));
>> _dst_pte = make_pte_marker(PTE_MARKER_POISONED);
>> set_huge_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte, size);
>>
> [Severity: High]
> Can a pmd table containing a poison marker still become shared across
> processes after the vma is unregistered from userfaultfd?
>
> When a process registers a shared hugetlbfs vma in missing mode and installs
> a poison marker via hugetlb_mfill_atomic_pte(), the marker is written into the
> process's private pmd table.
>
> If the process later unregisters the vma or closes the userfaultfd descriptor
> (via userfaultfd_unregister() -> userfaultfd_clear_vma() or
> userfaultfd_release_all() -> userfaultfd_clear_vma(), or remaps via
> mremap_userfaultfd_prep()), the userfaultfd flags and context are cleared
> while the poison marker remains in the page table:
>
> mm/userfaultfd.c:userfaultfd_clear_vma() {
> ...
> ret = vma_modify_flags_uffd(vmi, prev, vma, start, end,
> &new_vma_flags, NULL_VM_UFFD_CTX,
> give_up_on_oom);
>
> if (!IS_ERR(ret))
> userfaultfd_reset_ctx(ret);
> ...
> }
>
> Unlike write-protect markers, which are resolved by change_protection() on
> unregistration, poison markers intentionally persist after the userfaultfd
> context is reset.
>
> Once vma->vm_userfaultfd_ctx.ctx is cleared and the userfaultfd flags are
> removed from vma->vm_flags, uffd_disable_huge_pmd_share() returns false and
> page_table_shareable() in mm/hugetlb.c considers the vma eligible for sharing
> again:
>
> mm/hugetlb.c:huge_pmd_share() {
> ...
> mapping_rmap_tree_foreach(svma, mapping, idx, idx) {
> if (svma == vma)
> continue;
>
> saddr = page_table_shareable(svma, vma, addr, idx);
> if (saddr) {
> spte = hugetlb_walk(svma, saddr,
> vma_mmu_pagesize(svma));
> if (spte) {
> ptdesc_pmd_pts_inc(virt_to_ptdesc(spte));
> break;
> }
> }
> }
> ...
> }
>
> When another process mapping the same shared hugetlbfs file faults in that
> range (hugetlb_fault() -> huge_pte_alloc() -> want_pmd_share() ->
> huge_pmd_share()), huge_pmd_share() walks the first process's vma, finds its
> populated pmd table containing the poison marker, and shares it into the
> second process.
>
> That exposes the second process to the first process's poison marker. In
> addition, if the first process subsequently unshares the pmd table (for
> example via mprotect(), hugetlb_split(), or re-registering userfaultfd),
> __huge_pmd_unshare() clears the pud entry in the first process, causing the
> originating process to lose its own poison marker while leaving it installed
> in the second process.
>
> Should the vma or ptdesc record that a poison marker was installed so pmd
> sharing remains disabled for it even after userfaultfd unregistration?
Thanks for the report.
This looks like a valid concern, but I think it is not introduced by
this patch. Before this patch, VM_UFFD_MISSING did not prevent hugetlb
PMD sharing, so UFFDIO_POISON could already install poison markers into
page tables that could later become shared.
This patch addresses the registered-VMA side of that problem by making
hugetlb PMD sharing disabled for userfaultfd-registered VMAs. So new
poison markers should no longer be installed into shared PMD page tables
while the VMA is registered.
The unregister case seems to be a remaining pre-existing lifecycle issue
around PTE_MARKER_POISONED and hugetlb PMD sharing. David previously
mentioned broader poison-marker issues and prototype fixes here:
https://lore.kernel.org/all/bb72cc2c-a46a-44f8-99a2-b036abfdbd26@kernel.org/
So I think we should wait for David's thoughts on how to handle that
remaining case.
Thanks,
Zhe
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode
2026-10-23 4:09 ` Li Zhe
@ 2026-10-25 12:47 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 8+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-25 12:47 UTC (permalink / raw)
To: Li Zhe, sashiko-reviews, akpm, muchun.song, osalvador, peterx,
rppt, azpijr
Cc: linux-mm
On 10/23/26 06:09, Li Zhe wrote:
> On 10/23/26 12:36 AM, sashiko-bot@kernel.org wrote:
>> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>> - [High] mm/hugetlb: uffd_disable_huge_pmd_share() leaks poison markers on unregister
>> --
>>
>> commit 5da87cbb6b8f2271fb75acc59e14e2d5860cb54c
>> Author: David Hildenbrand (Arm) <david@kernel.org>
>>
>> mm/hugetlb: disable PMD sharing for UFFD missing mode
>>
>> This commit disables hugetlb pmd page table sharing for all userfaultfd
>> registered vmas by checking vma->vm_userfaultfd_ctx.ctx in
>> uffd_disable_huge_pmd_share(), preventing poison markers installed under
>> missing mode from affecting shared pmd tables. It also adds a debug check
>> in hugetlb_mfill_atomic_pte() when installing a poison marker.
>>
>>> diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
>>> index a14b8a9ffb7b1..ebd86677b052d 100644
>>> --- a/include/linux/userfaultfd_k.h
>>> +++ b/include/linux/userfaultfd_k.h
>>> @@ -168,22 +168,12 @@ static inline bool is_mergeable_vm_userfaultfd_ctx(struct vm_area_struct *vma,
>>> }
>>>
>>> /*
>>> - * Never enable huge pmd sharing on some uffd registered vmas:
>>> - *
>>> - * - VM_UFFD_WP and VM_UFFD_RWP VMAs, because the write protect / access
>>> - * tracking information is per pgtable entry.
>>> - *
>>> - * - VM_UFFD_MINOR VMAs, because otherwise we would never get minor faults for
>>> - * VMAs which share huge pmds. (If you have two mappings to the same
>>> - * underlying pages, and fault in the non-UFFD-registered one with a write,
>>> - * with huge pmd sharing this would *also* setup the second UFFD-registered
>>> - * mapping, and we'd not get minor faults.)
>>> + * Never enable huge pmd sharing on userfaultfd registered VMAs, because
>>> + * userfaultfd state can be tracked per VMA or per page table entry.
>>> */
>>> static inline bool uffd_disable_huge_pmd_share(struct vm_area_struct *vma)
>>> {
>>> - return vma_test_any_mask(vma,
>>> - mk_vma_flags_from_masks(VMA_UFFD_WP, VMA_UFFD_RWP,
>>> - VMA_UFFD_MINOR));
>>> + return vma->vm_userfaultfd_ctx.ctx;
>>> }
>>>
>>> /*
>>> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
>>> index 966d249469454..7566f2eed50b0 100644
>>> --- a/mm/hugetlb.c
>>> +++ b/mm/hugetlb.c
>>> @@ -6399,6 +6399,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
>>> return -EEXIST;
>>> }
>>>
>>> + VM_WARN_ON_ONCE(size == PMD_SIZE && hugetlb_pmd_shared(dst_pte));
>>> _dst_pte = make_pte_marker(PTE_MARKER_POISONED);
>>> set_huge_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte, size);
>>>
>> [Severity: High]
>> Can a pmd table containing a poison marker still become shared across
>> processes after the vma is unregistered from userfaultfd?
>>
>> When a process registers a shared hugetlbfs vma in missing mode and installs
>> a poison marker via hugetlb_mfill_atomic_pte(), the marker is written into the
>> process's private pmd table.
>>
>> If the process later unregisters the vma or closes the userfaultfd descriptor
>> (via userfaultfd_unregister() -> userfaultfd_clear_vma() or
>> userfaultfd_release_all() -> userfaultfd_clear_vma(), or remaps via
>> mremap_userfaultfd_prep()), the userfaultfd flags and context are cleared
>> while the poison marker remains in the page table:
>>
>> mm/userfaultfd.c:userfaultfd_clear_vma() {
>> ...
>> ret = vma_modify_flags_uffd(vmi, prev, vma, start, end,
>> &new_vma_flags, NULL_VM_UFFD_CTX,
>> give_up_on_oom);
>>
>> if (!IS_ERR(ret))
>> userfaultfd_reset_ctx(ret);
>> ...
>> }
>>
>> Unlike write-protect markers, which are resolved by change_protection() on
>> unregistration, poison markers intentionally persist after the userfaultfd
>> context is reset.
>>
>> Once vma->vm_userfaultfd_ctx.ctx is cleared and the userfaultfd flags are
>> removed from vma->vm_flags, uffd_disable_huge_pmd_share() returns false and
>> page_table_shareable() in mm/hugetlb.c considers the vma eligible for sharing
>> again:
>>
>> mm/hugetlb.c:huge_pmd_share() {
>> ...
>> mapping_rmap_tree_foreach(svma, mapping, idx, idx) {
>> if (svma == vma)
>> continue;
>>
>> saddr = page_table_shareable(svma, vma, addr, idx);
>> if (saddr) {
>> spte = hugetlb_walk(svma, saddr,
>> vma_mmu_pagesize(svma));
>> if (spte) {
>> ptdesc_pmd_pts_inc(virt_to_ptdesc(spte));
>> break;
>> }
>> }
>> }
>> ...
>> }
>>
>> When another process mapping the same shared hugetlbfs file faults in that
>> range (hugetlb_fault() -> huge_pte_alloc() -> want_pmd_share() ->
>> huge_pmd_share()), huge_pmd_share() walks the first process's vma, finds its
>> populated pmd table containing the poison marker, and shares it into the
>> second process.
>>
>> That exposes the second process to the first process's poison marker. In
>> addition, if the first process subsequently unshares the pmd table (for
>> example via mprotect(), hugetlb_split(), or re-registering userfaultfd),
>> __huge_pmd_unshare() clears the pud entry in the first process, causing the
>> originating process to lose its own poison marker while leaving it installed
>> in the second process.
>>
>> Should the vma or ptdesc record that a poison marker was installed so pmd
>> sharing remains disabled for it even after userfaultfd unregistration?
>
> Thanks for the report.
>
> This looks like a valid concern, but I think it is not introduced by
> this patch. Before this patch, VM_UFFD_MISSING did not prevent hugetlb
> PMD sharing, so UFFDIO_POISON could already install poison markers into
> page tables that could later become shared.
>
> This patch addresses the registered-VMA side of that problem by making
> hugetlb PMD sharing disabled for userfaultfd-registered VMAs. So new
> poison markers should no longer be installed into shared PMD page tables
> while the VMA is registered.
>
> The unregister case seems to be a remaining pre-existing lifecycle issue
> around PTE_MARKER_POISONED and hugetlb PMD sharing. David previously
> mentioned broader poison-marker issues and prototype fixes here:
>
> https://lore.kernel.org/all/bb72cc2c-a46a-44f8-99a2-b036abfdbd26@kernel.org/
>
> So I think we should wait for David's thoughts on how to handle that
> remaining case.
I have fixes for the fucked up UFFDIO_POISON handling already. They depend on
some fixes Lorenzo sent out on Friday.
Don't worry about it here :)
--
Cheers,
David
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3 3/3] mm/hugetlb: fix mremap deadlock when PMD sharing is possible
2026-10-22 3:45 [PATCH v3 0/3] mm/hugetlb: fix mremap bugs Li Zhe
2026-10-22 3:45 ` [PATCH v3 1/3] mm/hugetlb: fix mremap move length truncation Li Zhe
2026-10-22 3:45 ` [PATCH v3 2/3] mm/hugetlb: disable PMD sharing for UFFD missing mode Li Zhe
@ 2026-10-22 3:45 ` Li Zhe
2 siblings, 0 replies; 8+ messages in thread
From: Li Zhe @ 2026-10-22 3:45 UTC (permalink / raw)
To: akpm, david, muchun.song, osalvador, peterx, rppt, azpijr
Cc: linux-kernel, linux-mm, Li Zhe, stable
move_hugetlb_page_tables() holds the mapping's i_mmap_rwsem for
write while moving hugetlb PTEs. This prevents races with truncation
and protects huge_pmd_unshare(), but the move loop must not call
helpers that can acquire the same rwsem for read.
huge_pte_alloc() can do that when the destination can use PMD
sharing: it may call huge_pmd_share(), which takes i_mmap_rwsem for
read while searching for a shareable PMD table. Calling it while the
move path already holds i_mmap_rwsem for write can deadlock.
Avoid that path for PMD-sized hugetlb mappings that can use PMD
sharing. Instead of allocating or sharing the destination page table
while moving, zap the source range and let the destination fault path
populate or share the PMD lazily.
Reuse the hugetlb unmap logic for the source-side zap by splitting
the core loop out of __unmap_hugepage_range(). The wrapper keeps the
existing tlb_start_vma(), tlb_end_vma() and huge_pmd_unshare_flush()
behavior, while move_hugetlb_page_tables() can reuse the unmap loop
with its existing mmu_gather.
Fixes: 550a7d60bd5e ("mm, hugepages: add mremap() support for hugepage backed vma")
Cc: stable@vger.kernel.org
Co-developed-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
mm/hugetlb.c | 47 +++++++++++++++++++++++++++++++++++++----------
1 file changed, 37 insertions(+), 10 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 3cc33ac85a15b..dc6fc3fe4d4a3 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -124,6 +124,10 @@ static int __huge_pmd_unshare(struct mmu_gather *tlb,
bool check_locks);
static void hugetlb_unshare_pmds(struct vm_area_struct *vma,
unsigned long start, unsigned long end, bool take_locks);
+static void hugetlb_unmap_range(struct mmu_gather *tlb,
+ struct vm_area_struct *vma, unsigned long start,
+ unsigned long end, struct folio *folio,
+ zap_flags_t zap_flags);
static struct resv_map *vma_resv_map(struct vm_area_struct *vma);
static inline bool subpool_is_free(struct hugepage_subpool *spool)
@@ -5171,6 +5175,8 @@ unsigned long move_hugetlb_page_tables(struct vm_area_struct *vma,
for (; old_addr < old_end; old_addr += sz, new_addr += sz) {
const unsigned long offset_to_last_entry =
(old_addr | last_addr_mask) - old_addr;
+ const unsigned long offset_to_last_dst_entry =
+ (new_addr | last_addr_mask) - new_addr;
src_pte = hugetlb_walk(vma, old_addr, sz);
if (!src_pte) {
@@ -5187,6 +5193,24 @@ unsigned long move_hugetlb_page_tables(struct vm_area_struct *vma,
continue;
}
+ /*
+ * Avoid entering the destination PMD sharing path while holding
+ * i_mmap_rwsem for write. Leave destination page table
+ * population to the fault path, which can share the PMD lazily.
+ */
+ if (sz == PMD_SIZE && want_pmd_share(new_vma, new_addr)) {
+ const unsigned long unmap_size =
+ min(old_end - old_addr,
+ offset_to_last_dst_entry + sz);
+
+ hugetlb_unmap_range(&tlb, vma, old_addr,
+ old_addr + unmap_size, NULL,
+ ZAP_FLAG_DROP_MARKER);
+ old_addr += unmap_size - sz;
+ new_addr += unmap_size - sz;
+ continue;
+ }
+
dst_pte = huge_pte_alloc(mm, new_vma, new_addr, sz);
if (!dst_pte)
break;
@@ -5206,9 +5230,10 @@ unsigned long move_hugetlb_page_tables(struct vm_area_struct *vma,
return len + old_addr - old_end;
}
-void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
- unsigned long start, unsigned long end,
- struct folio *folio, zap_flags_t zap_flags)
+static void hugetlb_unmap_range(struct mmu_gather *tlb,
+ struct vm_area_struct *vma, unsigned long start,
+ unsigned long end, struct folio *folio,
+ zap_flags_t zap_flags)
{
struct mm_struct *mm = vma->vm_mm;
const bool folio_provided = !!folio;
@@ -5226,13 +5251,6 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
BUG_ON(start & ~huge_page_mask(h));
BUG_ON(end & ~huge_page_mask(h));
- /*
- * This is a hugetlb vma, all the pte entries should point
- * to huge page.
- */
- tlb_change_page_size(tlb, sz);
- tlb_start_vma(tlb, vma);
-
last_addr_mask = hugetlb_mask_last_page(h);
address = start;
for (; address < end; address += sz) {
@@ -5361,6 +5379,15 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
if (folio_provided)
break;
}
+}
+
+void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
+ unsigned long start, unsigned long end,
+ struct folio *folio, zap_flags_t zap_flags)
+{
+ tlb_change_page_size(tlb, huge_page_size(hstate_vma(vma)));
+ tlb_start_vma(tlb, vma);
+ hugetlb_unmap_range(tlb, vma, start, end, folio, zap_flags);
tlb_end_vma(tlb, vma);
huge_pmd_unshare_flush(tlb, vma);
--
2.20.1
^ permalink raw reply related [flat|nested] 8+ messages in thread