[PATCH] mm/huge_memory: avoid TOCTOU race in min_order_for_split()

[email protected] Tue, 4 Aug 2026 11:58:28 +0800
Newsgroups org.kvack.linux-mm
Message-ID <[email protected]>
From: liyouhong <[email protected]>

min_order_for_split() reads folio->mapping twice without any
synchronization. Concurrent truncate or invalidate can clear
folio->mapping between the check and subsequent function call.
Even with a held folio reference preventing the folio from
being freed, folio->mapping can still be overwritten to NULL. This
TOCTOU race allows passing a NULL mapping into mapping_min_folio_order(),
which leads to a NULL pointer dereference.

Cache folio->mapping to a local variable using READ_ONCE() to guarantee a
single memory load and remove the race window.

Link: https://sashiko.dev/#/patchset/[email protected]
Signed-off-by: liyouhong <[email protected]>
---
 mm/huge_memory.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2bccb0a53a0a..54d4261a2a15 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4284,19 +4284,23 @@ int folio_split(struct folio *folio, unsigned int new_order,
  */
 unsigned int min_order_for_split(struct folio *folio)
 {
+	struct address_space *mapping;
+
 	if (folio_test_anon(folio))
 		return 0;
 
+	mapping = READ_ONCE(folio->mapping);
+
 	/*
 	 * If the folio got truncated, we don't know the previous mapping and
 	 * consequently the old min order. But it doesn't matter, as any split
 	 * attempt will immediately fail with -EBUSY as the folio cannot get
 	 * split until freed.
 	 */
-	if (!folio->mapping)
+	if (!mapping)
 		return 0;
 
-	return mapping_min_folio_order(folio->mapping);
+	return mapping_min_folio_order(mapping);
 }
 
 int split_folio_to_list(struct folio *folio, struct list_head *list)
-- 
2.25.1