* Re: [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
2026-07-01 13:46 [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established Rik van Riel
@ 2026-07-01 14:34 ` Zi Yan
2026-07-01 14:52 ` Lorenzo Stoakes
` (2 subsequent siblings)
3 siblings, 0 replies; 8+ messages in thread
From: Zi Yan @ 2026-07-01 14:34 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, kernel-team, linux-mm, akpm, david, ljs,
baolin.wang, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, yang
On 1 Jul 2026, at 9:46, Rik van Riel wrote:
> __split_folio_to_order() copies the hwpoison state onto each new
> sub-folio while splitting a folio to a non-zero order. It did so via
>
> if (handle_hwpoison && page_range_has_hwpoisoned(new_head, new_nr_pages))
> folio_set_has_hwpoisoned(new_folio);
>
> *before* clear_compound_head(new_head)/prep_compound_page(new_head, ...)
> turn @new_head from a tail page into a proper folio head.
>
> PG_has_hwpoisoned is a FOLIO_SECOND_PAGE flag, so folio_set_has_hwpoisoned()
> resolves to folio_flags(folio, 1). With the new compound_info-based
> page-flags layout, folio_flags() asserts the page is not a tail:
>
> VM_BUG_ON_PGFLAGS(page->compound_info & 1, page);
> VM_BUG_ON_PGFLAGS(n > 0 && !test_bit(PG_head, &page->flags.f), page);
>
> At the original call site @new_head still has the tail marker
> (compound_info bit 0 set, PG_head clear), so on CONFIG_DEBUG_VM kernels
> this hits:
>
> kernel BUG at include/linux/page-flags.h:354
> folio_flags+0x82
> folio_set_has_hwpoisoned
> __split_folio_to_order
> __split_unmapped_folio
> __folio_split
> truncate_inode_partial_folio (shmem hole-punch / MADV_REMOVE)
>
> Reproduced by syzkaller: hwpoison-inject a few subpages of a large shmem
> folio, then MADV_REMOVE (fallocate punch hole) on the same range, which
> splits the partial folio to a non-zero order.
>
> Move the folio_set_has_hwpoisoned() call to after
> clear_compound_head()/prep_compound_page(), where @new_folio is a real
> order-new_order head folio (handle_hwpoison implies new_order != 0, so a
> second page always exists). The flag still lands on the same struct page
> (page[1] of the new folio); only the ordering relative to compound-head
> setup changes, satisfying the FOLIO_SECOND_PAGE precondition.
>
> Signed-off-by: Rik van Riel <riel@surriel.com>
> Assisted-by: Claude:claude-opus-4-8
> Fixes: fa5a06170036 ("mm/huge_memory: preserve PG_has_hwpoisoned if a folio is split to >0 order")
> ---
> mm/huge_memory.c | 16 ++++++++++++----
> 1 file changed, 12 insertions(+), 4 deletions(-)
>
Thanks. It makes sense to me.
Reviewed-by: Zi Yan <ziy@nvidia.com>
Please Cc: stable since commit fa5a06170036 is back ported to older kernels.
For the backport to v6.12, the folio_set_has_hwpoisoned() is actually
placed after clear_compound_head() and prep_compound_page()[1], so v6.12
does not need this fix. (Do not ask me why I did this way when I back ported
the patch, since I do not know either :p )
[1] https://elixir.bootlin.com/linux/v6.12.94/source/mm/huge_memory.c#L3183
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
2026-07-01 13:46 [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established Rik van Riel
2026-07-01 14:34 ` Zi Yan
@ 2026-07-01 14:52 ` Lorenzo Stoakes
2026-07-01 16:29 ` Rik van Riel
2026-07-01 15:24 ` Lance Yang
2026-07-01 16:33 ` David Hildenbrand (Arm)
3 siblings, 1 reply; 8+ messages in thread
From: Lorenzo Stoakes @ 2026-07-01 14:52 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, kernel-team, linux-mm, akpm, david, ziy,
baolin.wang, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, yang
On Wed, Jul 01, 2026 at 09:46:22AM -0400, Rik van Riel wrote:
> __split_folio_to_order() copies the hwpoison state onto each new
> sub-folio while splitting a folio to a non-zero order. It did so via
>
> if (handle_hwpoison && page_range_has_hwpoisoned(new_head, new_nr_pages))
> folio_set_has_hwpoisoned(new_folio);
>
> *before* clear_compound_head(new_head)/prep_compound_page(new_head, ...)
> turn @new_head from a tail page into a proper folio head.
>
> PG_has_hwpoisoned is a FOLIO_SECOND_PAGE flag, so folio_set_has_hwpoisoned()
> resolves to folio_flags(folio, 1). With the new compound_info-based
> page-flags layout, folio_flags() asserts the page is not a tail:
>
> VM_BUG_ON_PGFLAGS(page->compound_info & 1, page);
> VM_BUG_ON_PGFLAGS(n > 0 && !test_bit(PG_head, &page->flags.f), page);
>
> At the original call site @new_head still has the tail marker
> (compound_info bit 0 set, PG_head clear), so on CONFIG_DEBUG_VM kernels
> this hits:
>
> kernel BUG at include/linux/page-flags.h:354
> folio_flags+0x82
> folio_set_has_hwpoisoned
> __split_folio_to_order
> __split_unmapped_folio
> __folio_split
> truncate_inode_partial_folio (shmem hole-punch / MADV_REMOVE)
>
> Reproduced by syzkaller: hwpoison-inject a few subpages of a large shmem
> folio, then MADV_REMOVE (fallocate punch hole) on the same range, which
> splits the partial folio to a non-zero order.
>
> Move the folio_set_has_hwpoisoned() call to after
> clear_compound_head()/prep_compound_page(), where @new_folio is a real
> order-new_order head folio (handle_hwpoison implies new_order != 0, so a
> second page always exists). The flag still lands on the same struct page
> (page[1] of the new folio); only the ordering relative to compound-head
> setup changes, satisfying the FOLIO_SECOND_PAGE precondition.
>
> Signed-off-by: Rik van Riel <riel@surriel.com>
> Assisted-by: Claude:claude-opus-4-8
> Fixes: fa5a06170036 ("mm/huge_memory: preserve PG_has_hwpoisoned if a folio is split to >0 order")
> ---
> mm/huge_memory.c | 16 ++++++++++++----
> 1 file changed, 12 insertions(+), 4 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 2bccb0a53a0a..ee7ecb3b45c6 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3587,10 +3587,6 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
> (1L << PG_dropbehind) |
> LRU_GEN_MASK | LRU_REFS_MASK));
>
> - if (handle_hwpoison &&
> - page_range_has_hwpoisoned(new_head, new_nr_pages))
> - folio_set_has_hwpoisoned(new_folio);
> -
> new_folio->mapping = folio->mapping;
> new_folio->index = folio->index + i;
>
> @@ -3612,6 +3608,18 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
> folio_set_large_rmappable(new_folio);
> }
>
> + /*
> + * PG_has_hwpoisoned is a FOLIO_SECOND_PAGE flag, so it can only
> + * be set once @new_folio is a real (head) folio. Defer setting
> + * it until after clear_compound_head()/prep_compound_page() have
> + * turned @new_head from a tail page into a proper folio head;
> + * otherwise folio_flags() trips on (page->compound_info & 1).
> + * handle_hwpoison implies new_order != 0.
> + */
This reads like an LLM comment...! A ton of noise and unnecessary detail.
How about:
/*
* PG_has_hwpoisoned is on the 2nd page, so set it after
* compound head prepped.
*/
?
> + if (handle_hwpoison &&
> + page_range_has_hwpoisoned(new_head, new_nr_pages))
> + folio_set_has_hwpoisoned(new_folio);
> +
> if (folio_test_young(folio))
> folio_set_young(new_folio);
> if (folio_test_idle(folio))
> --
> 2.53.0-Meta
>
Thanks, Lorenzo
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
2026-07-01 14:52 ` Lorenzo Stoakes
@ 2026-07-01 16:29 ` Rik van Riel
0 siblings, 0 replies; 8+ messages in thread
From: Rik van Riel @ 2026-07-01 16:29 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: linux-kernel, kernel-team, linux-mm, akpm, david, ziy,
baolin.wang, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, yang
On Wed, 2026-07-01 at 15:52 +0100, Lorenzo Stoakes wrote:
> On Wed, Jul 01, 2026 at 09:46:22AM -0400, Rik van Riel wrote:
> >
> > @@ -3612,6 +3608,18 @@ static void __split_folio_to_order(struct
> > folio *folio, int old_order,
> > folio_set_large_rmappable(new_folio);
> > }
> >
> > + /*
> > + * PG_has_hwpoisoned is a FOLIO_SECOND_PAGE flag,
> > so it can only
> > + * be set once @new_folio is a real (head) folio.
> > Defer setting
> > + * it until after
> > clear_compound_head()/prep_compound_page() have
> > + * turned @new_head from a tail page into a proper
> > folio head;
> > + * otherwise folio_flags() trips on (page-
> > >compound_info & 1).
> > + * handle_hwpoison implies new_order != 0.
> > + */
>
> This reads like an LLM comment...! A ton of noise and unnecessary
> detail.
>
> How about:
>
> /*
> * PG_has_hwpoisoned is on the 2nd page, so set it
> after
> * compound head prepped.
> */
It was an LLM comment, indeed. I harvest these
syzkaller fixes from an automated run every
once in a while, where the fix is "verified"
by checking whether the issue can still reproduce
after a tentative fix has been applied.
I've fixed up the comment as suggested, and
will CC: stable@kernel.org on v2.
thank you for the review,
Rik
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
2026-07-01 13:46 [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established Rik van Riel
2026-07-01 14:34 ` Zi Yan
2026-07-01 14:52 ` Lorenzo Stoakes
@ 2026-07-01 15:24 ` Lance Yang
2026-07-01 16:33 ` David Hildenbrand (Arm)
3 siblings, 0 replies; 8+ messages in thread
From: Lance Yang @ 2026-07-01 15:24 UTC (permalink / raw)
To: riel
Cc: linux-kernel, kernel-team, linux-mm, akpm, david, ljs, ziy,
baolin.wang, liam, npache, ryan.roberts, dev.jain, baohua,
lance.yang, yang
On Wed, Jul 01, 2026 at 09:46:22AM -0400, Rik van Riel wrote:
>__split_folio_to_order() copies the hwpoison state onto each new
>sub-folio while splitting a folio to a non-zero order. It did so via
>
> if (handle_hwpoison && page_range_has_hwpoisoned(new_head, new_nr_pages))
> folio_set_has_hwpoisoned(new_folio);
>
>*before* clear_compound_head(new_head)/prep_compound_page(new_head, ...)
>turn @new_head from a tail page into a proper folio head.
>
>PG_has_hwpoisoned is a FOLIO_SECOND_PAGE flag, so folio_set_has_hwpoisoned()
>resolves to folio_flags(folio, 1). With the new compound_info-based
>page-flags layout, folio_flags() asserts the page is not a tail:
>
> VM_BUG_ON_PGFLAGS(page->compound_info & 1, page);
> VM_BUG_ON_PGFLAGS(n > 0 && !test_bit(PG_head, &page->flags.f), page);
>
>At the original call site @new_head still has the tail marker
>(compound_info bit 0 set, PG_head clear), so on CONFIG_DEBUG_VM kernels
>this hits:
>
> kernel BUG at include/linux/page-flags.h:354
> folio_flags+0x82
> folio_set_has_hwpoisoned
> __split_folio_to_order
> __split_unmapped_folio
> __folio_split
> truncate_inode_partial_folio (shmem hole-punch / MADV_REMOVE)
>
>Reproduced by syzkaller: hwpoison-inject a few subpages of a large shmem
>folio, then MADV_REMOVE (fallocate punch hole) on the same range, which
>splits the partial folio to a non-zero order.
Hmm, a bit weird ... for shmem, hwpoison-inject should call
try_to_split_thp_page(..., 0), i.e. a uniform split to order-0, no?
So MADV_REMOVE should no longer see a large poisoned folio. Am I missing
something, or is there a syzkaller link?
>Move the folio_set_has_hwpoisoned() call to after
>clear_compound_head()/prep_compound_page(), where @new_folio is a real
>order-new_order head folio (handle_hwpoison implies new_order != 0, so a
>second page always exists). The flag still lands on the same struct page
>(page[1] of the new folio); only the ordering relative to compound-head
>setup changes, satisfying the FOLIO_SECOND_PAGE precondition.
>
>Signed-off-by: Rik van Riel <riel@surriel.com>
>Assisted-by: Claude:claude-opus-4-8
>Fixes: fa5a06170036 ("mm/huge_memory: preserve PG_has_hwpoisoned if a folio is split to >0 order")
>---
Anyway, I used a local split_huge_pages_pid() hack to create this exact
situation: a large shmem folio with PG_has_hwpoisoned set and a poisoned
subpage before the non-uniform split. The BUG is gone with this patch :)
Tested-by: Lance Yang <lance.yang@linux.dev>
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
2026-07-01 13:46 [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established Rik van Riel
` (2 preceding siblings ...)
2026-07-01 15:24 ` Lance Yang
@ 2026-07-01 16:33 ` David Hildenbrand (Arm)
2026-07-01 17:24 ` Rik van Riel
3 siblings, 1 reply; 8+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-01 16:33 UTC (permalink / raw)
To: Rik van Riel, linux-kernel
Cc: kernel-team, linux-mm, akpm, ljs, ziy, baolin.wang, liam, npache,
ryan.roberts, dev.jain, baohua, lance.yang, yang
On 7/1/26 15:46, Rik van Riel wrote:
> __split_folio_to_order() copies the hwpoison state onto each new
> sub-folio while splitting a folio to a non-zero order. It did so via
>
> if (handle_hwpoison && page_range_has_hwpoisoned(new_head, new_nr_pages))
> folio_set_has_hwpoisoned(new_folio);
>
> *before* clear_compound_head(new_head)/prep_compound_page(new_head, ...)
> turn @new_head from a tail page into a proper folio head.
There is some grammatical issue that makes me wonder whether you are talking
about the present or the past.
"copies" ... "did so" "before ... turn".
Should it be "does so" and "before ... turns"
>
> PG_has_hwpoisoned is a FOLIO_SECOND_PAGE flag, so folio_set_has_hwpoisoned()
> resolves to folio_flags(folio, 1). With the new compound_info-based
> page-flags layout, folio_flags() asserts the page is not a tail:
>
> VM_BUG_ON_PGFLAGS(page->compound_info & 1, page);
> VM_BUG_ON_PGFLAGS(n > 0 && !test_bit(PG_head, &page->flags.f), page);
>
> At the original call site @new_head still has the tail marker
"current" call site?
> (compound_info bit 0 set, PG_head clear), so on CONFIG_DEBUG_VM kernels
> this hits:
>
> kernel BUG at include/linux/page-flags.h:354
> folio_flags+0x82
> folio_set_has_hwpoisoned
> __split_folio_to_order
> __split_unmapped_folio
> __folio_split
> truncate_inode_partial_folio (shmem hole-punch / MADV_REMOVE)
>
> Reproduced by syzkaller: hwpoison-inject a few subpages of a large shmem
> folio, then MADV_REMOVE (fallocate punch hole) on the same range, which
> splits the partial folio to a non-zero order.
As Lance says, after we do the TestSetPageHWPoison() in memory_failure(), we
call try_to_split_thp_page(). Does that already suffice, even without the
MADV_REMOCE.
>
> Move the folio_set_has_hwpoisoned() call to after
> clear_compound_head()/prep_compound_page(), where @new_folio is a real
> order-new_order head folio (handle_hwpoison implies new_order != 0, so a
> second page always exists). The flag still lands on the same struct page
> (page[1] of the new folio); only the ordering relative to compound-head
> setup changes, satisfying the FOLIO_SECOND_PAGE precondition.
>
> Signed-off-by: Rik van Riel <riel@surriel.com>
> Assisted-by: Claude:claude-opus-4-8
> Fixes: fa5a06170036 ("mm/huge_memory: preserve PG_has_hwpoisoned if a folio is split to >0 order")
> ---
> mm/huge_memory.c | 16 ++++++++++++----
> 1 file changed, 12 insertions(+), 4 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 2bccb0a53a0a..ee7ecb3b45c6 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3587,10 +3587,6 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
> (1L << PG_dropbehind) |
> LRU_GEN_MASK | LRU_REFS_MASK));
>
> - if (handle_hwpoison &&
> - page_range_has_hwpoisoned(new_head, new_nr_pages))
> - folio_set_has_hwpoisoned(new_folio);
> -
> new_folio->mapping = folio->mapping;
> new_folio->index = folio->index + i;
>
> @@ -3612,6 +3608,18 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
> folio_set_large_rmappable(new_folio);
> }
>
> + /*
> + * PG_has_hwpoisoned is a FOLIO_SECOND_PAGE flag, so it can only
> + * be set once @new_folio is a real (head) folio. Defer setting
> + * it until after clear_compound_head()/prep_compound_page() have
> + * turned @new_head from a tail page into a proper folio head;
> + * otherwise folio_flags() trips on (page->compound_info & 1).
> + * handle_hwpoison implies new_order != 0.
> + */
I prefer the shorter variant from Lorenzo.
> + if (handle_hwpoison &&
> + page_range_has_hwpoisoned(new_head, new_nr_pages))
> + folio_set_has_hwpoisoned(new_folio);
> +
> if (folio_test_young(folio))
> folio_set_young(new_folio);
> if (folio_test_idle(folio))
LGTM. The folio is still frozen at that point.
In general
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
2026-07-01 16:33 ` David Hildenbrand (Arm)
@ 2026-07-01 17:24 ` Rik van Riel
2026-07-02 2:29 ` Lance Yang
0 siblings, 1 reply; 8+ messages in thread
From: Rik van Riel @ 2026-07-01 17:24 UTC (permalink / raw)
To: David Hildenbrand (Arm), linux-kernel
Cc: kernel-team, linux-mm, akpm, ljs, ziy, baolin.wang, liam, npache,
ryan.roberts, dev.jain, baohua, lance.yang, yang
On Wed, 2026-07-01 at 18:33 +0200, David Hildenbrand (Arm) wrote:
>
> There is some grammatical issue that makes me wonder whether you are
> talking
> about the present or the past.
> >
Changelog issues fixed for v2. Thank you.
> > Reproduced by syzkaller: hwpoison-inject a few subpages of a large
> > shmem
> > folio, then MADV_REMOVE (fallocate punch hole) on the same range,
> > which
> > splits the partial folio to a non-zero order.
>
> As Lance says, after we do the TestSetPageHWPoison() in
> memory_failure(), we
> call try_to_split_thp_page(). Does that already suffice, even without
> the
> MADV_REMOCE.
While fa5a06170036 was created to deal with having
the folio split to >0 order, in this case it looks
like the try_to_split_thp_page() call from memory_failure()
failed, presumably because folio_ref_freeze() could not
freeze the order 7 folio, because the reproducer had
multiple threads poking at that folio simultaneously.
This test case is rather contrived, like most syzkaller
tests, but given that the try_to_split_thp_page() call
could fail for various reasons, we do need this fallback
path.
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
2026-07-01 17:24 ` Rik van Riel
@ 2026-07-02 2:29 ` Lance Yang
0 siblings, 0 replies; 8+ messages in thread
From: Lance Yang @ 2026-07-02 2:29 UTC (permalink / raw)
To: Rik van Riel
Cc: kernel-team, David Hildenbrand (Arm), linux-mm, akpm, ljs, ziy,
baolin.wang, linux-kernel, liam, npache, ryan.roberts, dev.jain,
baohua, yang
On 2026/7/2 01:24, Rik van Riel wrote:
> On Wed, 2026-07-01 at 18:33 +0200, David Hildenbrand (Arm) wrote:
>>
>> There is some grammatical issue that makes me wonder whether you are
>> talking
>> about the present or the past.
>>>
>
> Changelog issues fixed for v2. Thank you.
>
>>> Reproduced by syzkaller: hwpoison-inject a few subpages of a large
>>> shmem
>>> folio, then MADV_REMOVE (fallocate punch hole) on the same range,
>>> which
>>> splits the partial folio to a non-zero order.
>>
>> As Lance says, after we do the TestSetPageHWPoison() in
>> memory_failure(), we
>> call try_to_split_thp_page(). Does that already suffice, even without
>> the
>> MADV_REMOCE.
>
> While fa5a06170036 was created to deal with having
> the folio split to >0 order, in this case it looks
> like the try_to_split_thp_page() call from memory_failure()
> failed, presumably because folio_ref_freeze() could not
> freeze the order 7 folio, because the reproducer had
> multiple threads poking at that folio simultaneously.
>
> This test case is rather contrived, like most syzkaller
> tests, but given that the try_to_split_thp_page() call
> could fail for various reasons, we do need this fallback
> path.
Should be rare, but yeah, there it is. Wondered if the hwpoison-time
split had failed somehow ... turns out it did :)
Thanks for spelling that out!
^ permalink raw reply [flat|nested] 8+ messages in thread