[PATCH] mm: migrate: transfer large_rmappable flag in folio_migrate_flags()

From: Usama Arif

Date: Wed Mar 11 2026 - 09:31:10 EST


folio_migrate_flags() transfers folio state from source to destination
during migration, but does not transfer the large_rmappable flag.

Migration allocators like alloc_migration_target() and
alloc_misplaced_dst_folio() use __folio_alloc() directly without
wrapping the result in page_rmappable_folio(), so the destination folio
never gets large_rmappable set.

This becomes a problem when a folio on the deferred split queue is
migrated: the destination folio can be added to the deferred split queue
via deferred_split_folio() (which does not check large_rmappable), but
when the folio is later freed, folio_unqueue_deferred_split() bails out
early because large_rmappable is not set:

if (folio_order(folio) <= 1 || !folio_test_large_rmappable(folio))
return false;

This leaves a stale entry on the deferred split queue, leading to
use-after-free when the shrinker walks the list.

Fix this by transferring large_rmappable in folio_migrate_flags(),
consistent with how all other folio flags are handled.

Fixes: dafff3f4c850 ("mm: split underused THPs")
Signed-off-by: Usama Arif <usama.arif@xxxxxxxxx>
---
mm/migrate.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/mm/migrate.c b/mm/migrate.c
index 3380021fd3db..ee1c7bc851dd 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -846,6 +846,9 @@ void folio_migrate_flags(struct folio *newfolio, struct folio *folio)
folio_copy_owner(newfolio, folio);
pgalloc_tag_swap(newfolio, folio);

+ if (folio_test_large_rmappable(folio))
+ folio_set_large_rmappable(newfolio);
+
mem_cgroup_migrate(folio, newfolio);
}
EXPORT_SYMBOL(folio_migrate_flags);
--
2.52.0