* [PATCH] mm/hugetlb_vmemmap: map the shared tail page writable
@ 2026-10-28 13:26 Kiryl Shutsemau
2026-10-28 13:33 ` Muchun Song
2026-10-28 14:39 ` Oscar Salvador (SUSE)
0 siblings, 2 replies; 3+ messages in thread
From: Kiryl Shutsemau @ 2026-10-28 13:26 UTC (permalink / raw)
To: Andrew Morton, Muchun Song, Oscar Salvador, David Hildenbrand
Cc: Kiryl Shutsemau (Meta),
Peter Zijlstra, Frank van der Linden, Matthew Wilcox,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
Randy Dunlap, kernel-team, linux-mm, linux-doc, linux-kernel,
stable
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
page_ref_add_unless_zero() reads the refcount and then updates it with
a cmpxchg. A lockless get, such as a page cache lookup or a PFN
scanner, can read a non-zero refcount from a page that is then freed
and reused as a hugetlb tail page. If HVO remaps its struct page to
the shared tail page in between, the cmpxchg writes to read-only
vmemmap:
CPU0 (lockless get) CPU1
c = atomic_read(&page->_refcount)
page freed
page allocated as a hugetlb tail
HVO remaps the struct page RO
try_cmpxchg(&page->_refcount, &c, c + 1)
-> write fault on read-only vmemmap
The refcount is zero by then and the compare fails, but a
compare-and-swap instruction needs write access whether or not it
succeeds. The kernel oopses when hugetlb pages are allocated at
runtime with HVO enabled while a lockless get races with it.
Commit bd225530a4c7 ("mm/hugetlb_vmemmap: fix race with speculative PFN
walkers") closed the window with RCU on both sides. The RCU was removed
together with fake heads, on the wrong belief that it was only needed
for them.
Map the shared tail page writable instead of restoring the RCU. Every
struct page on it is a tail with a zero refcount, so a late cmpxchg
fails and leaves the page unchanged. RCU would put rcu_read_lock()
back around every get and synchronize_rcu() back into hugetlb
allocation and freeing.
The cost is the protection against stray writes to tail struct pages.
The shared page is shared by all hugetlb folios of the same order in
the zone, so such a write now corrupts all of them instead of oopsing.
Bootmem folios optimized when the memmap is set up keep their tails
read-only. Nothing does a lockless get on them before hugetlb takes
them over.
Fixes: 32c440d67e6c ("mm: drop fake head checks")
Fixes: 01b1d0ffb6f7 ("hugetlb: remove VMEMMAP_SYNCHRONIZE_RCU")
Reported-by: David Hildenbrand (Arm) <david@kernel.org>
Closes: https://lore.kernel.org/r/ebc7bde1-f52e-4da8-b16f-822d6c0a4d74@kernel.org
Cc: stable@vger.kernel.org
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
Changes since the draft posted inline in the report thread [1]:
- keep the pre-HVO write-protect for bootmem folios (Muchun);
- describe the fault in arch-agnostic terms (Muchun).
[1] https://lore.kernel.org/r/auDkbDlgSZxqlk2G@thinkstation
Documentation/mm/vmemmap_dedup.rst | 15 +++++++++++++--
mm/hugetlb_vmemmap.c | 8 +++++---
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/Documentation/mm/vmemmap_dedup.rst b/Documentation/mm/vmemmap_dedup.rst
index 9fa8642ded48..66d58fbea5b1 100644
--- a/Documentation/mm/vmemmap_dedup.rst
+++ b/Documentation/mm/vmemmap_dedup.rst
@@ -131,8 +131,19 @@ pages of ``struct page`` (page 1 to page 7) are tail pages.
The optimization is only applied when the size of the struct page is a power
of 2. In this case, all tail pages of the same order are identical. See
compound_head(). This allows us to remap the tail pages of the vmemmap to a
-shared, read-only page. The head page is also remapped to a new page. This
-allows the original vmemmap pages to be freed.
+shared page. The head page is also remapped to a new page. This allows the
+original vmemmap pages to be freed.
+
+The shared page is mapped writable. A lockless get of a page reference can
+read a non-zero refcount before the remap and attempt its atomic update
+after it. The refcount of a tail page is zero, so the update fails without
+changing the page. Nothing else may write to the shared page: it is shared
+by all HugeTLB pages of the same size in the zone, and a stray write would
+corrupt all of them.
+
+HugeTLB pages whose vmemmap is optimized when the memmap is first set up at
+boot keep a read-only mapping of the shared page. Nothing can do a lockless
+get on them before they are handed to HugeTLB.
Here is how things look after remapping::
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 917db0984143..8e5981888937 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -222,10 +222,12 @@ static void vmemmap_remap_pte(pte_t *pte, unsigned long addr,
VM_WARN_ON_ONCE(!PageTail((const struct page *)addr));
/*
- * Remap the tail pages as read-only to catch illegal write
- * operation to the tail pages.
+ * Map the shared tail page writable. A lockless get that read
+ * a non-zero refcount before the remap can still do its
+ * cmpxchg after it. The refcount is zero, so the cmpxchg
+ * fails and leaves the page unchanged.
*/
- entry = mk_pte(walk->vmemmap_tail, PAGE_KERNEL_RO);
+ entry = mk_pte(walk->vmemmap_tail, PAGE_KERNEL);
}
list_add(&page->lru, walk->vmemmap_pages);
base-commit: 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e
--
2.54.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] mm/hugetlb_vmemmap: map the shared tail page writable
2026-10-28 13:26 [PATCH] mm/hugetlb_vmemmap: map the shared tail page writable Kiryl Shutsemau
@ 2026-10-28 13:33 ` Muchun Song
2026-10-28 14:39 ` Oscar Salvador (SUSE)
1 sibling, 0 replies; 3+ messages in thread
From: Muchun Song @ 2026-10-28 13:33 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: Andrew Morton, Oscar Salvador, David Hildenbrand,
Kiryl Shutsemau (Meta),
Peter Zijlstra, Frank van der Linden, Matthew Wilcox,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
Randy Dunlap, kernel-team, linux-mm, linux-doc, linux-kernel,
stable
> On Oct 28, 2026, at 21:26, Kiryl Shutsemau <kirill@shutemov.name> wrote:
>
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
>
> page_ref_add_unless_zero() reads the refcount and then updates it with
> a cmpxchg. A lockless get, such as a page cache lookup or a PFN
> scanner, can read a non-zero refcount from a page that is then freed
> and reused as a hugetlb tail page. If HVO remaps its struct page to
> the shared tail page in between, the cmpxchg writes to read-only
> vmemmap:
>
> CPU0 (lockless get) CPU1
> c = atomic_read(&page->_refcount)
> page freed
> page allocated as a hugetlb tail
> HVO remaps the struct page RO
> try_cmpxchg(&page->_refcount, &c, c + 1)
> -> write fault on read-only vmemmap
>
> The refcount is zero by then and the compare fails, but a
> compare-and-swap instruction needs write access whether or not it
> succeeds. The kernel oopses when hugetlb pages are allocated at
> runtime with HVO enabled while a lockless get races with it.
>
> Commit bd225530a4c7 ("mm/hugetlb_vmemmap: fix race with speculative PFN
> walkers") closed the window with RCU on both sides. The RCU was removed
> together with fake heads, on the wrong belief that it was only needed
> for them.
>
> Map the shared tail page writable instead of restoring the RCU. Every
> struct page on it is a tail with a zero refcount, so a late cmpxchg
> fails and leaves the page unchanged. RCU would put rcu_read_lock()
> back around every get and synchronize_rcu() back into hugetlb
> allocation and freeing.
>
> The cost is the protection against stray writes to tail struct pages.
> The shared page is shared by all hugetlb folios of the same order in
> the zone, so such a write now corrupts all of them instead of oopsing.
>
> Bootmem folios optimized when the memmap is set up keep their tails
> read-only. Nothing does a lockless get on them before hugetlb takes
> them over.
>
> Fixes: 32c440d67e6c ("mm: drop fake head checks")
> Fixes: 01b1d0ffb6f7 ("hugetlb: remove VMEMMAP_SYNCHRONIZE_RCU")
> Reported-by: David Hildenbrand (Arm) <david@kernel.org>
> Closes: https://lore.kernel.org/r/ebc7bde1-f52e-4da8-b16f-822d6c0a4d74@kernel.org
> Cc: stable@vger.kernel.org
> Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
LGTM.
Acked-by: Muchun Song <muchun.song@linux.dev>
Thanks.
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] mm/hugetlb_vmemmap: map the shared tail page writable
2026-10-28 13:26 [PATCH] mm/hugetlb_vmemmap: map the shared tail page writable Kiryl Shutsemau
2026-10-28 13:33 ` Muchun Song
@ 2026-10-28 14:39 ` Oscar Salvador (SUSE)
1 sibling, 0 replies; 3+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-10-28 14:39 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: Andrew Morton, Muchun Song, Oscar Salvador, David Hildenbrand,
Kiryl Shutsemau (Meta),
Peter Zijlstra, Frank van der Linden, Matthew Wilcox,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
Randy Dunlap, kernel-team, linux-mm, linux-doc, linux-kernel,
stable
On Wed, Oct 28, 2026 at 01:26:21PM +0000, Kiryl Shutsemau wrote:
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
>
> page_ref_add_unless_zero() reads the refcount and then updates it with
> a cmpxchg. A lockless get, such as a page cache lookup or a PFN
> scanner, can read a non-zero refcount from a page that is then freed
> and reused as a hugetlb tail page. If HVO remaps its struct page to
> the shared tail page in between, the cmpxchg writes to read-only
> vmemmap:
>
> CPU0 (lockless get) CPU1
> c = atomic_read(&page->_refcount)
> page freed
> page allocated as a hugetlb tail
> HVO remaps the struct page RO
> try_cmpxchg(&page->_refcount, &c, c + 1)
> -> write fault on read-only vmemmap
>
> The refcount is zero by then and the compare fails, but a
> compare-and-swap instruction needs write access whether or not it
> succeeds. The kernel oopses when hugetlb pages are allocated at
> runtime with HVO enabled while a lockless get races with it.
>
> Commit bd225530a4c7 ("mm/hugetlb_vmemmap: fix race with speculative PFN
> walkers") closed the window with RCU on both sides. The RCU was removed
> together with fake heads, on the wrong belief that it was only needed
> for them.
>
> Map the shared tail page writable instead of restoring the RCU. Every
> struct page on it is a tail with a zero refcount, so a late cmpxchg
> fails and leaves the page unchanged. RCU would put rcu_read_lock()
> back around every get and synchronize_rcu() back into hugetlb
> allocation and freeing.
>
> The cost is the protection against stray writes to tail struct pages.
> The shared page is shared by all hugetlb folios of the same order in
> the zone, so such a write now corrupts all of them instead of oopsing.
>
> Bootmem folios optimized when the memmap is set up keep their tails
> read-only. Nothing does a lockless get on them before hugetlb takes
> them over.
>
> Fixes: 32c440d67e6c ("mm: drop fake head checks")
> Fixes: 01b1d0ffb6f7 ("hugetlb: remove VMEMMAP_SYNCHRONIZE_RCU")
> Reported-by: David Hildenbrand (Arm) <david@kernel.org>
> Closes: https://lore.kernel.org/r/ebc7bde1-f52e-4da8-b16f-822d6c0a4d74@kernel.org
> Cc: stable@vger.kernel.org
> Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Oscar Salvador <osalvador@suse.de>
--
Oscar Salvador
SUSE Labs
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-28 14:39 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-28 13:26 [PATCH] mm/hugetlb_vmemmap: map the shared tail page writable Kiryl Shutsemau
2026-10-28 13:33 ` Muchun Song
2026-10-28 14:39 ` Oscar Salvador (SUSE)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®