Non-DIRECT_RECLAIM (atomic) allocations that fail with ALLOC_NOFRAGMENT previously dropped the flag entirely and retried, allowing them to taint clean superpageblocks. This was the primary source of taint spreading observed on production systems. Stage the relaxation in three steps that keep atomic allocations inside tainted SPBs as long as possible: 1. Extend Pass 2 in __rmqueue_smallest with a sub-pageblock phase (Pass 2b). Pass 2 only finds whole free pageblocks (>= pageblock order) in tainted SPBs. Pass 2b searches for sub-pageblock-order free blocks and uses try_to_claim_block() to claim a pageblock that has enough compatible pages. This finds pages in tainted SPBs that have fragmented free space but no whole free pageblocks. 2. Add an ALLOC_NOFRAG_TAINTED_OK intermediate flag. Instead of going directly from ALLOC_NOFRAGMENT to no protection, atomic allocations first retry with ALLOC_NOFRAG_TAINTED_OK, which allows __rmqueue_steal to search tainted SPBs only. Clean and empty SPBs remain protected. Only if steal from tainted SPBs also fails is ALLOC_NOFRAGMENT fully dropped as a last resort. 3. Bypass the pageblock compatibility threshold inside try_to_claim_block() when the call originates from the tainted-SPB walk in Pass 2b. The free_pages + alike_pages >= 1 << (pageblock_order - 1) gate was designed to prevent the cross-fragment-fallback path from spreading mixing into clean SPBs; inside an already-tainted SPB the fragmentation has already been accepted, and the threshold rejects the typical fragmented-MOVABLE-pageblock case Pass 2b is meant to reclaim. Without the bypass Pass 2b would be largely a no-op. For callers that pass __GFP_NORETRY, the relaxation sequence is wrong in principle. The NORETRY contract is "I have a fallback; don't go to extreme lengths." Network skb_page_frag_refill, slab high-order allocations, and similar hot-path callers use NORETRY exactly so the allocator can return NULL and let their own fallback (smaller frag, lower-order slab, etc.) take over. Tainting a clean superpageblock to satisfy such a request is a lasting cost -- the SPB stays tainted for the remainder of the workload's lifetime, blocking 1 GiB hugepage allocation from that region -- that outlives the single allocation that triggered it. Skip the relaxation steps for NORETRY callers and return NULL immediately; their fallback path absorbs the failure cleanly. Observed on a 250 GB system running the page-superblock series: an atomic order-3 alloc from swapper context (PCP refill, gfp=0x152820 = __GFP_HIGH | __GFP_KSWAPD_RECLAIM | __GFP_NOWARN | __GFP_NORETRY | __GFP_COMP | __GFP_HARDWALL) tainted a fresh clean SPB at boot+~90 min despite ALLOC_NOFRAGMENT being set, because the atomic-retry path stripped the flag. The caller had a NORETRY fallback ready; the taint was gratuitous. Signed-off-by: Rik van Riel Assisted-by: Claude:claude-opus-4.7 syzkaller --- mm/internal.h | 1 + mm/page_alloc.c | 120 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/mm/internal.h b/mm/internal.h index c0dbc2e4b7f0..e6d61dbc18d9 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -1511,6 +1511,7 @@ unsigned int reclaim_clean_pages_from_list(struct zone *zone, #define ALLOC_HIGHATOMIC 0x200 /* Allows access to MIGRATE_HIGHATOMIC */ #define ALLOC_TRYLOCK 0x400 /* Only use spin_trylock in allocation path */ #define ALLOC_KSWAPD 0x800 /* allow waking of kswapd, __GFP_KSWAPD_RECLAIM set */ +#define ALLOC_NOFRAG_TAINTED_OK 0x1000 /* NOFRAGMENT, but allow steal from tainted SPBs */ /* Flags that allow allocations below the min watermark. */ #define ALLOC_RESERVES (ALLOC_NON_BLOCK|ALLOC_MIN_RESERVE|ALLOC_HIGHATOMIC|ALLOC_OOM) diff --git a/mm/page_alloc.c b/mm/page_alloc.c index b6a07bd72c0b..6884f638a97c 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -2729,6 +2729,10 @@ static struct page *__rmqueue_from_sb(struct zone *zone, unsigned int order, */ static struct page *claim_whole_block(struct zone *zone, struct page *page, int current_order, int order, int new_type, int old_type); +static struct page *try_to_claim_block(struct zone *zone, struct page *page, + int current_order, int order, int start_type, + int block_type, unsigned int alloc_flags, + bool from_tainted_spb); static __always_inline struct page *__rmqueue_smallest(struct zone *zone, unsigned int order, @@ -2798,6 +2802,11 @@ struct page *__rmqueue_smallest(struct zone *zone, unsigned int order, * free list (reset by mark_pageblock_free), so the search above * misses them. Claim them inline to keep non-movable allocations * concentrated in already-tainted superpageblocks. + * + * Try whole pageblock orders first (preferred for PCP buddy optimization), + * then fall back to sub-pageblock orders. Sub-pageblock claiming uses + * try_to_claim_block which checks whether the pageblock has enough + * compatible pages to justify claiming it. */ if (!movable && !is_migrate_cma(migratetype)) { for (full = SB_FULL; full < __NR_SB_FULLNESS; full++) { @@ -2830,6 +2839,43 @@ struct page *__rmqueue_smallest(struct zone *zone, unsigned int order, } } } + /* Pass 2b: sub-pageblock orders in tainted SPBs */ + for (full = SB_FULL; full < __NR_SB_FULLNESS; full++) { + list_for_each_entry(sb, + &zone->spb_lists[SB_TAINTED][full], list) { + int co; + + if (!sb->nr_free_pages) + continue; + for (co = min_t(int, pageblock_order - 1, + NR_PAGE_ORDERS - 1); + co >= (int)order; + --co) { + current_order = co; + area = &sb->free_area[current_order]; + page = get_page_from_free_area( + area, MIGRATE_MOVABLE); + if (!page) + continue; + if (get_pageblock_isolate(page)) + continue; + if (is_migrate_cma( + get_pageblock_migratetype(page))) + continue; + page = try_to_claim_block(zone, page, + current_order, order, + migratetype, MIGRATE_MOVABLE, + 0, true); + if (!page) + continue; + trace_mm_page_alloc_zone_locked( + page, order, migratetype, + pcp_allowed_order(order) && + migratetype < MIGRATE_PCPTYPES); + return page; + } + } + } } /* Empty superpageblocks: try before falling back to non-preferred category */ @@ -3298,11 +3344,17 @@ claim_whole_block(struct zone *zone, struct page *page, * not, we check the pageblock for constituent pages; if at least half of the * pages are free or compatible, we can still claim the whole block, so pages * freed in the future will be put on the correct free list. + * + * @from_tainted_spb: caller has already verified the block lives in a tainted + * superpageblock, where SPB-level fragmentation has already been accepted. + * Skip the per-pageblock compatibility threshold so we can absorb non-movable + * demand into the existing tainted SPB instead of tainting a fresh clean one. */ static struct page * try_to_claim_block(struct zone *zone, struct page *page, int current_order, int order, int start_type, - int block_type, unsigned int alloc_flags) + int block_type, unsigned int alloc_flags, + bool from_tainted_spb) { int free_pages, movable_pages, alike_pages; unsigned long start_pfn; @@ -3362,8 +3414,14 @@ try_to_claim_block(struct zone *zone, struct page *page, /* * If a sufficient number of pages in the block are either free or of * compatible migratability as our allocation, claim the whole block. - */ - if (free_pages + alike_pages >= (1 << (pageblock_order-1)) || + * The compatibility threshold protects clean MOVABLE pageblocks from + * being relabeled when most of their pages are still in-use movable + * allocations. Inside a tainted SPB the protection is unnecessary: + * fragmentation has already been accepted at the SPB level, and + * relabeling is much cheaper than tainting a fresh clean SPB. + */ + if (from_tainted_spb || + free_pages + alike_pages >= (1 << (pageblock_order-1)) || page_group_by_mobility_disabled) { __move_freepages_block(zone, start_pfn, block_type, start_type); set_pageblock_migratetype(pfn_to_page(start_pfn), start_type); @@ -3565,7 +3623,8 @@ __rmqueue_claim(struct zone *zone, int order, int start_migratetype, page = try_to_claim_block(zone, page, current_order, order, start_migratetype, - fallback_mt, alloc_flags); + fallback_mt, alloc_flags, + false); if (page) { trace_mm_page_alloc_extfrag(page, order, current_order, start_migratetype, @@ -3583,12 +3642,23 @@ __rmqueue_claim(struct zone *zone, int order, int start_migratetype, * the block as its current migratetype, potentially causing fragmentation. */ static __always_inline struct page * -__rmqueue_steal(struct zone *zone, int order, int start_migratetype) +__rmqueue_steal(struct zone *zone, int order, int start_migratetype, + unsigned int alloc_flags) { struct superpageblock *sb; int current_order; struct page *page; int fallback_mt; + unsigned int search_cats; + + /* + * When ALLOC_NOFRAG_TAINTED_OK is set, only steal from tainted + * SPBs to avoid tainting clean ones. Otherwise search all categories. + */ + if (alloc_flags & ALLOC_NOFRAG_TAINTED_OK) + search_cats = SB_SEARCH_PREFERRED; + else + search_cats = SB_SEARCH_PREFERRED | SB_SEARCH_FALLBACK; /* * Search per-superpageblock free lists for fallback migratetypes. @@ -3598,7 +3668,7 @@ __rmqueue_steal(struct zone *zone, int order, int start_migratetype) page = __rmqueue_sb_find_fallback(zone, current_order, start_migratetype, &fallback_mt, - SB_SEARCH_PREFERRED | SB_SEARCH_FALLBACK); + search_cats); if (!page) continue; @@ -3698,8 +3768,10 @@ __rmqueue(struct zone *zone, unsigned int order, int migratetype, } fallthrough; case RMQUEUE_STEAL: - if (!(alloc_flags & ALLOC_NOFRAGMENT)) { - page = __rmqueue_steal(zone, order, migratetype); + if (!(alloc_flags & ALLOC_NOFRAGMENT) || + (alloc_flags & ALLOC_NOFRAG_TAINTED_OK)) { + page = __rmqueue_steal(zone, order, migratetype, + alloc_flags); if (page) { *mode = RMQUEUE_STEAL; return page; @@ -5408,9 +5480,35 @@ get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags, /* * It's possible on a UMA machine to get through all zones that are * fragmented. If avoiding fragmentation, reset and try again. - */ - if (no_fallback && !defrag_mode) { - alloc_flags &= ~ALLOC_NOFRAGMENT; + * + * For allocations that can do direct reclaim, keep NOFRAGMENT set + * and let the slowpath try reclaim and compaction to free pages in + * already-tainted superpageblocks before allowing clean SPBs to be + * tainted. + * + * Atomic allocations cannot reclaim, but try an intermediate step + * first: allow steal/claim from tainted SPBs only. This avoids + * tainting clean SPBs while still finding pages in tainted ones. + * Only drop NOFRAGMENT entirely if that also fails. + * + * Exception: callers that explicitly opted into failure with + * __GFP_NORETRY have a fallback path of their own (a smaller + * order, a different cache, returning NULL from a best-effort + * cache refill, etc.). Tainting a clean superpageblock is a + * lasting cost that outlives this allocation; it is not justified + * to absorb it just to satisfy a caller that already has a + * cheaper escape hatch. Return NULL and let the caller's fallback + * run instead. + */ + if (no_fallback && !defrag_mode && + !(gfp_mask & __GFP_DIRECT_RECLAIM)) { + if (gfp_mask & __GFP_NORETRY) + return NULL; + if (!(alloc_flags & ALLOC_NOFRAG_TAINTED_OK)) { + alloc_flags |= ALLOC_NOFRAG_TAINTED_OK; + goto retry; + } + alloc_flags &= ~(ALLOC_NOFRAGMENT | ALLOC_NOFRAG_TAINTED_OK); goto retry; } -- 2.54.0