[PATCH v2 2/2] mm/page_isolation: guard compound_order() against racing

From: Qi Xi

Date: Thu Aug 20 2026 - 23:15:24 EST


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.

Fixes: b2c9e2fbba32 ("mm: make alloc_contig_range work at pageblock granularity")
Cc: stable@xxxxxxxxxxxxxxx
Suggested-by: Zi Yan <ziy@xxxxxxxxxx>
Signed-off-by: Qi Xi <xiqi2@xxxxxxxxxx>
---
mm/page_isolation.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/mm/page_isolation.c b/mm/page_isolation.c
index f2b648a68531..9cf7f92011bd 100644
--- a/mm/page_isolation.c
+++ b/mm/page_isolation.c
@@ -414,10 +414,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;
}
--
2.33.0