Skip to content

Commit b30c14c

Browse files
48caakpm00
authored andcommitted
hugetlb: unshare some PMDs when splitting VMAs
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/[email protected] Fixes: 6dfeaff ("hugetlb/userfaultfd: unshare all pmds for hugetlbfs when register wp") Signed-off-by: James Houghton <[email protected]> Reviewed-by: Mike Kravetz <[email protected]> Acked-by: Peter Xu <[email protected]> Cc: Axel Rasmussen <[email protected]> Cc: Muchun Song <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
1 parent a1193de commit b30c14c

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
@@ -94,6 +94,8 @@ static int hugetlb_acct_memory(struct hstate *h, long delta);
9494
static void hugetlb_vma_lock_free(struct vm_area_struct *vma);
9595
static void hugetlb_vma_lock_alloc(struct vm_area_struct *vma);
9696
static void __hugetlb_vma_unlock_write_free(struct vm_area_struct *vma);
97+
static void hugetlb_unshare_pmds(struct vm_area_struct *vma,
98+
unsigned long start, unsigned long end);
9799

98100
static inline bool subpool_is_free(struct hugepage_subpool *spool)
99101
{
@@ -4834,6 +4836,25 @@ static int hugetlb_vm_op_split(struct vm_area_struct *vma, unsigned long addr)
48344836
{
48354837
if (addr & ~(huge_page_mask(hstate_vma(vma))))
48364838
return -EINVAL;
4839+
4840+
/*
4841+
* PMD sharing is only possible for PUD_SIZE-aligned address ranges
4842+
* in HugeTLB VMAs. If we will lose PUD_SIZE alignment due to this
4843+
* split, unshare PMDs in the PUD_SIZE interval surrounding addr now.
4844+
*/
4845+
if (addr & ~PUD_MASK) {
4846+
/*
4847+
* hugetlb_vm_op_split is called right before we attempt to
4848+
* split the VMA. We will need to unshare PMDs in the old and
4849+
* new VMAs, so let's unshare before we split.
4850+
*/
4851+
unsigned long floor = addr & PUD_MASK;
4852+
unsigned long ceil = floor + PUD_SIZE;
4853+
4854+
if (floor >= vma->vm_start && ceil <= vma->vm_end)
4855+
hugetlb_unshare_pmds(vma, floor, ceil);
4856+
}
4857+
48374858
return 0;
48384859
}
48394860

@@ -7322,26 +7343,21 @@ void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio, int re
73227343
}
73237344
}
73247345

7325-
/*
7326-
* This function will unconditionally remove all the shared pmd pgtable entries
7327-
* within the specific vma for a hugetlbfs memory range.
7328-
*/
7329-
void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
7346+
static void hugetlb_unshare_pmds(struct vm_area_struct *vma,
7347+
unsigned long start,
7348+
unsigned long end)
73307349
{
73317350
struct hstate *h = hstate_vma(vma);
73327351
unsigned long sz = huge_page_size(h);
73337352
struct mm_struct *mm = vma->vm_mm;
73347353
struct mmu_notifier_range range;
7335-
unsigned long address, start, end;
7354+
unsigned long address;
73367355
spinlock_t *ptl;
73377356
pte_t *ptep;
73387357

73397358
if (!(vma->vm_flags & VM_MAYSHARE))
73407359
return;
73417360

7342-
start = ALIGN(vma->vm_start, PUD_SIZE);
7343-
end = ALIGN_DOWN(vma->vm_end, PUD_SIZE);
7344-
73457361
if (start >= end)
73467362
return;
73477363

@@ -7373,6 +7389,16 @@ void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
73737389
mmu_notifier_invalidate_range_end(&range);
73747390
}
73757391

7392+
/*
7393+
* This function will unconditionally remove all the shared pmd pgtable entries
7394+
* within the specific vma for a hugetlbfs memory range.
7395+
*/
7396+
void hugetlb_unshare_all_pmds(struct vm_area_struct *vma)
7397+
{
7398+
hugetlb_unshare_pmds(vma, ALIGN(vma->vm_start, PUD_SIZE),
7399+
ALIGN_DOWN(vma->vm_end, PUD_SIZE));
7400+
}
7401+
73767402
#ifdef CONFIG_CMA
73777403
static bool cma_reserve_called __initdata;
73787404

0 commit comments

Comments
 (0)