* [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page
@ 2026-10-19 1:09 Samarth Narang
2026-10-19 2:07 ` Lance Yang
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Samarth Narang @ 2026-10-19 1:09 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand
Cc: Xu Xin, Chengming Zhou, Peter Xu, Kiryl Shutsemau, Mike Rapoport,
Lance Yang, linux-mm, linux-kernel, Samarth Narang, stable
replace_page() maps the KSM page with a PTE built from scratch, so the
soft-dirty and uffd bits of the PTE it replaces are lost. Due to this, a
write before the merge no longer shows as soft-dirty, the next write to
a uffd-wp page raises no write-protect fault, and because the page is
mapped with the VMA's normal permissions, on a uffd-rwp VMA the next
access raises no RWP fault.
Copy the soft-dirty and uffd bits from the original PTE to the new PTE,
and restore PAGE_NONE for RWP VMAs, as try_to_map_unused_to_zeropage()
does.
Fixes: 0f8975ec4db2 ("mm: soft-dirty bits for user memory changes tracking")
Fixes: 63b2d4174c4a ("userfaultfd: wp: add the writeprotect API to userfaultfd ioctl")
Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
Cc: stable@vger.kernel.org
Tested-by: Lance Yang <lance.yang@linux.dev>
Assisted-by: LLM
Signed-off-by: Samarth Narang <samarth.colleges@gmail.com>
---
Notes:
v1: https://lore.kernel.org/all/20261018122957.526467-1-samarth.colleges@gmail.com/
Changes since v1 (thanks, Lance):
- Fixes: point the uffd-wp and RWP tags at the commits that make the
registration modes reachable (63b2d4174c4a, 2d427bb01639).
- Shorten the comment; the uffd bit carries RWP state too.
- Add Lance's Tested-by.
mm/ksm.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/mm/ksm.c b/mm/ksm.c
index dbfb8f98b189..de144fa07c49 100644
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -34,6 +34,7 @@
#include <linux/memory.h>
#include <linux/mmu_notifier.h>
#include <linux/swap.h>
+#include <linux/userfaultfd_k.h>
#include <linux/ksm.h>
#include <linux/hashtable.h>
#include <linux/freezer.h>
@@ -1463,6 +1464,17 @@ static int replace_page(struct vm_area_struct *vma, struct page *page,
dec_mm_counter(mm, MM_ANONPAGES);
}
+ /*
+ * Preserve soft-dirty and uffd bits when replacing the page.
+ * Restore PAGE_NONE for RWP-protected PTEs.
+ */
+ if (pte_soft_dirty(orig_pte))
+ newpte = pte_mksoft_dirty(newpte);
+ if (pte_uffd(orig_pte))
+ newpte = pte_mkuffd(newpte);
+ if (pte_uffd(orig_pte) && userfaultfd_rwp(vma))
+ newpte = pte_modify(newpte, PAGE_NONE);
+
flush_cache_page(vma, addr, pte_pfn(ptep_get(ptep)));
/*
* No need to notify as we are replacing a read only page with another
base-commit: 48fe11c8d0f1e4e59426a3606f20ac1130b6d8dc
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page
2026-10-19 1:09 [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page Samarth Narang
@ 2026-10-19 2:07 ` Lance Yang
2026-10-19 2:11 ` Samarth Narang
2026-10-19 13:15 ` sashiko-bot
2026-10-19 13:24 ` David Hildenbrand (Arm)
2 siblings, 1 reply; 8+ messages in thread
From: Lance Yang @ 2026-10-19 2:07 UTC (permalink / raw)
To: Samarth Narang
Cc: Xu Xin, Chengming Zhou, Peter Xu, Kiryl Shutsemau, Mike Rapoport,
linux-mm, linux-kernel, stable, David Hildenbrand, Andrew Morton
On 2026/10/19 09:09, Samarth Narang wrote:
> replace_page() maps the KSM page with a PTE built from scratch, so the
> soft-dirty and uffd bits of the PTE it replaces are lost. Due to this, a
> write before the merge no longer shows as soft-dirty, the next write to
> a uffd-wp page raises no write-protect fault, and because the page is
> mapped with the VMA's normal permissions, on a uffd-rwp VMA the next
> access raises no RWP fault.
>
> Copy the soft-dirty and uffd bits from the original PTE to the new PTE,
> and restore PAGE_NONE for RWP VMAs, as try_to_map_unused_to_zeropage()
> does.
>
> Fixes: 0f8975ec4db2 ("mm: soft-dirty bits for user memory changes tracking")
> Fixes: 63b2d4174c4a ("userfaultfd: wp: add the writeprotect API to userfaultfd ioctl")
> Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
> Cc: stable@vger.kernel.org
> Tested-by: Lance Yang <lance.yang@linux.dev>
> Assisted-by: LLM
> Signed-off-by: Samarth Narang <samarth.colleges@gmail.com>
> ---
>
> Notes:
> v1: https://lore.kernel.org/all/20261018122957.526467-1-samarth.colleges@gmail.com/
>
> Changes since v1 (thanks, Lance):
> - Fixes: point the uffd-wp and RWP tags at the commits that make the
> registration modes reachable (63b2d4174c4a, 2d427bb01639).
> - Shorten the comment; the uffd bit carries RWP state too.
> - Add Lance's Tested-by.
Generally, no need to rush the next version out, please. Let it soak for a
few days so folks have time to review properly, instead of making everyone
start over every time. Just saying :)
Cheers, Lance
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page
2026-10-19 2:07 ` Lance Yang
@ 2026-10-19 2:11 ` Samarth Narang
0 siblings, 0 replies; 8+ messages in thread
From: Samarth Narang @ 2026-10-19 2:11 UTC (permalink / raw)
To: Lance Yang
Cc: Xu Xin, Chengming Zhou, Peter Xu, Kiryl Shutsemau, Mike Rapoport,
linux-mm, linux-kernel, stable, David Hildenbrand, Andrew Morton
[-- Attachment #1: Type: text/plain, Size: 159 bytes --]
👍
Samarth Narang reacted via Gmail
<https://www.google.com/gmail/about/?utm_source=gmail-in-product&utm_medium=et&utm_campaign=emojireactionemail#app>
[-- Attachment #2: Type: text/vnd.google.email-reaction+json, Size: 40 bytes --]
{
"emoji": "👍",
"version": 1
}
[-- Attachment #3: Type: text/html, Size: 291 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page
2026-10-19 1:09 [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page Samarth Narang
2026-10-19 2:07 ` Lance Yang
@ 2026-10-19 13:15 ` sashiko-bot
2026-10-19 13:24 ` David Hildenbrand (Arm)
2 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-10-19 13:15 UTC (permalink / raw)
To: Samarth Narang, Andrew Morton, David Hildenbrand
Cc: Xu Xin, Chengming Zhou, Peter Xu, Kiryl Shutsemau, Mike Rapoport,
Lance Yang, linux-mm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mm/ksm: installing PAGE_NONE in replace_page() breaks break_ksm() on RWP PTEs
- [Medium] mm/ksm: replace_page() sets soft-dirty on clean KSM zero-page PTEs
--
commit e9f8ba7271894cc9b005a46048d0bcc61cff0fff
Author: Samarth Narang <samarth.colleges@gmail.com>
mm/ksm: keep soft-dirty and uffd-wp when merging a page
This commit preserves the soft-dirty and userfaultfd bits when replace_page()
installs a KSM PTE in place of an anonymous page. For VMAs registered with
userfaultfd RWP, it also modifies the new PTE with PAGE_NONE when the uffd bit
is set.
> diff --git a/mm/ksm.c b/mm/ksm.c
> index dbfb8f98b1892..de144fa07c493 100644
> --- a/mm/ksm.c
> +++ b/mm/ksm.c
[ ... ]
> @@ -1463,6 +1464,17 @@ static int replace_page(struct vm_area_struct *vma, struct page *page,
> dec_mm_counter(mm, MM_ANONPAGES);
> }
>
> + /*
> + * Preserve soft-dirty and uffd bits when replacing the page.
> + * Restore PAGE_NONE for RWP-protected PTEs.
> + */
> + if (pte_soft_dirty(orig_pte))
> + newpte = pte_mksoft_dirty(newpte);
[Severity: Medium]
When ksm_use_zero_pages is enabled and replace_page() merges a zero-filled
page whose soft-dirty bit was cleared, earlier in replace_page() the zero-page
PTE is constructed with pte_mkdirty():
newpte = pte_mkdirty(pte_mkspecial(pfn_pte(page_to_pfn(kpage), vma->vm_page_prot)));
On architectures such as x86, pte_mkdirty() sets both _PAGE_DIRTY and
_PAGE_SOFT_DIRTY:
arch/x86/include/asm/pgtable.h:pte_mkdirty() {
pte = pte_set_flags(pte, _PAGE_DIRTY | _PAGE_SOFT_DIRTY);
return pte_mksaveddirty(pte);
}
Without an else branch calling pte_clear_soft_dirty(newpte) (similar to
remove_migration_pte() in mm/migrate.c), won't clean pages merged with the KSM
zero page end up falsely marked soft-dirty in /proc/<pid>/pagemap?
> + if (pte_uffd(orig_pte))
> + newpte = pte_mkuffd(newpte);
> + if (pte_uffd(orig_pte) && userfaultfd_rwp(vma))
> + newpte = pte_modify(newpte, PAGE_NONE);
[Severity: High]
How does installing a KSM PTE with PAGE_NONE and the uffd bit interact with
break_ksm() when unmerging KSM pages (via ksm_madvise() for MADV_UNMERGEABLE,
prep_move_vma() during mremap(), __ksm_del_vma(),
unmerge_and_remove_all_rmap_items(), or break_cow())?
break_ksm() invokes handle_mm_fault() with FAULT_FLAG_UNSHARE |
FAULT_FLAG_REMOTE (and without FAULT_FLAG_ALLOW_RETRY):
mm/ksm.c:break_ksm() {
do {
...
ret = handle_mm_fault(vma, addr,
FAULT_FLAG_UNSHARE | FAULT_FLAG_REMOTE,
NULL);
} while (!(ret & (VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV | VM_FAULT_OOM)));
}
In handle_pte_fault(), the pte_protnone() check runs before the
FAULT_FLAG_UNSHARE check and dispatches to do_uffd_rwp():
mm/memory.c:handle_pte_fault() {
if (pte_protnone(vmf->orig_pte) && vma_is_accessible(vmf->vma)) {
if (userfaultfd_pte_rwp(vmf->vma, vmf->orig_pte))
return do_uffd_rwp(vmf);
return do_numa_page(vmf);
}
}
1. In synchronous RWP mode (!userfaultfd_rwp_async(vmf->vma)), do_uffd_rwp()
calls handle_userfault(vmf, VM_UFFD_RWP). Because FAULT_FLAG_ALLOW_RETRY is
not set, handle_userfault() triggers the CONFIG_DEBUG_VM warning for missing
FAULT_FLAG_ALLOW_RETRY and returns VM_FAULT_SIGBUS. break_ksm() then exits its
loop and returns 0, leaving the KSM page (and any subsequent KSM pages in the
range) still mapped after MADV_UNMERGEABLE, mremap(), or VMA teardown.
2. In asynchronous RWP mode (userfaultfd_rwp_async(vmf->vma)), the first
iteration of break_ksm() runs do_uffd_rwp(), which restores vma->vm_page_prot,
clears the uffd bit, and marks the PTE young, and the second iteration
unshares the page via do_wp_page() -> wp_page_copy(), silently stripping RWP
protection during an internal KSM unmerge.
3. Even if handle_pte_fault() were to reach wp_page_copy() directly for
FAULT_FLAG_UNSHARE, wp_page_copy() copies pte_uffd(vmf->orig_pte) onto entry
built from vma->vm_page_prot without restoring PAGE_NONE when
userfaultfd_rwp(vma) is true:
mm/memory.c:wp_page_copy() {
if (unlikely(unshare)) {
if (pte_soft_dirty(vmf->orig_pte))
entry = pte_mksoft_dirty(entry);
if (pte_uffd(vmf->orig_pte))
entry = pte_mkuffd(entry);
}
}
Does FAULT_FLAG_UNSHARE in handle_pte_fault() and wp_page_copy() need to
handle RWP-protected PTEs before KSM merges them with PAGE_NONE?
>
> flush_cache_page(vma, addr, pte_pfn(ptep_get(ptep)));
> /*
[]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261019010934.697895-1-samarth.colleges@gmail.com?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page
2026-10-19 1:09 [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page Samarth Narang
2026-10-19 2:07 ` Lance Yang
2026-10-19 13:15 ` sashiko-bot
@ 2026-10-19 13:24 ` David Hildenbrand (Arm)
2026-10-20 5:55 ` Lance Yang
2 siblings, 1 reply; 8+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-19 13:24 UTC (permalink / raw)
To: Samarth Narang, Andrew Morton
Cc: Xu Xin, Chengming Zhou, Peter Xu, Kiryl Shutsemau, Mike Rapoport,
Lance Yang, linux-mm, linux-kernel, stable, Kiryl Shutsemau
On 10/19/26 03:09, Samarth Narang wrote:
> replace_page() maps the KSM page with a PTE built from scratch, so the
> soft-dirty and uffd bits of the PTE it replaces are lost. Due to this, a
> write before the merge no longer shows as soft-dirty, the next write to
> a uffd-wp page raises no write-protect fault, and because the page is
> mapped with the VMA's normal permissions, on a uffd-rwp VMA the next
> access raises no RWP fault.
>
> Copy the soft-dirty and uffd bits from the original PTE to the new PTE,
> and restore PAGE_NONE for RWP VMAs, as try_to_map_unused_to_zeropage()
> does.
>
> Fixes: 0f8975ec4db2 ("mm: soft-dirty bits for user memory changes tracking")
> Fixes: 63b2d4174c4a ("userfaultfd: wp: add the writeprotect API to userfaultfd ioctl")
> Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
I would actually split that up into separate patches.
Over-indicating soft-dirty (as raised by Sashiko) is in general not a problem.
Losing WP bits is.
UFFD, the gift that keeps on giving.
How to deal RWP properly is a good question (see Sashiko review). I feel like
some more stuff might be missing there to handle this correctly.
--
Cheers,
David
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page
2026-10-19 13:24 ` David Hildenbrand (Arm)
@ 2026-10-20 5:55 ` Lance Yang
2026-10-20 13:03 ` David Hildenbrand (Arm)
0 siblings, 1 reply; 8+ messages in thread
From: Lance Yang @ 2026-10-20 5:55 UTC (permalink / raw)
To: david
Cc: samarth.colleges, akpm, xu.xin, chengming.zhou, peterx, kas, rppt,
lance.yang, linux-mm, linux-kernel, stable
On Mon, Oct 19, 2026 at 03:24:11PM +0200, David Hildenbrand (Arm) wrote:
>On 10/19/26 03:09, Samarth Narang wrote:
>> replace_page() maps the KSM page with a PTE built from scratch, so the
>> soft-dirty and uffd bits of the PTE it replaces are lost. Due to this, a
>> write before the merge no longer shows as soft-dirty, the next write to
>> a uffd-wp page raises no write-protect fault, and because the page is
>> mapped with the VMA's normal permissions, on a uffd-rwp VMA the next
>> access raises no RWP fault.
>>
>> Copy the soft-dirty and uffd bits from the original PTE to the new PTE,
>> and restore PAGE_NONE for RWP VMAs, as try_to_map_unused_to_zeropage()
>> does.
>>
>> Fixes: 0f8975ec4db2 ("mm: soft-dirty bits for user memory changes tracking")
>> Fixes: 63b2d4174c4a ("userfaultfd: wp: add the writeprotect API to userfaultfd ioctl")
>> Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
>
>I would actually split that up into separate patches.
>
>Over-indicating soft-dirty (as raised by Sashiko) is in general not a problem.
Right.
>Losing WP bits is.
>
>UFFD, the gift that keeps on giving.
Haha, yeah, I stared at the code a bit. Say we arm RWP on an existing KSM
mapping, then do MADV_UNMERGEABLE. We're just replacing the shared page
with a private copy. That shouldn't count as an access to the mapping ...
no?
But the protnone check sends us to do_uffd_rwp() before the
FAULT_FLAG_UNSHARE handling. No FAULT_FLAG_ALLOW_RETRY here, so sync RWP
gives us VM_FAULT_SIGBUS. Then break_ksm() does this:
static int break_ksm(struct vm_area_struct *vma, unsigned long addr,
unsigned long end, bool lock_vma)
{
vm_fault_t ret = 0;
...
do {
...
ret = handle_mm_fault(vma, addr,
FAULT_FLAG_UNSHARE | FAULT_FLAG_REMOTE,
NULL);
} while (!(ret & (VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV | VM_FAULT_OOM)));
...
return (ret & VM_FAULT_OOM) ? -ENOMEM : 0;
}
So we report success with the page still merged. Async RWP does unshare
it, but only after clearing the uffd bit.
I'd let FAULT_FLAG_UNSHARE skip do_uffd_rwp(). We'd still need to carry
PAGE_NONE over in wp_page_copy(), though. That PTE is rebuilt from
vma->vm_page_prot, so the uffd bit alone won't stop reads.
>How to deal RWP properly is a good question (see Sashiko review). I feel like
>some more stuff might be missing there to handle this correctly.
Tested, this is what I had in mind:
---8<---
diff --git a/mm/memory.c b/mm/memory.c
index 976cd03954e3..d99f5ef1dd8c 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4119,8 +4119,11 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)
if (unlikely(unshare)) {
if (pte_soft_dirty(vmf->orig_pte))
entry = pte_mksoft_dirty(entry);
- if (pte_uffd(vmf->orig_pte))
+ if (pte_uffd(vmf->orig_pte)) {
entry = pte_mkuffd(entry);
+ if (userfaultfd_rwp(vma))
+ entry = pte_modify(entry, PAGE_NONE);
+ }
} else {
entry = maybe_mkwrite(pte_mkdirty(entry), vma);
}
@@ -6656,9 +6659,12 @@ static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
* VM_UFFD_RWP VMA, a protnone PTE without the uffd bit is
* NUMA hinting and must still fall through to do_numa_page().
*/
- if (userfaultfd_pte_rwp(vmf->vma, vmf->orig_pte))
- return do_uffd_rwp(vmf);
- return do_numa_page(vmf);
+ if (userfaultfd_pte_rwp(vmf->vma, vmf->orig_pte)) {
+ if (!(vmf->flags & FAULT_FLAG_UNSHARE))
+ return do_uffd_rwp(vmf);
+ } else {
+ return do_numa_page(vmf);
+ }
}
spin_lock(vmf->ptl);
--
Cheers, Lance
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page
2026-10-20 5:55 ` Lance Yang
@ 2026-10-20 13:03 ` David Hildenbrand (Arm)
2026-10-20 13:29 ` Lance Yang
0 siblings, 1 reply; 8+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-20 13:03 UTC (permalink / raw)
To: Lance Yang
Cc: samarth.colleges, akpm, xu.xin, chengming.zhou, peterx, kas, rppt,
linux-mm, linux-kernel, stable
On 10/20/26 07:55, Lance Yang wrote:
>
> On Mon, Oct 19, 2026 at 03:24:11PM +0200, David Hildenbrand (Arm) wrote:
>> On 10/19/26 03:09, Samarth Narang wrote:
>>> replace_page() maps the KSM page with a PTE built from scratch, so the
>>> soft-dirty and uffd bits of the PTE it replaces are lost. Due to this, a
>>> write before the merge no longer shows as soft-dirty, the next write to
>>> a uffd-wp page raises no write-protect fault, and because the page is
>>> mapped with the VMA's normal permissions, on a uffd-rwp VMA the next
>>> access raises no RWP fault.
>>>
>>> Copy the soft-dirty and uffd bits from the original PTE to the new PTE,
>>> and restore PAGE_NONE for RWP VMAs, as try_to_map_unused_to_zeropage()
>>> does.
>>>
>>> Fixes: 0f8975ec4db2 ("mm: soft-dirty bits for user memory changes tracking")
>>> Fixes: 63b2d4174c4a ("userfaultfd: wp: add the writeprotect API to userfaultfd ioctl")
>>> Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
>>
>> I would actually split that up into separate patches.
>>
>> Over-indicating soft-dirty (as raised by Sashiko) is in general not a problem.
>
> Right.
>
>> Losing WP bits is.
>>
>> UFFD, the gift that keeps on giving.
>
> Haha, yeah, I stared at the code a bit. Say we arm RWP on an existing KSM
> mapping, then do MADV_UNMERGEABLE. We're just replacing the shared page
> with a private copy. That shouldn't count as an access to the mapping ...
> no?
>
> But the protnone check sends us to do_uffd_rwp() before the
> FAULT_FLAG_UNSHARE handling. No FAULT_FLAG_ALLOW_RETRY here, so sync RWP
> gives us VM_FAULT_SIGBUS. Then break_ksm() does this:
>
> static int break_ksm(struct vm_area_struct *vma, unsigned long addr,
> unsigned long end, bool lock_vma)
> {
> vm_fault_t ret = 0;
> ...
> do {
> ...
> ret = handle_mm_fault(vma, addr,
> FAULT_FLAG_UNSHARE | FAULT_FLAG_REMOTE,
> NULL);
> } while (!(ret & (VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV | VM_FAULT_OOM)));
> ...
> return (ret & VM_FAULT_OOM) ? -ENOMEM : 0;
> }
>
> So we report success with the page still merged. Async RWP does unshare
> it, but only after clearing the uffd bit.
>
> I'd let FAULT_FLAG_UNSHARE skip do_uffd_rwp(). We'd still need to carry
> PAGE_NONE over in wp_page_copy(), though. That PTE is rebuilt from
> vma->vm_page_prot, so the uffd bit alone won't stop reads.
>
>> How to deal RWP properly is a good question (see Sashiko review). I feel like
>> some more stuff might be missing there to handle this correctly.
>
> Tested, this is what I had in mind:
>
> ---8<---
> diff --git a/mm/memory.c b/mm/memory.c
> index 976cd03954e3..d99f5ef1dd8c 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -4119,8 +4119,11 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)
> if (unlikely(unshare)) {
> if (pte_soft_dirty(vmf->orig_pte))
> entry = pte_mksoft_dirty(entry);
> - if (pte_uffd(vmf->orig_pte))
> + if (pte_uffd(vmf->orig_pte)) {
> entry = pte_mkuffd(entry);
> + if (userfaultfd_rwp(vma))
> + entry = pte_modify(entry, PAGE_NONE);
> + }
> } else {
> entry = maybe_mkwrite(pte_mkdirty(entry), vma);
> }
> @@ -6656,9 +6659,12 @@ static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
> * VM_UFFD_RWP VMA, a protnone PTE without the uffd bit is
> * NUMA hinting and must still fall through to do_numa_page().
> */
> - if (userfaultfd_pte_rwp(vmf->vma, vmf->orig_pte))
> - return do_uffd_rwp(vmf);
> - return do_numa_page(vmf);
> + if (userfaultfd_pte_rwp(vmf->vma, vmf->orig_pte)) {
> + if (!(vmf->flags & FAULT_FLAG_UNSHARE))
> + return do_uffd_rwp(vmf);
> + } else {
> + return do_numa_page(vmf);
> + }
> }
Looks like a reasonable direction (without looking into all the details).
I've been also wondering whether KSM should simply skip these uffd regions, they
it never worked properly ... but I'm sure we'd run into issue with leftover uffd
pte bits :(
--
Cheers,
David
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page
2026-10-20 13:03 ` David Hildenbrand (Arm)
@ 2026-10-20 13:29 ` Lance Yang
0 siblings, 0 replies; 8+ messages in thread
From: Lance Yang @ 2026-10-20 13:29 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: samarth.colleges, akpm, xu.xin, chengming.zhou, peterx, kas, rppt,
linux-mm, linux-kernel, stable
On 2026/10/20 21:03, David Hildenbrand (Arm) wrote:
> On 10/20/26 07:55, Lance Yang wrote:
>>
>> On Mon, Oct 19, 2026 at 03:24:11PM +0200, David Hildenbrand (Arm) wrote:
>>> On 10/19/26 03:09, Samarth Narang wrote:
>>>> replace_page() maps the KSM page with a PTE built from scratch, so the
>>>> soft-dirty and uffd bits of the PTE it replaces are lost. Due to this, a
>>>> write before the merge no longer shows as soft-dirty, the next write to
>>>> a uffd-wp page raises no write-protect fault, and because the page is
>>>> mapped with the VMA's normal permissions, on a uffd-rwp VMA the next
>>>> access raises no RWP fault.
>>>>
>>>> Copy the soft-dirty and uffd bits from the original PTE to the new PTE,
>>>> and restore PAGE_NONE for RWP VMAs, as try_to_map_unused_to_zeropage()
>>>> does.
>>>>
>>>> Fixes: 0f8975ec4db2 ("mm: soft-dirty bits for user memory changes tracking")
>>>> Fixes: 63b2d4174c4a ("userfaultfd: wp: add the writeprotect API to userfaultfd ioctl")
>>>> Fixes: 2d427bb01639 ("mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP")
>>>
>>> I would actually split that up into separate patches.
>>>
>>> Over-indicating soft-dirty (as raised by Sashiko) is in general not a problem.
>>
>> Right.
>>
>>> Losing WP bits is.
>>>
>>> UFFD, the gift that keeps on giving.
>>
>> Haha, yeah, I stared at the code a bit. Say we arm RWP on an existing KSM
>> mapping, then do MADV_UNMERGEABLE. We're just replacing the shared page
>> with a private copy. That shouldn't count as an access to the mapping ...
>> no?
>>
>> But the protnone check sends us to do_uffd_rwp() before the
>> FAULT_FLAG_UNSHARE handling. No FAULT_FLAG_ALLOW_RETRY here, so sync RWP
>> gives us VM_FAULT_SIGBUS. Then break_ksm() does this:
>>
>> static int break_ksm(struct vm_area_struct *vma, unsigned long addr,
>> unsigned long end, bool lock_vma)
>> {
>> vm_fault_t ret = 0;
>> ...
>> do {
>> ...
>> ret = handle_mm_fault(vma, addr,
>> FAULT_FLAG_UNSHARE | FAULT_FLAG_REMOTE,
>> NULL);
>> } while (!(ret & (VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV | VM_FAULT_OOM)));
>> ...
>> return (ret & VM_FAULT_OOM) ? -ENOMEM : 0;
>> }
>>
>> So we report success with the page still merged. Async RWP does unshare
>> it, but only after clearing the uffd bit.
>>
>> I'd let FAULT_FLAG_UNSHARE skip do_uffd_rwp(). We'd still need to carry
>> PAGE_NONE over in wp_page_copy(), though. That PTE is rebuilt from
>> vma->vm_page_prot, so the uffd bit alone won't stop reads.
>>
>>> How to deal RWP properly is a good question (see Sashiko review). I feel like
>>> some more stuff might be missing there to handle this correctly.
>>
>> Tested, this is what I had in mind:
>>
>> ---8<---
>> diff --git a/mm/memory.c b/mm/memory.c
>> index 976cd03954e3..d99f5ef1dd8c 100644
>> --- a/mm/memory.c
>> +++ b/mm/memory.c
>> @@ -4119,8 +4119,11 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)
>> if (unlikely(unshare)) {
>> if (pte_soft_dirty(vmf->orig_pte))
>> entry = pte_mksoft_dirty(entry);
>> - if (pte_uffd(vmf->orig_pte))
>> + if (pte_uffd(vmf->orig_pte)) {
>> entry = pte_mkuffd(entry);
>> + if (userfaultfd_rwp(vma))
>> + entry = pte_modify(entry, PAGE_NONE);
>> + }
>> } else {
>> entry = maybe_mkwrite(pte_mkdirty(entry), vma);
>> }
>> @@ -6656,9 +6659,12 @@ static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
>> * VM_UFFD_RWP VMA, a protnone PTE without the uffd bit is
>> * NUMA hinting and must still fall through to do_numa_page().
>> */
>> - if (userfaultfd_pte_rwp(vmf->vma, vmf->orig_pte))
>> - return do_uffd_rwp(vmf);
>> - return do_numa_page(vmf);
>> + if (userfaultfd_pte_rwp(vmf->vma, vmf->orig_pte)) {
>> + if (!(vmf->flags & FAULT_FLAG_UNSHARE))
>> + return do_uffd_rwp(vmf);
>> + } else {
>> + return do_numa_page(vmf);
>> + }
>> }
>
> Looks like a reasonable direction (without looking into all the details).
>
> I've been also wondering whether KSM should simply skip these uffd regions, they
> it never worked properly ... but I'm sure we'd run into issue with leftover uffd
+1. Did it ever actually work there :) Why not just skip them and
call it a day :D
> pte bits :(
>
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-20 13:29 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19 1:09 [PATCH v2] mm/ksm: keep soft-dirty and uffd-wp when merging a page Samarth Narang
2026-10-19 2:07 ` Lance Yang
2026-10-19 2:11 ` Samarth Narang
2026-10-19 13:15 ` sashiko-bot
2026-10-19 13:24 ` David Hildenbrand (Arm)
2026-10-20 5:55 ` Lance Yang
2026-10-20 13:03 ` David Hildenbrand (Arm)
2026-10-20 13:29 ` Lance Yang
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.