mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
Subsystems (3)
PATCH v2 1/1 mm/huge_memory: set PG_has_hwpoisoned only after new folio head is established
__split_folio_to_order() copies the hwpoison state onto each new sub-folio while splitting a folio to a non-zero order. It does 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, ...) turns @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 current 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.
memory_failure() tries to split the poisoned folio to order 0 first, but that split is best-effort; when it fails the folio is left large with PG_has_hwpoisoned set, the case fa5a06170036 added this hwpoison copying for.
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.
---
v2:
- cleaned up comment (Lorenzo)
- consistent changelog grammar, plus rationale on why this path exists (David)
- Cc: stable (Zi)
mm/huge_memory.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
mm/huge_memory.c +8 -4
@@ -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,14 @@ static void __split_folio_to_order(struct folio *folio, int old_order,folio_set_large_rmappable (new_folio); } +/* +* PG_has_hwpoisoned is on the 2nd page, so set it after +* the compound head is 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))
Signature · 1 line
2.53.0-Meta
Quoted 44 lines · Rik van Riel
__split_folio_to_order()copies the hwpoison state onto each new sub-folio while splitting a folio to a non-zero order. It does so viaif (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, ...) turns @new_head from a tail page into a proper folio head.
PG_has_hwpoisonedis aFOLIO_SECOND_PAGEflag, sofolio_set_has_hwpoisoned()resolves tofolio_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 current call site @new_head still has the tail marker (
compound_infobit 0 set,PG_headclear), so onCONFIG_DEBUG_VMkernels this hits:kernel BUG at
include/linux/page-flags.h:354folio_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.
memory_failure()tries to split the poisoned folio to order 0 first, but that split is best-effort; when it fails the folio is left large withPG_has_hwpoisonedset, the casefa5a06170036added this hwpoison copying for.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_hwpoisonimpliesnew_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 theFOLIO_SECOND_PAGEprecondition.
Quoted 2 lines
Fixes fa5a06170036 ("mm/huge_memory: preserve PG_has_hwpoisoned if a folio is split to >0 order")Signed-off-by Rik van Riel <[email protected]>
The actual logic looks good, thanks for fixing this, and the comment is much nicer now :) So:
Quoted 30 lines
Assisted-by Claude:claude-opus-4-8Reviewed-by Zi Yan <[email protected]>Tested-by Lance Yang <[email protected]>---
v2:
- cleaned up comment (Lorenzo)
- consistent changelog grammar, plus rationale on why this path exists (David)
- Cc: stable (Zi)mm/huge_memory.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)mm/huge_memory.c +0
-4@@ -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;
mm/huge_memory.c static void __split_folio_to_order(struct folio *folio, int old_order, · line 3614
Quoted 7 lines
mm/huge_memory.c +4
-0@@ -3612,6 +3608,14 @@ static void __split_folio_to_order(struct folio *folio, int old_order,folio_set_large_rmappable (new_folio); } +/* +* PG_has_hwpoisoned is on the 2nd page, so set it after +* the compound head is prepped. +*/
Great thanks!
Quoted 10 lines
mm/huge_memory.c +4
-1+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
Cheers, Lorenzo
Quoted 44 lines · Rik van Riel
__split_folio_to_order()copies the hwpoison state onto each new sub-folio while splitting a folio to a non-zero order. It does so viaif (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, ...) turns @new_head from a tail page into a proper folio head.
PG_has_hwpoisonedis aFOLIO_SECOND_PAGEflag, sofolio_set_has_hwpoisoned()resolves tofolio_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 current call site @new_head still has the tail marker (
compound_infobit 0 set,PG_headclear), so onCONFIG_DEBUG_VMkernels this hits:kernel BUG at
include/linux/page-flags.h:354folio_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.
memory_failure()tries to split the poisoned folio to order 0 first, but that split is best-effort; when it fails the folio is left large withPG_has_hwpoisonedset, the casefa5a06170036added this hwpoison copying for.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_hwpoisonimpliesnew_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 theFOLIO_SECOND_PAGEprecondition.
Quoted 8 lines
Fixes fa5a06170036 ("mm/huge_memory: preserve PG_has_hwpoisoned if a folio is split to >0 order")Signed-off-by Rik van Riel <[email protected]>Assisted-by Claude:claude-opus-4-8Reviewed-by Zi Yan <[email protected]>Tested-by Lance Yang <[email protected]>---
Good catch. LGTM.