The page_owner scan walks PFNs locklessly and dereferences the resolved struct page in the scan itself and in print_page_owner(). With CONFIG_MEMORY_HOTREMOVE, a concurrent remove_memory() can tear down the section's vmemmap, so a dereference after a stale validity check may fault. Take a mem-hotplug read lock across each page's check-then-deref. In the scan, gate every PFN with pfn_to_online_page() under the reader and drop it before cond_resched(), so hotplug can still interleave between pages; in print_page_owner(), re-resolve the page under a reader covering just the print of one record. The reader is cheap: get_online_mems()/put_online_mems() are just per-CPU counter increments/decrements when no hotplug is active, so they contend only with an actual add/remove. Taking the lock per-page bounds the writer's wait to a single page's check-and-deref, keeping hot-remove stalls brief while all accesses stay protected. This makes the traversal crash-free. A recycled-but-present page can still yield stale data -- the lockless scan is best-effort and unchanged by this patch. Signed-off-by: Zhen Ni --- Changes in v3: - New patch. Add the mem-hotplug protect. --- mm/page_owner.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/mm/page_owner.c b/mm/page_owner.c index 92a92cdf74ec..8e72a85c663c 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -762,13 +762,22 @@ static bool page_owner_next_eligible(struct page_owner_scan *scan) continue; } - page = pfn_to_page(pfn); + /* + * Hold a mem-hotplug reader across the page derefs so the + * vmemmap isn't torn down mid-scan; released before + * cond_resched(). + */ + get_online_mems(); + page = pfn_to_online_page(pfn); + if (!page) + goto put_online_continue; + if (skip_buddy_pages(&pfn, page)) - continue; + goto put_online_continue; page_ext = page_ext_get(page); if (unlikely(!page_ext)) - continue; + goto put_online_continue; /* * Some pages could be missed by concurrent allocation or free, @@ -805,10 +814,13 @@ static bool page_owner_next_eligible(struct page_owner_scan *scan) goto ext_put_continue; page_ext_put(page_ext); + put_online_mems(); return true; ext_put_continue: page_ext_put(page_ext); +put_online_continue: + put_online_mems(); cond_resched(); } @@ -831,7 +843,17 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) /* Find an allocated page */ while (page_owner_next_eligible(&scan)) { - struct page *page = pfn_to_page(scan.pfn); + struct page *page; + ssize_t ret; + + /* + * The scan is lockless. Re-resolve the page under a mem-hotplug + * read-side lock so that we never access a hot-removed section. + */ + get_online_mems(); + page = pfn_to_online_page(scan.pfn); + if (!page) + goto skip_continue; if (state->nid_filter_enabled) { int nid; @@ -851,10 +873,13 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos) /* Record the next PFN to read in the file offset */ *ppos = scan.pfn + 1; - return print_page_owner(buf, count, scan.pfn, page, + ret = print_page_owner(buf, count, scan.pfn, page, &scan.po_snap, scan.po_snap.handle, state); + put_online_mems(); + return ret; skip_continue: + put_online_mems(); scan.pfn++; cond_resched(); } -- 2.20.1