Skip to content

Commit f369b07

Browse files
xzpeterakpm00
authored andcommitted
mm/uffd: reset write protection when unregister with wp-mode
The motivation of this patch comes from a recent report and patchfix from David Hildenbrand on hugetlb shared handling of wr-protected page [1]. With the reproducer provided in commit message of [1], one can leverage the uffd-wp lazy-reset of ptes to trigger a hugetlb issue which can affect not only the attacker process, but also the whole system. The lazy-reset mechanism of uffd-wp was used to make unregister faster, meanwhile it has an assumption that any leftover pgtable entries should only affect the process on its own, so not only the user should be aware of anything it does, but also it should not affect outside of the process. But it seems that this is not true, and it can also be utilized to make some exploit easier. So far there's no clue showing that the lazy-reset is important to any userfaultfd users because normally the unregister will only happen once for a specific range of memory of the lifecycle of the process. Considering all above, what this patch proposes is to do explicit pte resets when unregister an uffd region with wr-protect mode enabled. It should be the same as calling ioctl(UFFDIO_WRITEPROTECT, wp=false) right before ioctl(UFFDIO_UNREGISTER) for the user. So potentially it'll make the unregister slower. From that pov it's a very slight abi change, but hopefully nothing should break with this change either. Regarding to the change itself - core of uffd write [un]protect operation is moved into a separate function (uffd_wp_range()) and it is reused in the unregister code path. Note that the new function will not check for anything, e.g. ranges or memory types, because they should have been checked during the previous UFFDIO_REGISTER or it should have failed already. It also doesn't check mmap_changing because we're with mmap write lock held anyway. I added a Fixes upon introducing of uffd-wp shmem+hugetlbfs because that's the only issue reported so far and that's the commit David's reproducer will start working (v5.19+). But the whole idea actually applies to not only file memories but also anonymous. It's just that we don't need to fix anonymous prior to v5.19- because there's no known way to exploit. IOW, this patch can also fix the issue reported in [1] as the patch 2 does. [1] https://lore.kernel.org/all/20220811103435.188481-3-david@redhat.com/ Link: https://lkml.kernel.org/r/20220811201340.39342-1-peterx@redhat.com Fixes: b1f9e87 ("mm/uffd: enable write protection for shmem & hugetlbfs") Signed-off-by: Peter Xu <peterx@redhat.com> Cc: David Hildenbrand <david@redhat.com> Cc: Mike Rapoport <rppt@linux.vnet.ibm.com> Cc: Mike Kravetz <mike.kravetz@oracle.com> Cc: Andrea Arcangeli <aarcange@redhat.com> Cc: Nadav Amit <nadav.amit@gmail.com> Cc: Axel Rasmussen <axelrasmussen@google.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
1 parent efd4149 commit f369b07

File tree

3 files changed

+24
-11
lines changed

3 files changed

+24
-11
lines changed

fs/userfaultfd.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,10 @@ static int userfaultfd_unregister(struct userfaultfd_ctx *ctx,
16011601
wake_userfault(vma->vm_userfaultfd_ctx.ctx, &range);
16021602
}
16031603

1604+
/* Reset ptes for the whole vma range if wr-protected */
1605+
if (userfaultfd_wp(vma))
1606+
uffd_wp_range(mm, vma, start, vma_end - start, false);
1607+
16041608
new_flags = vma->vm_flags & ~__VM_UFFD_FLAGS;
16051609
prev = vma_merge(mm, prev, start, vma_end, new_flags,
16061610
vma->anon_vma, vma->vm_file, vma->vm_pgoff,

include/linux/userfaultfd_k.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ extern ssize_t mcopy_continue(struct mm_struct *dst_mm, unsigned long dst_start,
7373
extern int mwriteprotect_range(struct mm_struct *dst_mm,
7474
unsigned long start, unsigned long len,
7575
bool enable_wp, atomic_t *mmap_changing);
76+
extern void uffd_wp_range(struct mm_struct *dst_mm, struct vm_area_struct *vma,
77+
unsigned long start, unsigned long len, bool enable_wp);
7678

7779
/* mm helpers */
7880
static inline bool is_mergeable_vm_userfaultfd_ctx(struct vm_area_struct *vma,

mm/userfaultfd.c

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -703,14 +703,29 @@ ssize_t mcopy_continue(struct mm_struct *dst_mm, unsigned long start,
703703
mmap_changing, 0);
704704
}
705705

706+
void uffd_wp_range(struct mm_struct *dst_mm, struct vm_area_struct *dst_vma,
707+
unsigned long start, unsigned long len, bool enable_wp)
708+
{
709+
struct mmu_gather tlb;
710+
pgprot_t newprot;
711+
712+
if (enable_wp)
713+
newprot = vm_get_page_prot(dst_vma->vm_flags & ~(VM_WRITE));
714+
else
715+
newprot = vm_get_page_prot(dst_vma->vm_flags);
716+
717+
tlb_gather_mmu(&tlb, dst_mm);
718+
change_protection(&tlb, dst_vma, start, start + len, newprot,
719+
enable_wp ? MM_CP_UFFD_WP : MM_CP_UFFD_WP_RESOLVE);
720+
tlb_finish_mmu(&tlb);
721+
}
722+
706723
int mwriteprotect_range(struct mm_struct *dst_mm, unsigned long start,
707724
unsigned long len, bool enable_wp,
708725
atomic_t *mmap_changing)
709726
{
710727
struct vm_area_struct *dst_vma;
711728
unsigned long page_mask;
712-
struct mmu_gather tlb;
713-
pgprot_t newprot;
714729
int err;
715730

716731
/*
@@ -750,15 +765,7 @@ int mwriteprotect_range(struct mm_struct *dst_mm, unsigned long start,
750765
goto out_unlock;
751766
}
752767

753-
if (enable_wp)
754-
newprot = vm_get_page_prot(dst_vma->vm_flags & ~(VM_WRITE));
755-
else
756-
newprot = vm_get_page_prot(dst_vma->vm_flags);
757-
758-
tlb_gather_mmu(&tlb, dst_mm);
759-
change_protection(&tlb, dst_vma, start, start + len, newprot,
760-
enable_wp ? MM_CP_UFFD_WP : MM_CP_UFFD_WP_RESOLVE);
761-
tlb_finish_mmu(&tlb);
768+
uffd_wp_range(dst_mm, dst_vma, start, len, enable_wp);
762769

763770
err = 0;
764771
out_unlock:

0 commit comments

Comments
 (0)