Skip to content

Commit 71fd027

Browse files
author
Rafael Aquini
committed
mm: clear uffd-wp PTE/PMD state on mremap()
JIRA: https://issues.redhat.com/browse/RHEL-84184 JIRA: https://issues.redhat.com/browse/RHEL-80529 CVE: CVE-2025-21696 This patch is a backport of the following upstream commit: commit 0cef0bb Author: Ryan Roberts <[email protected]> Date: Tue Jan 7 14:47:52 2025 +0000 mm: clear uffd-wp PTE/PMD state on mremap() When mremap()ing a memory region previously registered with userfaultfd as write-protected but without UFFD_FEATURE_EVENT_REMAP, an inconsistency in flag clearing leads to a mismatch between the vma flags (which have uffd-wp cleared) and the pte/pmd flags (which do not have uffd-wp cleared). This mismatch causes a subsequent mprotect(PROT_WRITE) to trigger a warning in page_table_check_pte_flags() due to setting the pte to writable while uffd-wp is still set. Fix this by always explicitly clearing the uffd-wp pte/pmd flags on any such mremap() so that the values are consistent with the existing clearing of VM_UFFD_WP. Be careful to clear the logical flag regardless of its physical form; a PTE bit, a swap PTE bit, or a PTE marker. Cover PTE, huge PMD and hugetlb paths. Link: https://lkml.kernel.org/r/[email protected] Co-developed-by: Mikołaj Lenczewski <[email protected]> Signed-off-by: Mikołaj Lenczewski <[email protected]> Signed-off-by: Ryan Roberts <[email protected]> Closes: https://lore.kernel.org/linux-mm/[email protected]/ Fixes: 63b2d41 ("userfaultfd: wp: add the writeprotect API to userfaultfd ioctl") Cc: David Hildenbrand <[email protected]> Cc: Jann Horn <[email protected]> Cc: Liam R. Howlett <[email protected]> Cc: Lorenzo Stoakes <[email protected]> Cc: Mark Rutland <[email protected]> Cc: Muchun Song <[email protected]> Cc: Peter Xu <[email protected]> Cc: Shuah Khan <[email protected]> Cc: Vlastimil Babka <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Rafael Aquini <[email protected]>
1 parent d70c937 commit 71fd027

File tree

4 files changed

+68
-2
lines changed

4 files changed

+68
-2
lines changed

include/linux/userfaultfd_k.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@ static inline bool vma_can_userfault(struct vm_area_struct *vma,
220220
vma_is_shmem(vma);
221221
}
222222

223+
static inline bool vma_has_uffd_without_event_remap(struct vm_area_struct *vma)
224+
{
225+
struct userfaultfd_ctx *uffd_ctx = vma->vm_userfaultfd_ctx.ctx;
226+
227+
return uffd_ctx && (uffd_ctx->features & UFFD_FEATURE_EVENT_REMAP) == 0;
228+
}
229+
223230
extern int dup_userfaultfd(struct vm_area_struct *, struct list_head *);
224231
extern void dup_userfaultfd_complete(struct list_head *);
225232
void dup_userfaultfd_fail(struct list_head *);
@@ -343,6 +350,11 @@ static inline bool userfaultfd_wp_unpopulated(struct vm_area_struct *vma)
343350
return false;
344351
}
345352

353+
static inline bool vma_has_uffd_without_event_remap(struct vm_area_struct *vma)
354+
{
355+
return false;
356+
}
357+
346358
#endif /* CONFIG_USERFAULTFD */
347359

348360
static inline bool userfaultfd_wp_use_markers(struct vm_area_struct *vma)

mm/huge_memory.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,16 @@ static pmd_t move_soft_dirty_pmd(pmd_t pmd)
17571757
return pmd;
17581758
}
17591759

1760+
static pmd_t clear_uffd_wp_pmd(pmd_t pmd)
1761+
{
1762+
if (pmd_present(pmd))
1763+
pmd = pmd_clear_uffd_wp(pmd);
1764+
else if (is_swap_pmd(pmd))
1765+
pmd = pmd_swp_clear_uffd_wp(pmd);
1766+
1767+
return pmd;
1768+
}
1769+
17601770
bool move_huge_pmd(struct vm_area_struct *vma, unsigned long old_addr,
17611771
unsigned long new_addr, pmd_t *old_pmd, pmd_t *new_pmd)
17621772
{
@@ -1795,6 +1805,8 @@ bool move_huge_pmd(struct vm_area_struct *vma, unsigned long old_addr,
17951805
pgtable_trans_huge_deposit(mm, new_pmd, pgtable);
17961806
}
17971807
pmd = move_soft_dirty_pmd(pmd);
1808+
if (vma_has_uffd_without_event_remap(vma))
1809+
pmd = clear_uffd_wp_pmd(pmd);
17981810
set_pmd_at(mm, new_addr, new_pmd, pmd);
17991811
if (force_flush)
18001812
flush_pmd_tlb_range(vma, old_addr, old_addr + PMD_SIZE);

mm/hugetlb.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5256,6 +5256,7 @@ static void move_huge_pte(struct vm_area_struct *vma, unsigned long old_addr,
52565256
unsigned long new_addr, pte_t *src_pte, pte_t *dst_pte,
52575257
unsigned long sz)
52585258
{
5259+
bool need_clear_uffd_wp = vma_has_uffd_without_event_remap(vma);
52595260
struct hstate *h = hstate_vma(vma);
52605261
struct mm_struct *mm = vma->vm_mm;
52615262
spinlock_t *src_ptl, *dst_ptl;
@@ -5272,7 +5273,18 @@ static void move_huge_pte(struct vm_area_struct *vma, unsigned long old_addr,
52725273
spin_lock_nested(src_ptl, SINGLE_DEPTH_NESTING);
52735274

52745275
pte = huge_ptep_get_and_clear(mm, old_addr, src_pte);
5275-
set_huge_pte_at(mm, new_addr, dst_pte, pte, sz);
5276+
5277+
if (need_clear_uffd_wp && pte_marker_uffd_wp(pte))
5278+
huge_pte_clear(mm, new_addr, dst_pte, sz);
5279+
else {
5280+
if (need_clear_uffd_wp) {
5281+
if (pte_present(pte))
5282+
pte = huge_pte_clear_uffd_wp(pte);
5283+
else if (is_swap_pte(pte))
5284+
pte = pte_swp_clear_uffd_wp(pte);
5285+
}
5286+
set_huge_pte_at(mm, new_addr, dst_pte, pte, sz);
5287+
}
52765288

52775289
if (src_ptl != dst_ptl)
52785290
spin_unlock(src_ptl);

mm/mremap.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ static int move_ptes(struct vm_area_struct *vma, pmd_t *old_pmd,
138138
struct vm_area_struct *new_vma, pmd_t *new_pmd,
139139
unsigned long new_addr, bool need_rmap_locks)
140140
{
141+
bool need_clear_uffd_wp = vma_has_uffd_without_event_remap(vma);
141142
struct mm_struct *mm = vma->vm_mm;
142143
pte_t *old_pte, *new_pte, pte;
143144
spinlock_t *old_ptl, *new_ptl;
@@ -207,7 +208,18 @@ static int move_ptes(struct vm_area_struct *vma, pmd_t *old_pmd,
207208
force_flush = true;
208209
pte = move_pte(pte, old_addr, new_addr);
209210
pte = move_soft_dirty_pte(pte);
210-
set_pte_at(mm, new_addr, new_pte, pte);
211+
212+
if (need_clear_uffd_wp && pte_marker_uffd_wp(pte))
213+
pte_clear(mm, new_addr, new_pte);
214+
else {
215+
if (need_clear_uffd_wp) {
216+
if (pte_present(pte))
217+
pte = pte_clear_uffd_wp(pte);
218+
else if (is_swap_pte(pte))
219+
pte = pte_swp_clear_uffd_wp(pte);
220+
}
221+
set_pte_at(mm, new_addr, new_pte, pte);
222+
}
211223
}
212224

213225
arch_leave_lazy_mmu_mode();
@@ -269,6 +281,15 @@ static bool move_normal_pmd(struct vm_area_struct *vma, unsigned long old_addr,
269281
if (WARN_ON_ONCE(!pmd_none(*new_pmd)))
270282
return false;
271283

284+
/* If this pmd belongs to a uffd vma with remap events disabled, we need
285+
* to ensure that the uffd-wp state is cleared from all pgtables. This
286+
* means recursing into lower page tables in move_page_tables(), and we
287+
* can reuse the existing code if we simply treat the entry as "not
288+
* moved".
289+
*/
290+
if (vma_has_uffd_without_event_remap(vma))
291+
return false;
292+
272293
/*
273294
* We don't have to worry about the ordering of src and dst
274295
* ptlocks because exclusive mmap_lock prevents deadlock.
@@ -324,6 +345,15 @@ static bool move_normal_pud(struct vm_area_struct *vma, unsigned long old_addr,
324345
if (WARN_ON_ONCE(!pud_none(*new_pud)))
325346
return false;
326347

348+
/* If this pud belongs to a uffd vma with remap events disabled, we need
349+
* to ensure that the uffd-wp state is cleared from all pgtables. This
350+
* means recursing into lower page tables in move_page_tables(), and we
351+
* can reuse the existing code if we simply treat the entry as "not
352+
* moved".
353+
*/
354+
if (vma_has_uffd_without_event_remap(vma))
355+
return false;
356+
327357
/*
328358
* We don't have to worry about the ordering of src and dst
329359
* ptlocks because exclusive mmap_lock prevents deadlock.

0 commit comments

Comments
 (0)