[PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF

From: Kiryl Shutsemau

Date: Tue Jul 14 2026 - 08:24:06 EST


From: "Kiryl Shutsemau (Meta)" <kas@xxxxxxxxxx>

This fixes a use-after-free of the inode in __folio_split(), reported by
Hao Zhang, and cleans up the split helpers the fix leaves unused.

The bug: __folio_split() keeps dereferencing folio->mapping after the
split (shmem_uncharge(), i_mmap_unlock_read()), relying on the locked
@lock_at folio staying in the page cache to hold off inode eviction.
memory_failure() splits at a poisoned *tail*, and when that tail is
beyond EOF the split drops it from the page cache yet still returns it
locked -- the inode pin is gone, and a racing final iput() can evict and
free the inode mid-split.

v1 [1] pinned the inode with igrab()/iput() inside __folio_split().
Sashiko pointed out that the iput() under the folio lock can
self-deadlock: if it drops the last reference, eviction blocks on the
very folio the caller still holds locked. Thanks!

v2 drops that approach for two smaller changes:

- memory_failure() is the only caller that passed a tail as the split
anchor. Patch 1 makes it lock and split at the head instead. The head
is piece 0, which the beyond-EOF drop loop never removes, so it always
stays in the page cache and keeps the inode pinned. This alone fixes
the UAF.

- Patch 2 makes the requirement explicit in __folio_split(): refuse the
split with -EBUSY when the anchor is beyond EOF, so no future caller
can reintroduce it. No refcounting, no lock reordering.

Both are tagged for stable; patch 1 comes first so patch 2's guard never
changes memory_failure() behaviour.

Patches 3-5 are cleanups (not for stable). Patch 1 leaves
split_huge_page_to_order() with no callers, and the audit found
can_split_folio() (dead) and split_folio_to_list_to_order() (only ever
called with a NULL list) alongside it. Remove/fold them.

Only compile- and boot-tested: I could confirm memory_failure() enters
__folio_split() with a tail anchor (the pattern patch 1 removes) and that
the fixed kernel is stable, but not land the exact UAF -- the race window
is narrow enough that my instrumentation could not open it reliably.

[1] https://lore.kernel.org/all/20260713170915.239819-1-kirill@xxxxxxxxxxxxx

Kiryl Shutsemau (Meta) (5):
mm/memory-failure: keep the folio, not the poisoned subpage, locked
across split
mm/huge_memory: refuse to split a file folio when the anchor is beyond
EOF
mm/huge_memory: remove unused split_huge_page_to_order()
mm/huge_memory: remove unused can_split_folio()
mm/huge_memory: fold split_folio_to_list_to_order() into
split_folio_to_order()

include/linux/huge_mm.h | 22 +---------------------
mm/huge_memory.c | 14 ++++++++++++++
mm/memory-failure.c | 13 ++++++++++---
3 files changed, 25 insertions(+), 24 deletions(-)


base-commit: 0e35b9b6ec0ffcc5e23cbdec09f5c622ad532b53
--
2.54.0