Skip to content

Commit bd9a23a

Browse files
48cagregkh
authored andcommitted
hugetlb: unshare some PMDs when splitting VMAs
[ Upstream commit b30c14c ] PMD sharing can only be done in PUD_SIZE-aligned pieces of VMAs; however, it is possible that HugeTLB VMAs are split without unsharing the PMDs first. Without this fix, it is possible to hit the uffd-wp-related WARN_ON_ONCE in hugetlb_change_protection [1]. The key there is that hugetlb_unshare_all_pmds will not attempt to unshare PMDs in non-PUD_SIZE-aligned sections of the VMA. It might seem ideal to unshare in hugetlb_vm_op_open, but we need to unshare in both the new and old VMAs, so unsharing in hugetlb_vm_op_split seems natural. [1]: https://lore.kernel.org/linux-mm/CADrL8HVeOkj0QH5VZZbRzybNE8CG-tEGFshnA+bG9nMgcWtBSg@mail.gmail.com/ Link: https://lkml.kernel.org/r/20230104231910.1464197-1-jthoughton@google.com Fixes: 6dfeaff ("hugetlb/userfaultfd: unshare all pmds for hugetlbfs when register wp") Signed-off-by: James Houghton <jthoughton@google.com> Reviewed-by: Mike Kravetz <mike.kravetz@oracle.com> Acked-by: Peter Xu <peterx@redhat.com> Cc: Axel Rasmussen <axelrasmussen@google.com> Cc: Muchun Song <songmuchun@bytedance.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent 393d9e3 commit bd9a23a

File tree

1 file changed

+35
-9
lines changed

1 file changed

+35
-9
lines changed

mm/hugetlb.c

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ struct mutex *hugetlb_fault_mutex_table ____cacheline_aligned_in_smp;
8282

8383
/* Forward declaration */
8484
static int hugetlb_acct_memory(struct hstate *h, long delta);
85+
static void hugetlb_unshare_pmds(struct vm_area_struct *vma,
86+
unsigned long start, unsigned long end);
8587

8688
static inline bool subpool_is_free(struct hugepage_subpool *spool)
8789
{
@@ -4164,6 +4166,25 @@ static int hugetlb_vm_op_split(struct vm_area_struct *vma, unsigned long addr)
41644166
{
41654167
if (addr & ~(huge_page_mask(hstate_vma(vma))))
41664168
return -EINVAL;
4169+
4170+
/*
4171+
* PMD sharing is only possible for PUD_SIZE-aligned address ranges
4172+
* in HugeTLB VMAs. If we will lose PUD_SIZE alignment due to this
4173+
* split, unshare PMDs in the PUD_SIZE interval surrounding addr now.
4174+
*/
4175+
if (addr & ~PUD_MASK) {
4176+
/*
4177+
* hugetlb_vm_op_split is called right before we attempt to
4178+
* split the VMA. We will need to unshare PMDs in the old and
4179+
* new VMAs, so let's unshare before we split.
4180+
*/
4181+
unsigned long floor = addr & PUD_MASK;
4182+
unsigned long ceil = floor + PUD_SIZE;
4183+
4184+
if (floor >= vma->vm_start && ceil <= vma->vm_end)
4185+
hugetlb_unshare_pmds(vma, floor, ceil);
4186+
}
4187+
41674188
return 0;
41684189
}
41694190

@@ -6349,26 +6370,21 @@ void move_hugetlb_state(struct page *oldpage, struct page *newpage, int reason)
63496370
}
63506371
}
63516372

6352-
/*
6353-
* This function will unconditionally remove all the shared pmd pgtable entries
6354-
* within the specific vma for a hugetlbfs memory range.
6355-
*/
6356-
void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
6373+
static void hugetlb_unshare_pmds(struct vm_area_struct *vma,
6374+
unsigned long start,
6375+
unsigned long end)
63576376
{
63586377
struct hstate *h = hstate_vma(vma);
63596378
unsigned long sz = huge_page_size(h);
63606379
struct mm_struct *mm = vma->vm_mm;
63616380
struct mmu_notifier_range range;
6362-
unsigned long address, start, end;
6381+
unsigned long address;
63636382
spinlock_t *ptl;
63646383
pte_t *ptep;
63656384

63666385
if (!(vma->vm_flags & VM_MAYSHARE))
63676386
return;
63686387

6369-
start = ALIGN(vma->vm_start, PUD_SIZE);
6370-
end = ALIGN_DOWN(vma->vm_end, PUD_SIZE);
6371-
63726388
if (start >= end)
63736389
return;
63746390

@@ -6400,6 +6416,16 @@ void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
64006416
mmu_notifier_invalidate_range_end(&range);
64016417
}
64026418

6419+
/*
6420+
* This function will unconditionally remove all the shared pmd pgtable entries
6421+
* within the specific vma for a hugetlbfs memory range.
6422+
*/
6423+
void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
6424+
{
6425+
hugetlb_unshare_pmds(vma, ALIGN(vma->vm_start, PUD_SIZE),
6426+
ALIGN_DOWN(vma->vm_end, PUD_SIZE));
6427+
}
6428+
64036429
#ifdef CONFIG_CMA
64046430
static bool cma_reserve_called __initdata;
64056431

0 commit comments

Comments
 (0)