From: Muchun Song <muchun.song@linux.dev>
To: Kiryl Shutsemau <kas@kernel.org>
Cc: "David Hildenbrand (Arm)" <david@kernel.org>,
Peter Zijlstra <peterz@infradead.org>,
Andrew Morton <akpm@linux-foundation.org>,
Matthew Wilcox <willy@infradead.org>,
Usama Arif <usamaarif642@gmail.com>,
Frank van der Linden <fvdl@google.com>,
Oscar Salvador <osalvador@suse.de>,
Mike Rapoport <rppt@kernel.org>, Vlastimil Babka <vbabka@suse.cz>,
Lorenzo Stoakes <lorenzo.stoakes@oracle.com>,
Zi Yan <ziy@nvidia.com>, Baoquan He <bhe@redhat.com>,
Michal Hocko <mhocko@suse.com>,
Johannes Weiner <hannes@cmpxchg.org>,
Jonathan Corbet <corbet@lwn.net>,
Huacai Chen <chenhuacai@kernel.org>,
WANG Xuerui <kernel@xen0n.name>,
Palmer Dabbelt <palmer@dabbelt.com>,
Paul Walmsley <paul.walmsley@sifive.com>,
Albert Ou <aou@eecs.berkeley.edu>,
Alexandre Ghiti <alex@ghiti.fr>,
kernel-team@meta.com, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
loongarch@lists.linux.dev, linux-riscv@lists.infradead.org,
Dave Hansen <dave.hansen@linux.intel.com>,
Andy Lutomirski <luto@kernel.org>
Subject: Re: [PATCHv7 13/18] mm: Drop fake head checks
Date: Wed, 28 Oct 2026 10:38:22 +0800 [thread overview]
Message-ID: <7cb3dd91-f518-49b6-b311-9ec807b3c63b@linux.dev> (raw)
In-Reply-To: <auDkbDlgSZxqlk2G@thinkstation>
On 2026/10/28 01:51, Kiryl Shutsemau wrote:
> On Tue, Oct 27, 2026 at 10:03:54PM +0800, Muchun Song wrote:
>> Therefore, I am kind of leaning towards removing the RO protection
>> and restoring RW permissions to solve the race.
> Okay, what about this:
>
> From e23ab850837b53cc79d57d58f1cfb4266e745c3d Mon Sep 17 00:00:00 2001
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> Date: Tue, 27 Oct 2026 17:23:47 +0000
> Subject: [PATCH] mm/hugetlb_vmemmap: map the shared tail page writable
>
> 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 LOCK CMPXCHG on
> x86 writes anyway. The kernel oopses when hugetlb pages are allocated
Is this an X86-specific issue? If not, I suggest a more generic
description.
> 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 in early boot get their tails mapped writable
> and write protected later. Drop the write protect step.
For bootmem folios, I don't think the RO mapping should be removed,
because we are in the initialization stage of struct page.
No one should touch those pages at this point; otherwise, they could
encounter uninitialized struct page as well. This assumes no race
exists during early boot.
Since such a problem does not exist during the boot stage, why should
we give up the protection that RO provides?
Thoughts?
Thanks,
Muchun
>
> 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>
> ---
> Documentation/mm/vmemmap_dedup.rst | 13 +++++++++----
> include/linux/mm.h | 2 --
> mm/hugetlb.c | 4 ----
> mm/hugetlb_vmemmap.c | 21 +++++++--------------
> mm/sparse-vmemmap.c | 23 -----------------------
> 5 files changed, 16 insertions(+), 47 deletions(-)
>
> diff --git a/Documentation/mm/vmemmap_dedup.rst b/Documentation/mm/vmemmap_dedup.rst
> index 9fa8642ded48..6cd85aca4b96 100644
> --- a/Documentation/mm/vmemmap_dedup.rst
> +++ b/Documentation/mm/vmemmap_dedup.rst
> @@ -131,8 +131,15 @@ 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.
>
> Here is how things look after remapping::
>
> @@ -196,8 +203,6 @@ happens at a later stage when we populate the sections. HugeTLB reuses the
> the head vmemmap page representing, whereas device-dax reuses the tail
> vmemmap page. This results in only half of the savings compared to HugeTLB.
>
> -Deduplicated tail pages are not mapped read-only.
> -
> Here's how things look like on device-dax after the sections are populated::
>
> +-----------+ ---virt_to_page---> +-----------+ mapping to +-----------+
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index dd09c438fa23..bfaf4ce6d831 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -5162,8 +5162,6 @@ int vmemmap_populate(unsigned long start, unsigned long end, int node,
> int vmemmap_populate_hvo(unsigned long start, unsigned long end,
> unsigned int order, struct zone *zone,
> unsigned long headsize);
> -void vmemmap_wrprotect_hvo(unsigned long start, unsigned long end, int node,
> - unsigned long headsize);
> void vmemmap_populate_print_last(void);
> #ifdef CONFIG_MEMORY_HOTPLUG
> void vmemmap_free(unsigned long start, unsigned long end,
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index cea25773a6c9..7596704d94b5 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -3220,10 +3220,6 @@ static bool __init hugetlb_bootmem_page_earlycma(struct huge_bootmem_page *m)
> * if marked with the 'noinit' flag. Set it to the default (MIGRATE_MOVABLE)
> * here, or MIGRATE_CMA if this was a page allocated through an early CMA
> * reservation.
> - *
> - * In case of vmemmap optimized folios, the tail vmemmap pages are mapped
> - * read-only, but that's ok - for sparse vmemmap this does not write to
> - * the page structure.
> */
> static void __init hugetlb_bootmem_init_migratetype(struct folio *folio,
> struct hstate *h)
> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
> index 917db0984143..77f9d035d04c 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);
> @@ -629,19 +631,10 @@ static void __hugetlb_vmemmap_optimize_folios(struct hstate *h,
> nr_to_optimize = 0;
> list_for_each_entry(folio, folio_list, lru) {
> int ret;
> - unsigned long spfn, epfn;
>
> - if (boot && folio_test_hugetlb_vmemmap_optimized(folio)) {
> - /*
> - * Already optimized by pre-HVO, just map the
> - * mirrored tail page structs RO.
> - */
> - spfn = (unsigned long)&folio->page;
> - epfn = spfn + hugetlb_vmemmap_size(h);
> - vmemmap_wrprotect_hvo(spfn, epfn, folio_nid(folio),
> - HUGETLB_VMEMMAP_RESERVE_SIZE);
> + /* Already optimized by pre-HVO */
> + if (boot && folio_test_hugetlb_vmemmap_optimized(folio))
> continue;
> - }
>
> nr_to_optimize++;
>
> diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
> index 5a2469fb1838..3f25d4a0bf47 100644
> --- a/mm/sparse-vmemmap.c
> +++ b/mm/sparse-vmemmap.c
> @@ -299,29 +299,6 @@ int __meminit vmemmap_populate_basepages(unsigned long start, unsigned long end,
> return vmemmap_populate_range(start, end, node, altmap, -1, 0);
> }
>
> -/*
> - * Write protect the mirrored tail page structs for HVO. This will be
> - * called from the hugetlb code when gathering and initializing the
> - * memblock allocated gigantic pages. The write protect can't be
> - * done earlier, since it can't be guaranteed that the reserved
> - * page structures will not be written to during initialization,
> - * even if CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled.
> - *
> - * The PTEs are known to exist, and nothing else should be touching
> - * these pages. The caller is responsible for any TLB flushing.
> - */
> -void vmemmap_wrprotect_hvo(unsigned long addr, unsigned long end,
> - int node, unsigned long headsize)
> -{
> - unsigned long maddr;
> - pte_t *pte;
> -
> - for (maddr = addr + headsize; maddr < end; maddr += PAGE_SIZE) {
> - pte = virt_to_kpte(maddr);
> - ptep_set_wrprotect(&init_mm, maddr, pte);
> - }
> -}
> -
> #ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
> static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
> {
next prev parent reply other threads:[~2026-10-28 2:38 UTC|newest]
Thread overview: 61+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-27 19:42 [PATCHv7 RESEND 00/17] mm: Eliminate fake head pages from vmemmap optimization Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 01/18] mm: Move MAX_FOLIO_ORDER definition to mmzone.h Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 02/18] mm: Change the interface of prep_compound_tail() Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 03/18] mm: Rename the 'compound_head' field in the 'struct page' to 'compound_info' Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 04/18] mm: Move set/clear_compound_head() next to compound_head() Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 05/18] riscv/mm: Align vmemmap to maximal folio size Kiryl Shutsemau (Meta)
2026-03-16 16:30 ` David Hildenbrand (Arm)
2026-02-27 19:42 ` [PATCHv7 06/18] LoongArch/mm: " Kiryl Shutsemau (Meta)
2026-03-16 16:31 ` David Hildenbrand (Arm)
2026-02-27 19:42 ` [PATCHv7 07/18] mm: Rework compound_head() for power-of-2 sizeof(struct page) Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 08/18] mm/sparse: Check memmap alignment for compound_info_has_mask() Kiryl Shutsemau (Meta)
2026-03-16 16:33 ` David Hildenbrand (Arm)
2026-02-27 19:42 ` [PATCHv7 09/18] mm/hugetlb: Defer vmemmap population for bootmem hugepages Kiryl Shutsemau (Meta)
2026-03-16 16:48 ` David Hildenbrand (Arm)
2026-03-17 11:28 ` Kiryl Shutsemau
2026-03-17 12:47 ` David Hildenbrand (Arm)
2026-02-27 19:42 ` [PATCHv7 10/18] mm/hugetlb: Refactor code around vmemmap_walk Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 11/18] x86/vdso: Undefine CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP for vdso32 Kiryl Shutsemau (Meta)
2026-03-16 16:49 ` David Hildenbrand (Arm)
2026-02-27 19:42 ` [PATCHv7 12/18] mm/hugetlb: Remove fake head pages Kiryl Shutsemau (Meta)
2026-03-05 10:39 ` Vlastimil Babka (SUSE)
2026-03-16 17:01 ` David Hildenbrand (Arm)
2026-03-17 11:47 ` [PATCHv7.1 " Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 13/18] mm: Drop fake head checks Kiryl Shutsemau (Meta)
2026-10-26 17:45 ` David Hildenbrand (Arm)
2026-10-26 23:25 ` Kiryl Shutsemau
2026-10-26 23:37 ` David Hildenbrand (Arm)
2026-10-27 11:00 ` Peter Zijlstra
2026-10-27 12:00 ` Kiryl Shutsemau
2026-10-27 12:05 ` David Hildenbrand (Arm)
2026-10-27 12:31 ` Kiryl Shutsemau
2026-10-27 12:47 ` Peter Zijlstra
2026-10-27 12:49 ` Peter Zijlstra
2026-10-27 12:58 ` David Hildenbrand (Arm)
2026-10-27 13:13 ` Muchun Song
2026-10-27 13:35 ` David Hildenbrand (Arm)
2026-10-27 14:03 ` Muchun Song
2026-10-27 17:51 ` Kiryl Shutsemau
2026-10-27 20:16 ` David Hildenbrand (Arm)
2026-10-28 2:38 ` Muchun Song [this message]
2026-10-28 8:06 ` Peter Zijlstra
2026-10-28 12:58 ` Kiryl Shutsemau
2026-10-28 13:34 ` Peter Zijlstra
2026-10-28 14:16 ` Kiryl Shutsemau
2026-10-28 13:04 ` Kiryl Shutsemau
2026-10-27 12:13 ` Peter Zijlstra
2026-10-27 12:22 ` Kiryl Shutsemau
2026-10-27 12:29 ` Peter Zijlstra
2026-02-27 19:42 ` [PATCHv7 14/18] hugetlb: Remove VMEMMAP_SYNCHRONIZE_RCU Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 15/18] mm/hugetlb: Remove hugetlb_optimize_vmemmap_key static key Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 16/18] mm: Remove the branch from compound_head() Kiryl Shutsemau (Meta)
2026-02-27 19:42 ` [PATCHv7 17/18] hugetlb: Update vmemmap_dedup.rst Kiryl Shutsemau (Meta)
2026-02-27 21:08 ` Randy Dunlap
2026-03-02 3:10 ` Muchun Song
2026-03-02 10:56 ` [PATCHv7.1 " Kiryl Shutsemau (Meta)
2026-04-30 3:25 ` patchwork-bot+linux-riscv
2026-02-27 19:42 ` [PATCHv7 18/18] mm/slab: Use compound_head() in page_slab() Kiryl Shutsemau (Meta)
2026-02-28 23:01 ` [PATCHv7 RESEND 00/17] mm: Eliminate fake head pages from vmemmap optimization Andrew Morton
2026-04-30 3:25 ` patchwork-bot+linux-riscv
-- strict thread matches above, loose matches on Subject: below --
2026-02-02 15:56 [PATCHv6 " Kiryl Shutsemau
2026-02-27 19:30 ` [PATCHv7 13/18] mm: Drop fake head checks Kiryl Shutsemau (Meta)
2026-04-30 3:25 ` patchwork-bot+linux-riscv
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=7cb3dd91-f518-49b6-b311-9ec807b3c63b@linux.dev \
--to=muchun.song@linux.dev \
--cc=akpm@linux-foundation.org \
--cc=alex@ghiti.fr \
--cc=aou@eecs.berkeley.edu \
--cc=bhe@redhat.com \
--cc=chenhuacai@kernel.org \
--cc=corbet@lwn.net \
--cc=dave.hansen@linux.intel.com \
--cc=david@kernel.org \
--cc=fvdl@google.com \
--cc=hannes@cmpxchg.org \
--cc=kas@kernel.org \
--cc=kernel-team@meta.com \
--cc=kernel@xen0n.name \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-riscv@lists.infradead.org \
--cc=loongarch@lists.linux.dev \
--cc=lorenzo.stoakes@oracle.com \
--cc=luto@kernel.org \
--cc=mhocko@suse.com \
--cc=osalvador@suse.de \
--cc=palmer@dabbelt.com \
--cc=paul.walmsley@sifive.com \
--cc=peterz@infradead.org \
--cc=rppt@kernel.org \
--cc=usamaarif642@gmail.com \
--cc=vbabka@suse.cz \
--cc=willy@infradead.org \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox