Re: [PATCH 04/10] mm/hugetlb: unmap the page where the other VMA maps it
From: Muchun Song
Date: Fri Oct 16 2026 - 05:50:39 EST
> On Oct 16, 2026, at 00:07, David Hildenbrand (Arm) <david@xxxxxxxxxx> wrote:
>
> From: Josef Bacik <josef@xxxxxxxxxxxxxx>
>
> If the owner of a MAP_PRIVATE hugetlb mapping fails to allocate a page
> for CoW, unmap_ref_private() unmaps the old page from all other private
> VMAs that have no reservation of their own. It finds them by the offset
> in the file, but then zaps each of them at the address of the faulting
> VMA. Nothing says that another VMA maps the offset at that address: a
> child can have moved its copy of the VMA with mremap(), and a second
> mapping of the file is wherever mmap() has put it.
>
> So __unmap_hugepage_range() walks the page tables of the other mm at an
> address outside of iter_vma, where i_mmap_rwsem of this file protects
> nothing and where anything can be mapped, and the page stays mapped where
> it really is.
>
> All of the following was seen on v7.3-rc6+ (g6c377d19d4a5) in KVM guests,
> as a user without any privilege. It takes a pool of 2 MiB pages with no
> free page that is not reserved, so that the allocation fails: the user
> can take or reserve what is free itself.
>
> - The child moves its VMA away. The page is never unmapped, the
> mapcount stays 2, and the owner goes round retry_avoidcopy in
> hugetlb_wp() until the child is gone or a page becomes free. SIGKILL
> to the owner does not end it. 5 of 5 runs.
>
> - The owner's mm has a second private mapping of the offset, made with
> MAP_NORESERVE. The owner zaps its own entry and reads zeroes where
> its data was. 5 of 5 runs.
>
> - The child moves its VMA away and maps a 1 GiB hugetlb page at the old
> address, so this one takes a pool with a 1 GiB page as well.
> huge_pte_offset() for the 2 MiB size steps through the PUD
> leaf, so the "PMD table" is the 1 GiB page itself and its "page table
> lock" is folio->private of that page, which holds 2. Kernels with
> lock debugging oops in __unmap_hugepage_range(), 7 of 7 runs. Others
> spin on that word for ever with preemption disabled, 5 of 5 runs:
>
> watchdog: BUG: soft lockup - CPU#0 stuck for 26s! [u_det3:130]
> RIP: 0010:queued_spin_lock_slowpath+0x8a/0x290
> RAX: 0000000000000002 RBX: ffffea0001000028
> Call Trace:
> __unmap_hugepage_range+0x154/0x6d0
> unmap_hugepage_range+0xc1/0x140
> hugetlb_wp+0x55c/0x700
> hugetlb_fault+0x5f3/0x8f0
>
> RCU stalls follow, and killing all tasks of the user changes nothing.
> Four such owners, whose children share a single 1 GiB page, took down
> the whole guest with its four CPUs, 2 of 2 runs: one reported soft
> lockups on all four, the other gave no sign of life any more.
>
> i_mmap_rwsem stays write locked. With a MAP_PRIVATE mapping of a
> hugetlbfs file that belongs to another user, and that the attacker
> can only read, the next mmap() of that file by its owner sleeps
> uninterruptibly in __mmap_region(), 4 of 4 runs.
>
> Compute the address from the offset for each VMA. The tree walk returns
> only VMAs that cover pgoff, and vm_start, vm_end and vm_pgoff of a hugetlb
> VMA are aligned to the huge page size, as pgoff is, so the whole range
> lies inside iter_vma. Its page tables can not go away there, since the
> VMA is unlinked from the file under i_mmap_rwsem first.
>
> With the right address the page is unmapped from all VMAs but the
> owner's, so the retry in hugetlb_wp() finds a mapcount of 1 and reuses the
> page.
>
> A child that has moved its copy of the VMA now loses the page, and gets
> SIGBUS when it faults on a missing page of that VMA. That is what the
> comment above the function has always promised to those who do not own
> the reservation, and what already happens to a child that has left the
> VMA in place.
>
> The address has been wrong from the start, and the second case needs
> only mmap() and fork(). mremap() of hugetlb VMAs, which the other cases
> use, came with commit 550a7d60bd5e ("mm, hugepages: add mremap() support
> for hugepage backed vma") in v5.16.
>
> Original fix generated by an LLM and sent in private by Josef.
>
> Fixes: 04f2cbe35699 ("hugetlb: guarantee that COW faults for a process that called mmap(MAP_PRIVATE) on hugetlbfs will succeed")
> Reported-by: Chris Mason <mason@xxxxxxxxxx>
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
> [ david: switch to vma_filebacked_address, add VM_WARN_ON_ONCE, refine comments]
> Reviewed-by: Lorenzo Stoakes (ARM) <ljs@xxxxxxxxxx>
> Signed-off-by: David Hildenbrand (Arm) <david@xxxxxxxxxx>
Acked-by: Muchun Song <muchun.song@xxxxxxxxx>
Thanks.