From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794371023; b=YgdJ1Kvy3cd9D8Sr0DIuF1WrcbHw/IiVxPkJop7tg1lJLrkamV3unA7vV0625GxXSgpa FjAxkx1lpFCY3Ad0cqzzv02SJKxcJEB1Ss9lBg9oA8cgjVcqH7B4wbUmbFM7RdyfYlGwx YvoNe7OcO4YSBBaykbsAYcb6hEXw9VNfjpH7xRRbnMN2L2LnxjODRn2dyWxSYyOodGian bZSROGEokg4aEf95qRJ436YDuOvG8M4c00N46JoQHb9iOVBHMchDpzOMhjTwvRTZchjuN GR2tJ/mayBWSb/lfpFVObVeUoDwXs70kwX9DVIj1SpZmBEwIu4Erfv63KkqmNiMtAOQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794371023; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:Content-Transfer-Encoding; bh=1G2AFGkb97svQTPsfQd9hdrssJP61oSOocnoxx+eL4Y=; b=r5Ou9QLCnRZTLkgAyIhmio9byJRAyt1fT+F4A+gxoszjfp6QCX8meOwHW2Wxza/izQqW xoKqKHBmM8NXulFkP+cTLAW55MSSjBbyM5Jx8t1Okf4EnDJ4TSRcnF/85ZozmOqmvtmtE XSzOLNXQ7ib/t6I0Nc4Q4BE9m4akM9ZktLrfjN92nRYywqMqQPUkryF5h3msKZwULz1xs BzhNE8XkXp3261qkEfZBoLr0TL2ELSrqZIxTkvlAZhgJXt/pxql86Ip6sOJbfpF8fPZR0 efBa2oFW8SHQGvAQ2+PIMT3xBHA3OQYgd6IsiCETQvIoVBy5l6OZtCBldUQGeVe3fkQ== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b="d469K3/T"; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 10EAD606A31C for ; Wed, 11 Nov 2026 04:23:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=d469K3/T Received: by smtp.kernel.org (Postfix) with ESMTPSA id 83B501F0093C; Wed, 11 Nov 2026 04:23:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1794371022; bh=1G2AFGkb97svQTPsfQd9hdrssJP61oSOocnoxx+eL4Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=d469K3/T2Bdb5gdoHZoRfGvDy3qbUpJb8m5vCODLOR5UPfObIN5QUisOBFrPI3RUP k2bGg73bFggqbwPVXNV0mBfqndIcwI7psJWGfCzZSprmXb3cfBJ4dE1Epa0Eed4VJ3 iDiMp0bKaXFMWbsTJR8WT30aQjXEjAwgqgAej4dnHVzB28ScvMTEU3YC2s3Iw0G3eu 6HwKaO5KNRR+vmchbBRvppeENBxqFGvEw9F6EHDgmYlUp23jBUr7FhJZY6WvwEKs30 7yw2Dxh8ousySmWpEsbmQtpmXKDHkUhNa3AZwpZdMAWZE4FK0orOLnfAHUQXt2w+AP mt25JmjhrSLjA== From: Sasha Levin To: stable@vger.kernel.org Cc: Josef Bacik , "David Hildenbrand (Arm)" , Andrew Morton , Chris Mason , "Lorenzo Stoakes (ARM)" , Muchun Song , Lance Yang , "Liam R. Howlett" , Vlastimil Babka , Jann Horn , Pedro Falcato , Oscar Salvador , Miaohe Lin , Naoya Horiguchi , Adam Litke , Mel Gorman , Rik van Riel , Peter Xu , Kiryl Shutsemau , Mike Rapoport , Sasha Levin Subject: [PATCH 6.6.y] mm/hugetlb: unmap the page where the other VMA maps it Date: Tue, 10 Nov 2026 23:23:39 -0500 Message-ID: <20261111042309.1075767-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20261107053841-failed-6.6-ae99c503eb36@kernel.org> References: <20261107053841-failed-6.6-ae99c503eb36@kernel.org> X-Mailing-List: stable@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Josef Bacik [ Upstream commit ae99c503eb362b42cd5d24d80bc6cba669acc4c2 ] 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. [david@kernel.org: switch to vma_filebacked_address, add VM_WARN_ON_ONCE, refine comments] Link: https://lore.kernel.org/20261015-urgent_fixes-v1-4-42796911043b@kernel.org Fixes: 04f2cbe35699 ("hugetlb: guarantee that COW faults for a process that called mmap(MAP_PRIVATE) on hugetlbfs will succeed") Signed-off-by: Josef Bacik Signed-off-by: David Hildenbrand (Arm) Signed-off-by: Andrew Morton Reported-by: Chris Mason Reviewed-by: Lorenzo Stoakes (ARM) Acked-by: Muchun Song Reviewed-by: Lance Yang Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Jann Horn Cc: Pedro Falcato Cc: Oscar Salvador Cc: Miaohe Lin Cc: Naoya Horiguchi Cc: Adam Litke Cc: Mel Gorman Cc: Rik van Riel Cc: Peter Xu Cc: Kiryl Shutsemau Cc: Mike Rapoport Cc: [ sashal: adapted unmap_ref_private() to the existing struct page interface and vma_pgoff_address() helper. ] Signed-off-by: Sasha Levin --- mm/hugetlb.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 34eedfec4ff8a..175b0726c4aa5 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -5524,6 +5524,8 @@ static void unmap_ref_private(struct mm_struct *mm, struct vm_area_struct *vma, struct address_space *mapping; pgoff_t pgoff; + VM_WARN_ON_ONCE(!folio_test_anon(page_folio(page))); + /* * vm_pgoff is in PAGE_SIZE units, hence the different calculation * from page cache lookup which is in HPAGE_SIZE units. @@ -5559,9 +5561,17 @@ static void unmap_ref_private(struct mm_struct *mm, struct vm_area_struct *vma, * could insert a zeroed page instead of the data existing * from the time of fork. This would look like data corruption */ - if (!is_vma_resv_set(iter_vma, HPAGE_RESV_OWNER)) - unmap_hugepage_range(iter_vma, address, - address + huge_page_size(h), page, 0); + if (is_vma_resv_set(iter_vma, HPAGE_RESV_OWNER)) + continue; + + /* + * The address in other VMAs can differ. Even though our old + * folio is anon, we always operate on the underlying hugetlb + * file. + */ + address = vma_pgoff_address(pgoff, 1, iter_vma); + unmap_hugepage_range(iter_vma, address, + address + huge_page_size(h), page, 0); } i_mmap_unlock_write(mapping); } -- 2.53.0