mm/page_isolation: guard compound_order() against racing

The PageCompound branch reads compound_head() without holding a reference.
A racing split or free can cause compound_head() to return a stale
pointer, and compound_nr() reads the order from that stale head, leading
to out-of-range shifts and making the skip distance meaningless.

Read the order explicitly with compound_order() and validate it is within
MAX_FOLIO_ORDER before shifting.  Also verify the derived head_pfn against
the legitimate pfn: the head must not be past pfn, must be aligned to
nr_pages, and pfn must fall within the compound page.  Bail out with
-EBUSY if any check fails.

Link: https://lore.kernel.org/20260825120549.966271-3-xiqi2@huawei.com
Fixes: b2c9e2fbba ("mm: make alloc_contig_range work at pageblock granularity")
Signed-off-by: Qi Xi <xiqi2@huawei.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Suggested-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Cc: Brendan Jackman <brendan.jackman@linux.dev>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Kefeng Wang <wangkefeng.wang@huawei.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Nanyong Sun <sunnanyong@huawei.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Cc: <stable@vger.kernel.org>
This commit is contained in:
Qi Xi 2026-08-25 20:05:49 +08:00 committed by Andrew Morton
parent eba0e6297e
commit 4c767c5dd9

View File

@ -419,10 +419,28 @@ static int isolate_single_pageblock(unsigned long boundary_pfn,
if (PageCompound(page)) {
struct page *head = compound_head(page);
unsigned long head_pfn = page_to_pfn(head);
unsigned long nr_pages = compound_nr(head);
unsigned int order = compound_order(head);
unsigned long nr_pages;
/* compound_order() is racy. Cap it at MAX_FOLIO_ORDER. */
if (order > MAX_FOLIO_ORDER)
goto failed;
nr_pages = 1UL << order;
/*
* compound_head() is also racy, so the derived head_pfn
* needs additional checks to make sure it is valid.
* Otherwise, just fail the check. pfn comes from
* __first_valid_page() as a legitimate PFN, so use it to
* check head_pfn.
*/
if (head_pfn > pfn || !IS_ALIGNED(head_pfn, nr_pages) ||
pfn - head_pfn >= nr_pages)
goto failed;
if (head_pfn + nr_pages <= boundary_pfn ||
PageHuge(page)) {
PageHuge(head)) {
pfn = head_pfn + nr_pages;
continue;
}