mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu
@ 2026-11-11 18:13 Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 01/13] mm/mmap: free gathered pages before MMF_OOM_SKIP Lorenzo Stoakes (ARM)
                   ` (12 more replies)
  0 siblings, 13 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	stable, Josef Bacik

A bunch of fixes for oomk, cachestat, device migration and the per-CPU
allocator.

These patches were originally AI-generated, tamed into upstreamable state.

Some are more reworked than others. Those which needed a significant
rethink are submitted as mine and marked as Reported-by the relevant
original author, others are smaller edits which are annotated as such in
the usual fashion.

Please check for correctness, sanity, and whether the cc: stable is
warranted (I have not included it for 3 where it seemed unwarranted.)

Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
Chris Mason (5):
      mm: cachestat: count each page only once
      mm: cachestat: restart the walk after each entry
      mm/migrate_device: fix ksmd oops on a migrated KSM page
      mm/migrate_device: refuse to migrate shadow stacks
      percpu: queue the balance work on an unbound workqueue

Josef Bacik (4):
      mm/migrate_device: allocate THP deferred split heads
      mm/migrate_device: don't collect a range twice
      mm/migrate_device: don't let go of the fault page
      mm/migrate_device: look up fault folio after a split

Lorenzo Stoakes (ARM) (4):
      mm/mmap: free gathered pages before MMF_OOM_SKIP
      mm/migrate_device: fix use-after-free of the anon rmap
      percpu: fix contig hints that are too low
      percpu: search the whole reserved chunk

 include/linux/migrate.h  |   1 +
 include/linux/mm_types.h |   2 +
 include/linux/oom.h      |   9 ++++
 mm/filemap.c             |  21 ++++++---
 mm/migrate_device.c      | 111 ++++++++++++++++++++++++++++++++---------------
 mm/mmap.c                |   9 +++-
 mm/oom_kill.c            |   4 +-
 mm/percpu.c              |  69 +++++++++--------------------
 8 files changed, 136 insertions(+), 90 deletions(-)
---
base-commit: 7f04048247a69019ccaa9c152002d3601b19453c
change-id: 20261107-mason-migrate-et-al-fixes-0494d34884c4

Cheers,
-- 
Lorenzo Stoakes (ARM) <ljs@kernel.org>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 01/13] mm/mmap: free gathered pages before MMF_OOM_SKIP
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 02/13] mm: cachestat: count each page only once Lorenzo Stoakes (ARM)
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	stable

The OOM killer can quickly trigger again after having selected a
victim. That victim might not have freed its memory yet, but its mm will
be marked MMF_OOM_SKIP by this point, so the OOM killer will ignore it.

This can cause the OOM killer to kill an innocent process when the first
might have freed enough memory to relieve the memory pressure.

Fix the issue by freeing the memory before marking the mm MMF_OOM_SKIP.

Note that the task which calls mmput() might not be the victim - if it
took a reference on the mm (e.g. ptrace), it may hold the last reference
and release it when it calls mmput().

Therefore we cannot rely on current to tell whether the mm belongs to an
OOM victim. Instead, partially revert commit b3541d912a84 ("mm: delete
unused MMF_OOM_VICTIM flag") to reinstate MMF_OOM_VICTIM, which
mark_oom_victim() sets on the victim's mm, and check that.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: bf3980c85212 ("mm: drop oom code from exit_mmap")
Cc: <stable@vger.kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 include/linux/mm_types.h | 2 ++
 include/linux/oom.h      | 9 +++++++++
 mm/mmap.c                | 9 ++++++++-
 mm/oom_kill.c            | 4 +++-
 4 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 1ed35d258d2e..09e63ff12b04 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -2030,6 +2030,8 @@ enum {
 #define MMF_TOPDOWN		31	/* mm searches top down by default */
 #define MMF_TOPDOWN_MASK	BIT(MMF_TOPDOWN)
 
+#define MMF_OOM_VICTIM		32	/* mm is the oom victim */
+
 #define MMF_INIT_LEGACY_MASK	(MMF_DUMP_FILTER_MASK |\
 				 MMF_DISABLE_THP_MASK | MMF_HAS_MDWE_MASK |\
 				 MMF_VM_MERGE_ANY_MASK | MMF_TOPDOWN_MASK)
diff --git a/include/linux/oom.h b/include/linux/oom.h
index 00da05d227e6..a9390cb70541 100644
--- a/include/linux/oom.h
+++ b/include/linux/oom.h
@@ -76,6 +76,15 @@ static inline bool tsk_is_oom_victim(struct task_struct * tsk)
 	return tsk->signal->oom_mm;
 }
 
+/*
+ * Use this helper if tsk->mm != mm and the victim mm needs a special
+ * handling. This is guaranteed to stay true after once set.
+ */
+static inline bool mm_is_oom_victim(struct mm_struct *mm)
+{
+	return mm_flags_test(MMF_OOM_VICTIM, mm);
+}
+
 /*
  * Checks whether a page fault on the given mm is still reliable.
  * This is no longer true if the oom reaper started to reap the
diff --git a/mm/mmap.c b/mm/mmap.c
index 7618e72766a5..4cb18b217048 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1313,11 +1313,18 @@ void exit_mmap(struct mm_struct *mm)
 	/* update_hiwater_rss(mm) here? but nobody should be looking */
 	/* Use ULONG_MAX here to ensure all VMAs in the mm are unmapped */
 	unmap_vmas(&tlb, &unmap);
+
+	/*
+	 * Free the memory before marking MMF_OOM_SKIP so a re-triggered OOM
+	 * killer will only skip this mm if it's already freed all it can.
+	 */
+	if (mm_is_oom_victim(mm))
+		tlb_flush_mmu(&tlb);
 	mmap_read_unlock(mm);
 
 	/*
 	 * Set MMF_OOM_SKIP to hide this task from the oom killer/reaper
-	 * because the memory has been already freed.
+	 * because the memory has been already unmapped.
 	 */
 	mm_flags_set(MMF_OOM_SKIP, mm);
 	mmap_write_lock(mm);
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index 5d48bd862c27..9de57a23893c 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -752,8 +752,10 @@ static void mark_oom_victim(struct task_struct *tsk)
 		return;
 
 	/* oom_mm is bound to the signal struct life time. */
-	if (!cmpxchg(&tsk->signal->oom_mm, NULL, mm))
+	if (!cmpxchg(&tsk->signal->oom_mm, NULL, mm)) {
 		mmgrab(tsk->signal->oom_mm);
+		mm_flags_set(MMF_OOM_VICTIM, mm);
+	}
 
 	/*
 	 * Make sure that the process is woken up from uninterruptible sleep

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 02/13] mm: cachestat: count each page only once
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 01/13] mm/mmap: free gathered pages before MMF_OOM_SKIP Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 03/13] mm: cachestat: restart the walk after each entry Lorenzo Stoakes (ARM)
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	stable

From: Chris Mason <mason@kernel.org>

It turns out that filemap_cachestat() can unwittingly return incorrect
page counts, if large folios are present.

This can occur due to underflow - nr_cache is nearly 2^64 on a 64-bit
kernel if the input range starts more than 64 pages into a folio that is
split during the call.

It can also occur due to pages after a large folio being skipped if the
range starts in its second or later XArray slot and the call reschedules at
that folio.

Finally it can be caused by pages being counted twice due to folios in the
range being split or collapsed during the call.

filemap_cachestat() walks the page cache under the RCU read lock only,
and for each entry it counts the pages that lie in the range. It does
not track how far it has counted, so the counts are only right if the
walk returns each index once.

But a large folio can be split during the walk, or small folios can be
collapsed into a large one, and the next entries then cover indices that
were already counted.

Resolve the issue by tracking the current index as one past the last index
in the folio, and explicitly using xas_set() to set the current position in
the XArray.

Additionally, add a check for folio_last_index reaching or exceeding
last_index and break out of the loop if so.

Fixes: cf264e1329fb ("cachestat: implement cachestat syscall")
Cc: <stable@vger.kernel.org>
Signed-off-by: Chris Mason <mason@kernel.org>
[ ljs: updated to use curr_index, fixed comments, updated commit message ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/filemap.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/mm/filemap.c b/mm/filemap.c
index b04be64a5069..7c05e4fad081 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -4638,6 +4638,7 @@ static void filemap_cachestat(struct address_space *mapping,
 {
 	XA_STATE(xas, &mapping->i_pages, first_index);
 	struct folio *folio;
+	pgoff_t curr_index = first_index;
 
 	/* Flush stats (and potentially sleep) outside the RCU read section. */
 	mem_cgroup_flush_stats_ratelimited(NULL);
@@ -4667,13 +4668,19 @@ static void filemap_cachestat(struct address_space *mapping,
 		folio_first_index = round_down(xas.xa_index, 1 << order);
 		folio_last_index = folio_first_index + nr_pages - 1;
 
+		/* Skip already counted entries, e.g. after a split/collapse. */
+		if (folio_last_index < curr_index)
+			goto resched;
+
 		/* Folios might straddle the range boundaries, only count covered pages */
-		if (folio_first_index < first_index)
-			nr_pages -= first_index - folio_first_index;
+		if (folio_first_index < curr_index)
+			nr_pages -= curr_index - folio_first_index;
 
 		if (folio_last_index > last_index)
 			nr_pages -= folio_last_index - last_index;
 
+		curr_index = folio_last_index + 1;
+
 		if (xa_is_value(folio)) {
 			/* page is evicted */
 			void *shadow = (void *)folio;
@@ -4721,8 +4728,11 @@ static void filemap_cachestat(struct address_space *mapping,
 			cs->nr_writeback += nr_pages;
 
 resched:
+		if (folio_last_index >= last_index)
+			break;
+
 		if (need_resched()) {
-			xas_pause(&xas);
+			xas_set(&xas, curr_index);
 			cond_resched_rcu();
 		}
 	}

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 03/13] mm: cachestat: restart the walk after each entry
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 01/13] mm/mmap: free gathered pages before MMF_OOM_SKIP Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 02/13] mm: cachestat: count each page only once Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 04/13] mm/migrate_device: allocate THP deferred split heads Lorenzo Stoakes (ARM)
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM)

From: Chris Mason <mason@kernel.org>

cachestat() can leave cached pages out of its counts. This happens if
the entry that the call has just counted is replaced by a larger folio
that starts in the same XArray slot.

The issue is that filemap_cachestat() walks the page cache under the RCU
read lock, so racing concurrent collapse/split is possible.

Thus, an entry can be replaced immediately after the walk has returned
it. When that happens, the next xas_find() skips the sibling entries that
point to the slot of that entry, since ordinarily they belong to that
entry.

However a concurrent collapse_file() or __filemap_add_folio() (when
replacing a shadow entry) can store a larger folio in the slot, in which
case the sibling entries belong to the new folio and the walk still skips
them.

Consider:

    cachestat                         collapse
    =========                         ========
    count a 64-page folio in slot 8
                                      store a 512-page folio in slot 8,
                                      with sibling entries in slots 9-15
    xas_find() starts at slot 8
    and skips slots 9-15
      /* 448 pages are never counted */

Also consider:

    filemap_cachestat()                  __filemap_add_folio()
    ===================                  =====================
    counts the shadow entry in slot 0
                                         slot 0: a folio of 32 pages
                                         slots 1-31: siblings of slot 0
    xas_find() skips slots 1-31
      /* 31 pages are never counted */

Essentially the fact that large folios might be present means we cannot
safely continue the XArray walk, and must reset the iterator each time.

Fixes: cf264e1329fb ("cachestat: implement cachestat syscall")
Signed-off-by: Chris Mason <mason@kernel.org>
[ ljs: squashed 2 commits, fixed up comment in code, and commit message ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/filemap.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/mm/filemap.c b/mm/filemap.c
index 7c05e4fad081..c4e82891767b 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -4731,10 +4731,11 @@ static void filemap_cachestat(struct address_space *mapping,
 		if (folio_last_index >= last_index)
 			break;
 
-		if (need_resched()) {
-			xas_set(&xas, curr_index);
+		/* Collapse or new folio spanning shadow entries needs reset. */
+		xas_set(&xas, curr_index);
+
+		if (need_resched())
 			cond_resched_rcu();
-		}
 	}
 	rcu_read_unlock();
 }

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 04/13] mm/migrate_device: allocate THP deferred split heads
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (2 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 03/13] mm: cachestat: restart the walk after each entry Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 19:30   ` Kiryl Shutsemau
  2026-11-11 18:13 ` [PATCH 05/13] mm/migrate_device: don't collect a range twice Lorenzo Stoakes (ARM)
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	Josef Bacik, stable

From: Josef Bacik <josef@toxicpanda.com>

A THP that device migration maps into an empty PMD can end up on the wrong
deferred split list when it is later queued for a deferred split.

This occurs if the memcg has no heads yet (resulting in a warning if
CONFIG_DEBUG_VM is set), where deferred_split_folio() will then queue the
folio on the parent's list.

Once a later THP fault in the same memcg allocates the heads,
__folio_unqueue_deferred_split() and the split path resolve folio_memcg()
to the memcg's own, now existing, list and take its lock.  They then
list_del_init() a folio that is linked on the parent's list, racing with
anything holding the parent's lock, and both lists' nr_items counts go
wrong.

Resolve the issue by calling folio_memcg_alloc_deferred() after the charge
in migrate_vma_insert_huge_pmd_page() and falling back like a failed charge
if that fails, as __do_huge_pmd_anonymous_page() does.

This was originally discovered by code inspection.

Fixes: fafaeceb89a5 ("mm: switch deferred split shrinker to list_lru")
Cc: <stable@vger.kernel.org>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
[ ljs: tweak commit message, remove unnecessary comment ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/migrate_device.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 9e19fadfa65c..9e5dab8e1b77 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -833,6 +833,13 @@ static int migrate_vma_insert_huge_pmd_page(struct migrate_vma *migrate,
 		goto abort;
 	}
 
+	if (folio_memcg_alloc_deferred(folio)) {
+		count_vm_event(THP_FAULT_FALLBACK);
+		count_mthp_stat(HPAGE_PMD_ORDER, MTHP_STAT_ANON_FAULT_FALLBACK);
+		ret = -ENOMEM;
+		goto abort;
+	}
+
 	__folio_mark_uptodate(folio);
 
 	pgtable = pte_alloc_one(vma->vm_mm);

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 05/13] mm/migrate_device: don't collect a range twice
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (3 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 04/13] mm/migrate_device: allocate THP deferred split heads Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 06/13] mm/migrate_device: don't let go of the fault page Lorenzo Stoakes (ARM)
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	Josef Bacik, stable

From: Josef Bacik <josef@toxicpanda.com>

migrate_vma_collect_pmd() drops the page table lock to split a large folio
which it finds in the middle of a page table, and then starts over at the
"again" label. The entries for [start, addr) are in migrate->src[] and
migrate->dst[] by then.

While the page table lock is dropped the page table can go away, in which
case the restarted walk invokes migrate_vma_collect_huge_pmd(). However
this is passed start rather than addr, so [start, addr) is collected a
second time.

This can result in migrate->npages exceeding the number of pages the
caller asked for, and therefore in writes past the end of the
migrate->src[] and migrate->dst[] arrays.

Fix this by only handing over what has not yet been collected,
page-by-page, and flush the TLB for those entries which have been
previously replaced, similar to the other returns in the function.

Fixes: 022a12deda53 ("mm/migrate_device: handle partially mapped folios during collection")
Cc: <stable@vger.kernel.org>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
[ ljs: cleanup commit message ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/migrate_device.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 9e5dab8e1b77..28ed706109da 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -257,13 +257,17 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,
 	pte_t *ptep;
 
 again:
+	/* [start, addr) is collected if we get here after splitting a folio */
 	if (pmd_trans_huge(*pmdp) || !pmd_present(*pmdp)) {
-		int ret = migrate_vma_collect_huge_pmd(pmdp, start, end, walk, fault_folio);
+		int ret = migrate_vma_collect_huge_pmd(pmdp, addr, end, walk, fault_folio);
 
 		if (ret == -EAGAIN)
 			goto again;
-		if (ret == 0)
+		if (ret == 0) {
+			if (unmapped)
+				flush_tlb_range(walk->vma, start, end);
 			return 0;
+		}
 	}
 
 	ptep = pte_offset_map_lock(mm, pmdp, start, &ptl);

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 06/13] mm/migrate_device: don't let go of the fault page
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (4 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 05/13] mm/migrate_device: don't collect a range twice Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 07/13] mm/migrate_device: look up fault folio after a split Lorenzo Stoakes (ARM)
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	Josef Bacik, stable

From: Josef Bacik <josef@toxicpanda.com>

When migrate_vma_split_folio() has split the folio of the fault page,
the lock and the reference which the fault handler has taken are at the
former head page.

It then moves them to the folio which now holds the fault page, by
taking a reference on and locking that folio, then unlocking and putting
the former head.

split_folio() has unlocked the new folio and has dropped its own
reference on it, so until folio_get() nothing but the page table entry
holds it.

Only the mmap lock is held for read, which does not prevent another thread
from zapping the entry with MADV_DONTNEED.

If this happens the folio is freed which results in folio_get() taking a
reference on a free device page, which the driver can hand out again at any
time.

In addition, folio_lock() can sleep here with the lock of the head page
held.

Utilise split_huge_page() which will leave the lock and reference at the
fault page correctly.

Fixes: 022a12deda53 ("mm/migrate_device: handle partially mapped folios during collection")
Cc: <stable@vger.kernel.org>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
[ ljs: tweaked commit message ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/migrate_device.c | 39 ++++++++++-----------------------------
 1 file changed, 10 insertions(+), 29 deletions(-)

diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 28ed706109da..ae795fdf36f4 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -87,39 +87,20 @@ static int migrate_vma_split_folio(struct folio *folio,
 {
 	int ret;
 	struct folio *fault_folio = fault_page ? page_folio(fault_page) : NULL;
-	struct folio *new_fault_folio = NULL;
-
-	if (folio != fault_folio)
-		folio_lock(folio);
-
-	ret = split_folio(folio);
-	if (ret) {
-		if (folio != fault_folio) {
-			folio_unlock(folio);
-			folio_put(folio);
-		}
-		return ret;
-	}
-
-	new_fault_folio = fault_page ? page_folio(fault_page) : NULL;
 
 	/*
-	 * Ensure the lock is held on the correct
-	 * folio after the split
+	 * The lock and the reference of the fault handler stay with the
+	 * folio which holds the fault page after the split.
 	 */
-	if (!new_fault_folio) {
-		folio_unlock(folio);
-		folio_put(folio);
-	} else if (folio != new_fault_folio) {
-		if (new_fault_folio != fault_folio) {
-			folio_get(new_fault_folio);
-			folio_lock(new_fault_folio);
-		}
-		folio_unlock(folio);
-		folio_put(folio);
-	}
+	if (folio == fault_folio)
+		return split_huge_page(fault_page);
 
-	return 0;
+	folio_lock(folio);
+	ret = split_folio(folio);
+	folio_unlock(folio);
+	folio_put(folio);
+
+	return ret;
 }
 
 /** migrate_vma_collect_huge_pmd - collect THP pages without splitting the

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 07/13] mm/migrate_device: look up fault folio after a split
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (5 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 06/13] mm/migrate_device: don't let go of the fault page Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 08/13] mm/migrate_device: fix ksmd oops on a migrated KSM page Lorenzo Stoakes (ARM)
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	Josef Bacik, stable

From: Josef Bacik <josef@toxicpanda.com>

migrate_vma_collect_pmd() looks up the folio of migrate->fault_page once,
when it is entered, at which point the folio is locked.

The fault folio is assumed not to change after this, however if
migrate_vma_split_folio() splits a PTE-mapped large folio which contains
the fault page, the fault page ends up in a different folio.

Resolve the issue by looking up fault_folio again after each restart of the
walk.

Fixes: 022a12deda53 ("mm/migrate_device: handle partially mapped folios during collection")
Cc: <stable@vger.kernel.org>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
[ ljs: pared down commit message ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/migrate_device.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index ae795fdf36f4..45e571dbfb4c 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -233,11 +233,14 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,
 	struct mm_struct *mm = vma->vm_mm;
 	unsigned long addr = start, unmapped = 0;
 	spinlock_t *ptl;
-	struct folio *fault_folio = migrate->fault_page ?
-		page_folio(migrate->fault_page) : NULL;
+	struct folio *fault_folio;
 	pte_t *ptep;
 
 again:
+	/* The fault page is in another folio once its folio has been split */
+	fault_folio = migrate->fault_page ?
+		page_folio(migrate->fault_page) : NULL;
+
 	/* [start, addr) is collected if we get here after splitting a folio */
 	if (pmd_trans_huge(*pmdp) || !pmd_present(*pmdp)) {
 		int ret = migrate_vma_collect_huge_pmd(pmdp, addr, end, walk, fault_folio);

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 08/13] mm/migrate_device: fix ksmd oops on a migrated KSM page
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (6 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 07/13] mm/migrate_device: look up fault folio after a split Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 21:54   ` David Hildenbrand (Arm)
  2026-11-11 18:13 ` [PATCH 09/13] mm/migrate_device: fix use-after-free of the anon rmap Lorenzo Stoakes (ARM)
                   ` (4 subsequent siblings)
  12 siblings, 1 reply; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	stable

From: Chris Mason <mason@kernel.org>

After a KSM page is migrated to device private memory, ksmd can oops,
and KSM then stops until reboot. A process that maps the page can also
loop in page faults when it next accesses the page.

The problem arises because KSM doesn't know not to try to look at device
private memory that does not exist in the direct map.

Disallow this from happening by explicitly testing for this scenario in
__migrate_device_pages().

Fixes: a5430dda8a3a ("mm/migrate: support un-addressable ZONE_DEVICE page in migration")
Cc: <stable@vger.kernel.org>
Signed-off-by: Chris Mason <mason@kernel.org>
[ ljs: tweak commit message, comment ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/migrate_device.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 45e571dbfb4c..7ebd02ec303b 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -1205,6 +1205,15 @@ static void __migrate_device_pages(unsigned long *src_pfns,
 		}
 
 
+		/*
+		 * KSM doesn't know not to try to read device private pages, so
+		 * disallow the combination.
+		 */
+		if (folio_is_device_private(newfolio) && folio_test_ksm(folio)) {
+			src_pfns[i] &= ~MIGRATE_PFN_MIGRATE;
+			goto next;
+		}
+
 		if (folio_is_device_private(newfolio) ||
 		    folio_is_device_coherent(newfolio)) {
 			if (mapping) {

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 09/13] mm/migrate_device: fix use-after-free of the anon rmap
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (7 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 08/13] mm/migrate_device: fix ksmd oops on a migrated KSM page Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 10/13] mm/migrate_device: refuse to migrate shadow stacks Lorenzo Stoakes (ARM)
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	stable

A munmap() of anonymous memory can result in a use-after-free of the
anonymous rmap if a concurrent device memory migration occurs and the
migration runs without the mmap_lock held.

This can occur for a long-term pin of device-coherent memory, or a driver
evicting its device memory by PFN.

Consider:

    eviction                        munmap()
    ========                        ========
    migrate_device_range()
      migrate_device_unmap()
        try_to_migrate()
                                    zap the migration entry
                                    unlink_anon_vmas()
                                      last VMA: free the anon_vma
    migrate_device_finalize()
      remove_migration_ptes()
        /* locks and walks the freed anon_vma */

If munmap() runs before try_to_migrate() instead, the folio is no longer
mapped and gets no migration entry. The walk still runs, and follows the
stale folio->mapping to the freed anon_vma.

Resolve this by pinning the anon rmap of each folio which possesses one in
migrate_device_range(), migrate_device_pfns() and
migrate_device_coherent_folio() before its PTEs are replaced, and
unpinning it once the walk is done.

A folio which is no longer mapped cannot be pinned and has no migration
entries to remove, so skip it rather than migrating it.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: e778406b40db ("mm/migrate_device.c: add migrate_device_range()")
Cc: <stable@vger.kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 include/linux/migrate.h |  1 +
 mm/migrate_device.c     | 39 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/include/linux/migrate.h b/include/linux/migrate.h
index 78424b3824c2..c6cce7925b9b 100644
--- a/include/linux/migrate.h
+++ b/include/linux/migrate.h
@@ -136,6 +136,7 @@ static inline int migrate_misplaced_folio(struct folio *folio, int node)
 #define MIGRATE_PFN_MIGRATE	(1UL << 1)
 #define MIGRATE_PFN_WRITE	(1UL << 3)
 #define MIGRATE_PFN_COMPOUND	(1UL << 4)
+#define MIGRATE_PFN_ANON_RMAP	(1UL << 5)	/* mm internal */
 #define MIGRATE_PFN_SHIFT	6
 
 static inline struct page *migrate_pfn_to_page(unsigned long mpfn)
diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 7ebd02ec303b..5fe11dd01291 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -552,6 +552,27 @@ static bool migrate_vma_check_page(struct page *page, struct page *fault_page)
 	return true;
 }
 
+/*
+ * Pin the anonymous rmap if present to allow migration entries to be safely
+ * removed. Returns true if the pin succeeded or no pin is required, and false
+ * if the folio is no longer mapped.
+ */
+static bool pin_anon_rmap(struct folio *folio, unsigned long *src_pfn)
+{
+	if (!folio_test_anon(folio) || folio_test_ksm(folio))
+		return true;
+	if (!folio_get_anon_vma(folio)) /* Pin the anon rmap. */
+		return false;
+	*src_pfn |= MIGRATE_PFN_ANON_RMAP;
+	return true;
+}
+
+static void unpin_anon_rmap(struct folio *folio, unsigned long src_pfn)
+{
+	if (src_pfn & MIGRATE_PFN_ANON_RMAP)
+		put_anon_vma(folio_anon_vma(folio)); /* Unpin the anon rmap. */
+}
+
 /*
  * Unmaps pages for migration. Returns number of source pfns marked as
  * migrating.
@@ -633,6 +654,7 @@ static unsigned long migrate_device_unmap(unsigned long *src_pfns,
 
 		folio = page_folio(page);
 		remove_migration_ptes(folio, folio, 0);
+		unpin_anon_rmap(folio, src_pfns[i]);
 
 		src_pfns[i] = 0;
 		if (fault_folio != folio)
@@ -1335,6 +1357,7 @@ static void __migrate_device_finalize(unsigned long *src_pfns,
 		if (!folio_is_zone_device(dst))
 			folio_add_lru(dst);
 		remove_migration_ptes(src, dst, 0);
+		unpin_anon_rmap(src, src_pfns[i]);
 		if (fault_folio != src)
 			folio_unlock(src);
 		folio_put(src);
@@ -1384,6 +1407,7 @@ EXPORT_SYMBOL(migrate_vma_finalize);
 
 static unsigned long migrate_device_pfn_lock(unsigned long pfn)
 {
+	unsigned long src_pfn = migrate_pfn(pfn) | MIGRATE_PFN_MIGRATE;
 	struct folio *folio;
 
 	folio = folio_get_nontail_page(pfn_to_page(pfn));
@@ -1395,7 +1419,13 @@ static unsigned long migrate_device_pfn_lock(unsigned long pfn)
 		return 0;
 	}
 
-	return migrate_pfn(pfn) | MIGRATE_PFN_MIGRATE;
+	if (!pin_anon_rmap(folio, &src_pfn)) {
+		folio_unlock(folio);
+		folio_put(folio);
+		return 0;
+	}
+
+	return src_pfn;
 }
 
 /**
@@ -1431,6 +1461,7 @@ int migrate_device_range(unsigned long *src_pfns, unsigned long start,
 		nr = folio_nr_pages(folio);
 		if (nr > npages - i) {
 			if (src_pfns[i] & MIGRATE_PFN_MIGRATE) {
+				unpin_anon_rmap(folio, src_pfns[i]);
 				folio_unlock(folio);
 				folio_put(folio);
 			}
@@ -1474,6 +1505,7 @@ int migrate_device_pfns(unsigned long *src_pfns, unsigned long npages)
 		nr = folio_nr_pages(folio);
 		if (nr > npages - i) {
 			if (src_pfns[i] & MIGRATE_PFN_MIGRATE) {
+				unpin_anon_rmap(folio, src_pfns[i]);
 				folio_unlock(folio);
 				folio_put(folio);
 			}
@@ -1509,6 +1541,11 @@ int migrate_device_coherent_folio(struct folio *folio)
 
 	folio_lock(folio);
 	src_pfn = migrate_pfn(folio_pfn(folio)) | MIGRATE_PFN_MIGRATE;
+	if (!pin_anon_rmap(folio, &src_pfn)) {
+		folio_unlock(folio);
+		folio_put(folio);
+		return -EBUSY;
+	}
 
 	/*
 	 * We don't have a VMA and don't need to walk the page tables to find

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 10/13] mm/migrate_device: refuse to migrate shadow stacks
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (8 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 09/13] mm/migrate_device: fix use-after-free of the anon rmap Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 21:15   ` Balbir Singh
  2026-11-11 18:13 ` [PATCH 11/13] percpu: fix contig hints that are too low Lorenzo Stoakes (ARM)
                   ` (2 subsequent siblings)
  12 siblings, 1 reply; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	stable

From: Chris Mason <mason@kernel.org>

The kernel blocks ordinary writes to a shadow stack. But pte_write() is
true for a shadow stack entry, and migrate_vma_setup() sets
MIGRATE_PFN_WRITE for each page where pte_write() is true.

Notably, the nouveau GPU driver takes that flag as permission to map its
copy of the page writable for the GPU, meaning that the next CPU access
migrates the page back into the shadow stack, and the page then holds what
the GPU wrote.

Be consistent and disallow this for migration.

Fixes: 54007f818206 ("mm: Introduce VM_SHADOW_STACK for shadow stack memory")
Cc: <stable@vger.kernel.org>
Signed-off-by: Chris Mason <mason@kernel.org>
[ ljs: removed unnecessary comment, refined commit msg ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/migrate_device.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 5fe11dd01291..a59864f2e9a6 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -759,6 +759,8 @@ int migrate_vma_setup(struct migrate_vma *args)
 	/* The migrate_vma page walk has no hugetlb support. */
 	if (!vma_is_mm_backed(vma) || vma_is_hugetlb(vma) || vma_is_dax(vma))
 		return -EINVAL;
+	if (vma_test_single_mask(vma, VMA_SHADOW_STACK))
+		return -EINVAL;
 	if (nr_pages <= 0)
 		return -EINVAL;
 	if (args->start < vma->vm_start || args->start >= vma->vm_end)

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 11/13] percpu: fix contig hints that are too low
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (9 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 10/13] mm/migrate_device: refuse to migrate shadow stacks Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 12/13] percpu: search the whole reserved chunk Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 13/13] percpu: queue the balance work on an unbound workqueue Lorenzo Stoakes (ARM)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM)

pcpu_find_block_fit() skips a chunk or block whose contig_hint is too
small for the request, so a contig_hint must never be smaller than the
largest free area.

If it is, atomic and reserved chunk allocations can fail spuriously, and
others can create a chunk which is not needed.

There are three ways in which this can happen:

Firstly, when an allocation breaks a contig_hint, both
pcpu_block_refresh_hint() and pcpu_chunk_refresh_hint() promote the
scan_hint to contig_hint and scan only what follows it. However a free
can set the scan_hint regardless of what precedes it:

    free areas    A = 10 bits, then C = 50 bits
    hints         contig_hint = C
                  scan_hint = 0, cleared by an earlier allocation
    free B        5 bits between A and C: scan_hint = B
    allocate C    contig_hint = B, the scan starts after B
    result        contig_hint = 5, but A has 10 bits

Secondly, when a free stays within one block,
pcpu_block_update_hint_free() passes the end of the free area to the
chunk hints as a block offset rather than a chunk offset, so in any block
but the first the chunk hints are not updated at all.

Thirdly, that free area stops at the block boundary, even if the free
bits continue into a neighbouring block.

Fix these by always rescanning when a contig_hint is broken, passing the
correct offset, and refreshing the chunk hints whenever a free area
reaches a block boundary.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: da3afdd5bb54 ("percpu: use block scan_hint to only scan forward")
Fixes: 92c14cab4326 ("percpu: convert chunk hints to be based on pcpu_block_md")
Fixes: d33d9f3dd96b ("percpu: use chunk scan_hint to skip some scanning")
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/percpu.c | 59 +++++++++++++----------------------------------------------
 1 file changed, 13 insertions(+), 46 deletions(-)

diff --git a/mm/percpu.c b/mm/percpu.c
index d7883507dc15..dfd3ff0a1ac0 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -733,30 +733,16 @@ static void pcpu_block_update_scan(struct pcpu_chunk *chunk, int bit_off,
 /**
  * pcpu_chunk_refresh_hint - updates metadata about a chunk
  * @chunk: chunk of interest
- * @full_scan: if we should scan from the beginning
  *
  * Iterates over the metadata blocks to find the largest contig area.
- * A full scan can be avoided on the allocation path as this is triggered
- * if we broke the contig_hint.  In doing so, the scan_hint will be before
- * the contig_hint or after if the scan_hint == contig_hint.  This cannot
- * be prevented on freeing as we want to find the largest area possibly
- * spanning blocks.
  */
-static void pcpu_chunk_refresh_hint(struct pcpu_chunk *chunk, bool full_scan)
+static void pcpu_chunk_refresh_hint(struct pcpu_chunk *chunk)
 {
 	struct pcpu_block_md *chunk_md = &chunk->chunk_md;
-	int bit_off, bits;
+	int bit_off = chunk_md->first_free, bits;
 
-	/* promote scan_hint to contig_hint */
-	if (!full_scan && chunk_md->scan_hint) {
-		bit_off = chunk_md->scan_hint_start + chunk_md->scan_hint;
-		chunk_md->contig_hint_start = chunk_md->scan_hint_start;
-		chunk_md->contig_hint = chunk_md->scan_hint;
-		chunk_md->scan_hint = 0;
-	} else {
-		bit_off = chunk_md->first_free;
-		chunk_md->contig_hint = 0;
-	}
+	chunk_md->contig_hint = 0;
+	chunk_md->scan_hint = 0;
 
 	pcpu_for_each_md_free_region(chunk, bit_off, bits)
 		pcpu_block_update(chunk_md, bit_off, bit_off + bits);
@@ -774,19 +760,10 @@ static void pcpu_block_refresh_hint(struct pcpu_chunk *chunk, int index)
 {
 	struct pcpu_block_md *block = chunk->md_blocks + index;
 	unsigned long *alloc_map = pcpu_index_alloc_map(chunk, index);
-	unsigned int start, end;	/* region start, region end */
-
-	/* promote scan_hint to contig_hint */
-	if (block->scan_hint) {
-		start = block->scan_hint_start + block->scan_hint;
-		block->contig_hint_start = block->scan_hint_start;
-		block->contig_hint = block->scan_hint;
-		block->scan_hint = 0;
-	} else {
-		start = block->first_free;
-		block->contig_hint = 0;
-	}
+	unsigned int start = block->first_free, end;
 
+	block->contig_hint = 0;
+	block->scan_hint = 0;
 	block->right_free = 0;
 
 	/* iterate over free areas and update the contig hints */
@@ -938,7 +915,7 @@ static void pcpu_block_update_hint_alloc(struct pcpu_chunk *chunk, int bit_off,
 				chunk_md->contig_hint,
 				bit_off,
 				bit_off + bits))
-		pcpu_chunk_refresh_hint(chunk, false);
+		pcpu_chunk_refresh_hint(chunk);
 }
 
 /**
@@ -952,12 +929,8 @@ static void pcpu_block_update_hint_alloc(struct pcpu_chunk *chunk, int bit_off,
  * forward and backward to determine the extent of the free area.  This is
  * capped at the boundary of blocks.
  *
- * A chunk update is triggered if a page becomes free, a block becomes free,
- * or the free spans across blocks.  This tradeoff is to minimize iterating
- * over the block metadata to update chunk_md->contig_hint.
- * chunk_md->contig_hint may be off by up to a page, but it will never be more
- * than the available space.  If the contig hint is contained in one block, it
- * will be accurate.
+ * The chunk metadata is refreshed if the free area reaches a block boundary,
+ * as it may then extend into neighbouring blocks.
  */
 static void pcpu_block_update_hint_free(struct pcpu_chunk *chunk, int bit_off,
 					int bits)
@@ -1042,18 +1015,12 @@ static void pcpu_block_update_hint_free(struct pcpu_chunk *chunk, int bit_off,
 	if (nr_empty_pages)
 		pcpu_update_empty_pages(chunk, nr_empty_pages);
 
-	/*
-	 * Refresh chunk metadata when the free makes a block free or spans
-	 * across blocks.  The contig_hint may be off by up to a page, but if
-	 * the contig_hint is contained in a block, it will be accurate with
-	 * the else condition below.
-	 */
-	if (((end - start) >= PCPU_BITMAP_BLOCK_BITS) || s_index != e_index)
-		pcpu_chunk_refresh_hint(chunk, true);
+	if (!start || end == PCPU_BITMAP_BLOCK_BITS || s_index != e_index)
+		pcpu_chunk_refresh_hint(chunk);
 	else
 		pcpu_block_update(&chunk->chunk_md,
 				  pcpu_block_off_to_off(s_index, start),
-				  end);
+				  pcpu_block_off_to_off(s_index, end));
 }
 
 /**

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 12/13] percpu: search the whole reserved chunk
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (10 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 11/13] percpu: fix contig hints that are too low Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  2026-11-11 18:13 ` [PATCH 13/13] percpu: queue the balance work on an unbound workqueue Lorenzo Stoakes (ARM)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM),
	stable

Loading a module with static percpu data can fail with -ENOMEM even
though the reserved chunk has a free area that fits the data.

pcpu_find_block_fit() does not scan the allocation bitmap, rather it
tests the request against hints, starting with the contig_hint of the
chunk, which describes its largest free area. If that area is too small
once aligned, the search gives up even if a smaller free area fits.

Consider:

    request:      256 bytes, aligned to 256 bytes
    contig_hint:  260 bytes, starts 4 bytes after a 256-byte boundary,
                  so 8 bytes remain after alignment
    also free:    256 bytes, starts on a 256-byte boundary

Ordinarily this is fine as the allocator moves on to the next chunk or
creates a new one, but the static percpu data of modules can only come
from the reserved chunk.

Fix this by searching the allocation bitmap of the reserved chunk
directly rather than using hints.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 13f966373f92 ("percpu: skip chunks if the alloc does not fit in the contig hint")
Fixes: b4c2116cfae6 ("percpu: update pcpu_find_block_fit to use an iterator")
Cc: <stable@vger.kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/percpu.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/mm/percpu.c b/mm/percpu.c
index dfd3ff0a1ac0..f87813e6ad73 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -1771,8 +1771,11 @@ void __percpu *pcpu_alloc_noprof(size_t size, size_t align, bool reserved,
 	if (reserved && pcpu_reserved_chunk) {
 		chunk = pcpu_reserved_chunk;
 
-		off = pcpu_find_block_fit(chunk, bits, bit_align, is_atomic);
-		if (off < 0) {
+		/* No other chunk to fall back on, so don't rely on hints. */
+		off = bitmap_find_next_zero_area(chunk->alloc_map,
+						 pcpu_chunk_map_bits(chunk), 0,
+						 bits, bit_align - 1);
+		if (off >= pcpu_chunk_map_bits(chunk)) {
 			err = "alloc from reserved chunk failed";
 			goto fail_unlock;
 		}

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 13/13] percpu: queue the balance work on an unbound workqueue
  2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
                   ` (11 preceding siblings ...)
  2026-11-11 18:13 ` [PATCH 12/13] percpu: search the whole reserved chunk Lorenzo Stoakes (ARM)
@ 2026-11-11 18:13 ` Lorenzo Stoakes (ARM)
  12 siblings, 0 replies; 18+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-11 18:13 UTC (permalink / raw)
  To: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Lorenzo Stoakes (ARM)

From: Chris Mason <mason@kernel.org>

A burst of small atomic per-CPU allocations can fail with -ENOMEM while the
machine has free memory and idle CPUs, as atomic per-CPU allocations are
only served from pages that are already populated, and those can be used up
before a worker gets to populate more.

Consider:

    caller on CPU 1                     kworker bound to CPU 1
    ===============                     ======================
    allocates 8 bytes, which leaves
    one empty page and 4088 bytes
      schedule_work()                   woken, waits for CPU 1
    allocates 8 bytes, 1023 times
    next allocation: "no space left"
    gives up CPU 1                      populates pages

The allocator keeps a few empty populated pages in reserve, and the work
item pcpu_balance_work refills them.

pcpu_alloc_noprof() queues the work item using schedule_work(), so only a
worker bound to the CPU of the caller can run the work item.

The issue is that the caller keeps that CPU - a task keeps it for the rest
of its time slice, and a softirq keeps it until the softirq ends.

In that time the caller can use up the reserve, resulting in an atomic
per-CPU allocation failing.

Fix the issue by queueing the work item on an unbound workqueue so that
another CPU can populate pages while the caller keeps allocating.

Fixes: 1a4d76076cda ("percpu: implement asynchronous chunk population")
Signed-off-by: Chris Mason <mason@kernel.org>
[ ljs: tweaked commit message, comment ]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
 mm/percpu.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/mm/percpu.c b/mm/percpu.c
index f87813e6ad73..39cba1710383 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -200,8 +200,9 @@ static bool pcpu_atomic_alloc_failed;
 
 static void pcpu_schedule_balance_work(void)
 {
+	/* Unbound, so another CPU can populate pgs if caller keeps this one. */
 	if (pcpu_async_enabled)
-		schedule_work(&pcpu_balance_work);
+		queue_work(system_dfl_wq, &pcpu_balance_work);
 }
 
 /**

-- 
2.56.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 04/13] mm/migrate_device: allocate THP deferred split heads
  2026-11-11 18:13 ` [PATCH 04/13] mm/migrate_device: allocate THP deferred split heads Lorenzo Stoakes (ARM)
@ 2026-11-11 19:30   ` Kiryl Shutsemau
  2026-11-11 19:40     ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 18+ messages in thread
From: Kiryl Shutsemau @ 2026-11-11 19:30 UTC (permalink / raw)
  To: Lorenzo Stoakes (ARM)
  Cc: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Yu-cheng Yu, Dave Hansen, Dennis Zhou,
	Tejun Heo, Christoph Lameter, Peng Fan, Josef Bacik, Chris Mason,
	linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Josef Bacik, stable

On Wed, Nov 11, 2026 at 06:13:18PM +0000, Lorenzo Stoakes (ARM) wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> A THP that device migration maps into an empty PMD can end up on the wrong
> deferred split list when it is later queued for a deferred split.
> 
> This occurs if the memcg has no heads yet (resulting in a warning if
> CONFIG_DEBUG_VM is set), where deferred_split_folio() will then queue the
> folio on the parent's list.
> 
> Once a later THP fault in the same memcg allocates the heads,
> __folio_unqueue_deferred_split() and the split path resolve folio_memcg()
> to the memcg's own, now existing, list and take its lock.  They then
> list_del_init() a folio that is linked on the parent's list, racing with
> anything holding the parent's lock, and both lists' nr_items counts go
> wrong.
> 
> Resolve the issue by calling folio_memcg_alloc_deferred() after the charge
> in migrate_vma_insert_huge_pmd_page() and falling back like a failed charge
> if that fails, as __do_huge_pmd_anonymous_page() does.
> 
> This was originally discovered by code inspection.
> 
> Fixes: fafaeceb89a5 ("mm: switch deferred split shrinker to list_lru")
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> [ ljs: tweak commit message, remove unnecessary comment ]
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

mm-new already has this: "mm/migrate_device: allocate deferred split
sublists for new huge folios", same Fixes: tag.

Hm?

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 04/13] mm/migrate_device: allocate THP deferred split heads
  2026-11-11 19:30   ` Kiryl Shutsemau
@ 2026-11-11 19:40     ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 18+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-11 19:40 UTC (permalink / raw)
  To: Kiryl Shutsemau, Lorenzo Stoakes (ARM)
  Cc: Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, David Rientjes, Shakeel Butt,
	Andrew Morton, Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Yu-cheng Yu, Dave Hansen, Dennis Zhou,
	Tejun Heo, Christoph Lameter, Peng Fan, Josef Bacik, Chris Mason,
	linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, Josef Bacik, stable

On 11/11/26 20:30, Kiryl Shutsemau wrote:
> On Wed, Nov 11, 2026 at 06:13:18PM +0000, Lorenzo Stoakes (ARM) wrote:
>> From: Josef Bacik <josef@toxicpanda.com>
>>
>> A THP that device migration maps into an empty PMD can end up on the wrong
>> deferred split list when it is later queued for a deferred split.
>>
>> This occurs if the memcg has no heads yet (resulting in a warning if
>> CONFIG_DEBUG_VM is set), where deferred_split_folio() will then queue the
>> folio on the parent's list.
>>
>> Once a later THP fault in the same memcg allocates the heads,
>> __folio_unqueue_deferred_split() and the split path resolve folio_memcg()
>> to the memcg's own, now existing, list and take its lock.  They then
>> list_del_init() a folio that is linked on the parent's list, racing with
>> anything holding the parent's lock, and both lists' nr_items counts go
>> wrong.
>>
>> Resolve the issue by calling folio_memcg_alloc_deferred() after the charge
>> in migrate_vma_insert_huge_pmd_page() and falling back like a failed charge
>> if that fails, as __do_huge_pmd_anonymous_page() does.
>>
>> This was originally discovered by code inspection.
>>
>> Fixes: fafaeceb89a5 ("mm: switch deferred split shrinker to list_lru")
>> Cc: <stable@vger.kernel.org>
>> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
>> [ ljs: tweak commit message, remove unnecessary comment ]
>> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> 
> mm-new already has this: "mm/migrate_device: allocate deferred split
> sublists for new huge folios", same Fixes: tag.
> 
> Hm?

I assume that's one from Andrew that he created after a security report (where I
was CCed and the reporter didn't follow up), but he never sent it out officially.

So let's review this one here. It's supposed to go through my tree either way :)

-- 
Cheers,

David

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 10/13] mm/migrate_device: refuse to migrate shadow stacks
  2026-11-11 18:13 ` [PATCH 10/13] mm/migrate_device: refuse to migrate shadow stacks Lorenzo Stoakes (ARM)
@ 2026-11-11 21:15   ` Balbir Singh
  0 siblings, 0 replies; 18+ messages in thread
From: Balbir Singh @ 2026-11-11 21:15 UTC (permalink / raw)
  To: Lorenzo Stoakes (ARM)
  Cc: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, David Rientjes,
	Shakeel Butt, Andrew Morton, Jann Horn, Pedro Falcato,
	Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason, linux-mm, linux-kernel, linux-fsdevel,
	Borislav Petkov (AMD), Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, stable

On Wed, Nov 11, 2026 at 06:13:24PM +0000, Lorenzo Stoakes (ARM) wrote:
> From: Chris Mason <mason@kernel.org>
> 
> The kernel blocks ordinary writes to a shadow stack. But pte_write() is
> true for a shadow stack entry, and migrate_vma_setup() sets
> MIGRATE_PFN_WRITE for each page where pte_write() is true.
> 
> Notably, the nouveau GPU driver takes that flag as permission to map its
> copy of the page writable for the GPU, meaning that the next CPU access
> migrates the page back into the shadow stack, and the page then holds what
> the GPU wrote.
> 
> Be consistent and disallow this for migration.
> 
> Fixes: 54007f818206 ("mm: Introduce VM_SHADOW_STACK for shadow stack memory")
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Chris Mason <mason@kernel.org>
> [ ljs: removed unnecessary comment, refined commit msg ]
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
>  mm/migrate_device.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/mm/migrate_device.c b/mm/migrate_device.c
> index 5fe11dd01291..a59864f2e9a6 100644
> --- a/mm/migrate_device.c
> +++ b/mm/migrate_device.c
> @@ -759,6 +759,8 @@ int migrate_vma_setup(struct migrate_vma *args)
>  	/* The migrate_vma page walk has no hugetlb support. */
>  	if (!vma_is_mm_backed(vma) || vma_is_hugetlb(vma) || vma_is_dax(vma))
>  		return -EINVAL;
> +	if (vma_test_single_mask(vma, VMA_SHADOW_STACK))
> +		return -EINVAL;
>  	if (nr_pages <= 0)
>  		return -EINVAL;
>  	if (args->start < vma->vm_start || args->start >= vma->vm_end)
>

Reviewed-by: Balbir Singh <balbirs@nvidia.com>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 08/13] mm/migrate_device: fix ksmd oops on a migrated KSM page
  2026-11-11 18:13 ` [PATCH 08/13] mm/migrate_device: fix ksmd oops on a migrated KSM page Lorenzo Stoakes (ARM)
@ 2026-11-11 21:54   ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 18+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-11 21:54 UTC (permalink / raw)
  To: Lorenzo Stoakes (ARM),
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, David Rientjes, Shakeel Butt,
	Andrew Morton, Jann Horn, Pedro Falcato, Matthew Wilcox (Oracle),
	Jan Kara, Nhat Pham, Johannes Weiner, Zi Yan, Matthew Brost,
	Joshua Hahn, Byungchul Park, Gregory Price, Ying Huang,
	Alistair Popple, Usama Arif, Kairui Song, Balbir Singh,
	Jérôme Glisse, Kiryl Shutsemau, Yu-cheng Yu,
	Dave Hansen, Dennis Zhou, Tejun Heo, Christoph Lameter, Peng Fan,
	Josef Bacik, Chris Mason
  Cc: linux-mm, linux-kernel, linux-fsdevel, Borislav Petkov (AMD),
	Mark Brown, Dennis Zhou (Facebook),
	Rick Edgecombe, stable

On 11/11/26 19:13, Lorenzo Stoakes (ARM) wrote:
> From: Chris Mason <mason@kernel.org>
> 
> After a KSM page is migrated to device private memory, ksmd can oops,
> and KSM then stops until reboot. A process that maps the page can also
> loop in page faults when it next accesses the page.
> 
> The problem arises because KSM doesn't know not to try to look at device
> private memory that does not exist in the direct map.
> 
> Disallow this from happening by explicitly testing for this scenario in
> __migrate_device_pages().
> 
> Fixes: a5430dda8a3a ("mm/migrate: support un-addressable ZONE_DEVICE page in migration")
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Chris Mason <mason@kernel.org>
> [ ljs: tweak commit message, comment ]
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
>  mm/migrate_device.c | 9 +++++++++
>  1 file changed, 9 insertions(+)
> 
> diff --git a/mm/migrate_device.c b/mm/migrate_device.c
> index 45e571dbfb4c..7ebd02ec303b 100644
> --- a/mm/migrate_device.c
> +++ b/mm/migrate_device.c
> @@ -1205,6 +1205,15 @@ static void __migrate_device_pages(unsigned long *src_pfns,
>  		}
>  
>  
> +		/*
> +		 * KSM doesn't know not to try to read device private pages, so
> +		 * disallow the combination.
> +		 */
> +		if (folio_is_device_private(newfolio) && folio_test_ksm(folio)) {
> +			src_pfns[i] &= ~MIGRATE_PFN_MIGRATE;
> +			goto next;

In general, LGTM. I was just wondering if we could reject that earlier. because
apparently we already allocated a destination folio :)

-- 
Cheers,

David

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-11-11 21:54 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 18:13 [PATCH 00/13] mm: batch of (urgentish?) fixes in oomk, cachestat, dev-migrate, percpu Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 01/13] mm/mmap: free gathered pages before MMF_OOM_SKIP Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 02/13] mm: cachestat: count each page only once Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 03/13] mm: cachestat: restart the walk after each entry Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 04/13] mm/migrate_device: allocate THP deferred split heads Lorenzo Stoakes (ARM)
2026-11-11 19:30   ` Kiryl Shutsemau
2026-11-11 19:40     ` David Hildenbrand (Arm)
2026-11-11 18:13 ` [PATCH 05/13] mm/migrate_device: don't collect a range twice Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 06/13] mm/migrate_device: don't let go of the fault page Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 07/13] mm/migrate_device: look up fault folio after a split Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 08/13] mm/migrate_device: fix ksmd oops on a migrated KSM page Lorenzo Stoakes (ARM)
2026-11-11 21:54   ` David Hildenbrand (Arm)
2026-11-11 18:13 ` [PATCH 09/13] mm/migrate_device: fix use-after-free of the anon rmap Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 10/13] mm/migrate_device: refuse to migrate shadow stacks Lorenzo Stoakes (ARM)
2026-11-11 21:15   ` Balbir Singh
2026-11-11 18:13 ` [PATCH 11/13] percpu: fix contig hints that are too low Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 12/13] percpu: search the whole reserved chunk Lorenzo Stoakes (ARM)
2026-11-11 18:13 ` [PATCH 13/13] percpu: queue the balance work on an unbound workqueue Lorenzo Stoakes (ARM)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®