All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF
@ 2026-07-14 12:23 Kiryl Shutsemau
  2026-07-14 12:23 ` [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split Kiryl Shutsemau
                   ` (5 more replies)
  0 siblings, 6 replies; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 12:23 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Miaohe Lin,
	Naoya Horiguchi
  Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, Kiryl Shutsemau (Meta)

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

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@shutemov.name

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



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

* [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 12:23 [PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF Kiryl Shutsemau
@ 2026-07-14 12:23 ` Kiryl Shutsemau
  2026-07-14 13:01   ` David Hildenbrand (Arm)
  2026-07-14 13:05   ` Kiryl Shutsemau
  2026-07-14 12:23 ` [PATCH v2 2/5] mm/huge_memory: refuse to split a file folio when the anchor is beyond EOF Kiryl Shutsemau
                   ` (4 subsequent siblings)
  5 siblings, 2 replies; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 12:23 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Miaohe Lin,
	Naoya Horiguchi
  Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, Kiryl Shutsemau (Meta), stable

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

try_to_split_thp_page() locked the poisoned page and passed it to
split_huge_page_to_order(), which returns that very page locked to the
caller.  For a tail page that means __folio_split() runs with @lock_at
pointing into the middle of the folio.

__folio_split() dereferences the mapping after the split completes
(shmem_uncharge(), i_mmap_unlock_read()).  The only thing keeping the
inode alive across that is the locked @lock_at folio: while it stays in
the page cache, eviction cannot complete.

But a tail @lock_at can lie beyond EOF -- e.g. part of a shmem THP that
reaches past i_size while the file is being truncated.  The split then
drops it from the page cache yet still returns it locked, so the pin is
gone and a racing final iput() can evict and RCU-free the inode while
__folio_split() is still running:

  BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790
   i_mmap_unlock_read include/linux/fs.h:537 [inline]
   __folio_split+0x732/0x1640 mm/huge_memory.c:4100
   try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675
   memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470

  Freed by task 4601:
   shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177
   evict+0x57f/0xac0 fs/inode.c:870

Split the folio as a folio, via split_folio_to_order(), so the head is
the anchor left locked.  The head is piece 0, which the beyond-EOF drop
loop never removes (it starts at folio_next(folio)), so the split always
leaves it in the page cache and the inode stays pinned for the whole of
__folio_split().  memory_failure() and soft offline re-lock the poisoned
subpage's folio themselves after the split, so they do not depend on it
being returned locked.

Reported-by: Hao Zhang <zhanghao1@kylinos.cn>
Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc
Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
Cc: <stable@vger.kernel.org>
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
 mm/memory-failure.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 51508a55c405..68d42cbed458 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1657,11 +1657,18 @@ static int identify_page_state(unsigned long pfn, struct page *p,
 static int try_to_split_thp_page(struct page *page, unsigned int new_order,
 		bool release)
 {
+	struct folio *folio = page_folio(page);
 	int ret;
 
-	lock_page(page);
-	ret = split_huge_page_to_order(page, new_order);
-	unlock_page(page);
+	/*
+	 * Lock and split at the head, not the poisoned subpage: __folio_split()
+	 * keeps the anchor folio locked and needs it to stay in the page cache
+	 * to pin the inode. A tail beyond EOF would be dropped yet returned
+	 * locked, losing that pin. The caller re-locks @page afterwards.
+	 */
+	folio_lock(folio);
+	ret = split_folio_to_order(folio, new_order);
+	folio_unlock(folio);
 
 	if (ret && release)
 		put_page(page);
-- 
2.54.0



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

* [PATCH v2 2/5] mm/huge_memory: refuse to split a file folio when the anchor is beyond EOF
  2026-07-14 12:23 [PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF Kiryl Shutsemau
  2026-07-14 12:23 ` [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split Kiryl Shutsemau
@ 2026-07-14 12:23 ` Kiryl Shutsemau
  2026-07-14 12:23 ` [PATCH v2 3/5] mm/huge_memory: remove unused split_huge_page_to_order() Kiryl Shutsemau
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 12:23 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Miaohe Lin,
	Naoya Horiguchi
  Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, Kiryl Shutsemau (Meta), stable

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

__folio_split() dereferences the mapping after the split completes:
shmem_uncharge(mapping->host) for folios dropped beyond EOF and
i_mmap_unlock_read(mapping) on the way out.  Nothing holds an inode
reference for that duration; the split relies on the caller's locked
@lock_at folio, while it is locked and present in the page cache, to keep
the inode alive through eviction's truncate_inode_pages_final().

If @lock_at lies beyond EOF, __folio_freeze_and_split_unmapped() removes
it from the page cache while keeping it locked for the caller.  That drops
the pin and lets a concurrent final iput() evict and free the inode under
the still-running split.  On the anon side __folio_split() already pins
its anchor explicitly (folio_get_anon_vma()); the file side's anchor was
always the locked in-cache folio, just never enforced.

The only in-tree caller that passed a beyond-EOF @lock_at was
memory_failure(), fixed in the previous patch to anchor on the head.  Make
the requirement explicit so it cannot be reintroduced: refuse the split
with -EBUSY when @lock_at is at or beyond the sampled EOF.  Such a folio
is racing truncation, so there is nothing useful to split; -EBUSY is
already handled by every caller.

The check uses the same @end sampled under the folio lock that the drop
loop uses, so it does not race the trimming it guards against.

Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
Cc: <stable@vger.kernel.org>
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
 mm/huge_memory.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2bccb0a53a0a..0e3ca7178d8c 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4065,6 +4065,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
 		if (shmem_mapping(mapping))
 			end = shmem_fallocend(mapping->host, end);
+
+		/*
+		 * @lock_at is returned locked to the caller, and while it is
+		 * locked and present in the page cache it is what keeps the
+		 * inode alive: the mapping is still dereferenced after the split
+		 * (shmem_uncharge(), i_mmap_unlock_read()).  If it lies beyond
+		 * EOF the split would drop it from the page cache while handing
+		 * it back locked, removing that pin.  Such a folio is racing
+		 * truncation and there is nothing useful to split; bail out.
+		 */
+		if (folio->index + folio_page_idx(folio, lock_at) >= end) {
+			ret = -EBUSY;
+			goto out_unlock;
+		}
 	}
 
 	/*
-- 
2.54.0



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

* [PATCH v2 3/5] mm/huge_memory: remove unused split_huge_page_to_order()
  2026-07-14 12:23 [PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF Kiryl Shutsemau
  2026-07-14 12:23 ` [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split Kiryl Shutsemau
  2026-07-14 12:23 ` [PATCH v2 2/5] mm/huge_memory: refuse to split a file folio when the anchor is beyond EOF Kiryl Shutsemau
@ 2026-07-14 12:23 ` Kiryl Shutsemau
  2026-07-14 12:23 ` [PATCH v2 4/5] mm/huge_memory: remove unused can_split_folio() Kiryl Shutsemau
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 12:23 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Miaohe Lin,
	Naoya Horiguchi
  Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, Kiryl Shutsemau (Meta)

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

The last caller, memory_failure()'s try_to_split_thp_page(), was
converted to the folio-native split_folio_to_order() in the previous
change.  split_huge_page_to_order() now has no users; the folio helpers
(split_folio_to_order(), split_folio_to_list_to_order()) cover uniform
splits and split_huge_page() covers the order-0 case.  Remove it.

No functional change.

Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
 include/linux/huge_mm.h | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index ad20f7f8c179..77483c613f84 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -414,10 +414,6 @@ static inline int split_huge_page_to_list_to_order(struct page *page, struct lis
 {
 	return __split_huge_page_to_list_to_order(page, list, new_order);
 }
-static inline int split_huge_page_to_order(struct page *page, unsigned int new_order)
-{
-	return split_huge_page_to_list_to_order(page, NULL, new_order);
-}
 
 static inline int split_huge_page(struct page *page)
 {
@@ -633,11 +629,6 @@ split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
 	VM_WARN_ON_ONCE_PAGE(1, page);
 	return -EINVAL;
 }
-static inline int split_huge_page_to_order(struct page *page, unsigned int new_order)
-{
-	VM_WARN_ON_ONCE_PAGE(1, page);
-	return -EINVAL;
-}
 static inline int split_huge_page(struct page *page)
 {
 	VM_WARN_ON_ONCE_PAGE(1, page);
-- 
2.54.0



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

* [PATCH v2 4/5] mm/huge_memory: remove unused can_split_folio()
  2026-07-14 12:23 [PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF Kiryl Shutsemau
                   ` (2 preceding siblings ...)
  2026-07-14 12:23 ` [PATCH v2 3/5] mm/huge_memory: remove unused split_huge_page_to_order() Kiryl Shutsemau
@ 2026-07-14 12:23 ` Kiryl Shutsemau
  2026-07-14 12:23 ` [PATCH v2 5/5] mm/huge_memory: fold split_folio_to_list_to_order() into split_folio_to_order() Kiryl Shutsemau
  2026-07-15  5:55 ` [syzbot ci] Re: mm: fix inode UAF when splitting a file folio past EOF syzbot ci
  5 siblings, 0 replies; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 12:23 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Miaohe Lin,
	Naoya Horiguchi
  Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, Kiryl Shutsemau (Meta)

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

can_split_folio() has no callers and no definition under
CONFIG_TRANSPARENT_HUGEPAGE; only the !THP stub remains, left behind by an
earlier cleanup.  Remove it.

No functional change.

Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
 include/linux/huge_mm.h | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index 77483c613f84..a38db095b5bd 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -617,11 +617,6 @@ thp_get_unmapped_area_vmflags(struct file *filp, unsigned long addr,
 	return 0;
 }
 
-static inline bool
-can_split_folio(struct folio *folio, int caller_pins, int *pextra_pins)
-{
-	return false;
-}
 static inline int
 split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
 		unsigned int new_order)
-- 
2.54.0



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

* [PATCH v2 5/5] mm/huge_memory: fold split_folio_to_list_to_order() into split_folio_to_order()
  2026-07-14 12:23 [PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF Kiryl Shutsemau
                   ` (3 preceding siblings ...)
  2026-07-14 12:23 ` [PATCH v2 4/5] mm/huge_memory: remove unused can_split_folio() Kiryl Shutsemau
@ 2026-07-14 12:23 ` Kiryl Shutsemau
  2026-07-15  5:55 ` [syzbot ci] Re: mm: fix inode UAF when splitting a file folio past EOF syzbot ci
  5 siblings, 0 replies; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 12:23 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Miaohe Lin,
	Naoya Horiguchi
  Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, Kiryl Shutsemau (Meta)

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

split_folio_to_list_to_order() had no direct callers; its only user was
split_folio_to_order(), which always passed a NULL list.  Fold it into
split_folio_to_order() and call split_huge_page_to_list_to_order()
directly.

No functional change.

Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
 include/linux/huge_mm.h | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index a38db095b5bd..8a10a886e62a 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -783,15 +783,9 @@ static inline bool is_pmd_order(unsigned int order)
 	return order == HPAGE_PMD_ORDER;
 }
 
-static inline int split_folio_to_list_to_order(struct folio *folio,
-		struct list_head *list, int new_order)
-{
-	return split_huge_page_to_list_to_order(&folio->page, list, new_order);
-}
-
 static inline int split_folio_to_order(struct folio *folio, int new_order)
 {
-	return split_folio_to_list_to_order(folio, NULL, new_order);
+	return split_huge_page_to_list_to_order(&folio->page, NULL, new_order);
 }
 
 /**
-- 
2.54.0



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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 12:23 ` [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split Kiryl Shutsemau
@ 2026-07-14 13:01   ` David Hildenbrand (Arm)
  2026-07-14 14:53     ` Kiryl Shutsemau
  2026-07-14 13:05   ` Kiryl Shutsemau
  1 sibling, 1 reply; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-14 13:01 UTC (permalink / raw)
  To: Kiryl Shutsemau, Andrew Morton, Lorenzo Stoakes, Miaohe Lin,
	Naoya Horiguchi
  Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, Kiryl Shutsemau (Meta), stable

On 7/14/26 14:23, Kiryl Shutsemau wrote:
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> 
> try_to_split_thp_page() locked the poisoned page and passed it to
> split_huge_page_to_order(), which returns that very page locked to the
> caller.  For a tail page that means __folio_split() runs with @lock_at
> pointing into the middle of the folio.
> 
> __folio_split() dereferences the mapping after the split completes
> (shmem_uncharge(), i_mmap_unlock_read()).  The only thing keeping the
> inode alive across that is the locked @lock_at folio: while it stays in
> the page cache, eviction cannot complete.
> 
> But a tail @lock_at can lie beyond EOF -- e.g. part of a shmem THP that
> reaches past i_size while the file is being truncated.  The split then
> drops it from the page cache yet still returns it locked, so the pin is
> gone and a racing final iput() can evict and RCU-free the inode while
> __folio_split() is still running:
> 
>   BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790
>    i_mmap_unlock_read include/linux/fs.h:537 [inline]
>    __folio_split+0x732/0x1640 mm/huge_memory.c:4100
>    try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675
>    memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470
> 
>   Freed by task 4601:
>    shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177
>    evict+0x57f/0xac0 fs/inode.c:870
> 
> Split the folio as a folio, via split_folio_to_order(), so the head is
> the anchor left locked.  The head is piece 0, which the beyond-EOF drop
> loop never removes (it starts at folio_next(folio)), so the split always
> leaves it in the page cache and the inode stays pinned for the whole of
> __folio_split().  memory_failure() and soft offline re-lock the poisoned
> subpage's folio themselves after the split, so they do not depend on it
> being returned locked.
> 
> Reported-by: Hao Zhang <zhanghao1@kylinos.cn>
> Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc
> Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> ---
>  mm/memory-failure.c | 13 ++++++++++---
>  1 file changed, 10 insertions(+), 3 deletions(-)
> 
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index 51508a55c405..68d42cbed458 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -1657,11 +1657,18 @@ static int identify_page_state(unsigned long pfn, struct page *p,
>  static int try_to_split_thp_page(struct page *page, unsigned int new_order,
>  		bool release)
>  {
> +	struct folio *folio = page_folio(page);
>  	int ret;
>  
> -	lock_page(page);
> -	ret = split_huge_page_to_order(page, new_order);
> -	unlock_page(page);
> +	/*
> +	 * Lock and split at the head, not the poisoned subpage: __folio_split()
> +	 * keeps the anchor folio locked and needs it to stay in the page cache
> +	 * to pin the inode. A tail beyond EOF would be dropped yet returned
> +	 * locked, losing that pin. The caller re-locks @page afterwards.
> +	 */
> +	folio_lock(folio);
> +	ret = split_folio_to_order(folio, new_order);
> +	folio_unlock(folio);

With a non-uniform split it would actually make a difference: we'd want to split
such that we the other folio pages minimal.

 split_folio_to_order() always seems to end up in
__split_huge_page_to_list_to_order() where we do a SPLIT_TYPE_UNIFORM.

I recall discussing with Zi and Willy that in the future we'd want to convert
more places to do a non-uniform split.

So I'm afraid that would just re-introduce the problem then.

-- 
Cheers,

David

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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 12:23 ` [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split Kiryl Shutsemau
  2026-07-14 13:01   ` David Hildenbrand (Arm)
@ 2026-07-14 13:05   ` Kiryl Shutsemau
  1 sibling, 0 replies; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 13:05 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Miaohe Lin,
	Naoya Horiguchi
  Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, stable

On Tue, Jul 14, 2026 at 01:23:40PM +0100, Kiryl Shutsemau wrote:
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index 51508a55c405..68d42cbed458 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -1657,11 +1657,18 @@ static int identify_page_state(unsigned long pfn, struct page *p,
>  static int try_to_split_thp_page(struct page *page, unsigned int new_order,
>  		bool release)
>  {
> +	struct folio *folio = page_folio(page);
>  	int ret;
>  
> -	lock_page(page);
> -	ret = split_huge_page_to_order(page, new_order);
> -	unlock_page(page);
> +	/*
> +	 * Lock and split at the head, not the poisoned subpage: __folio_split()
> +	 * keeps the anchor folio locked and needs it to stay in the page cache
> +	 * to pin the inode. A tail beyond EOF would be dropped yet returned
> +	 * locked, losing that pin. The caller re-locks @page afterwards.
> +	 */
> +	folio_lock(folio);
> +	ret = split_folio_to_order(folio, new_order);
> +	folio_unlock(folio);
>  
>  	if (ret && release)
>  		put_page(page);

Ughh.. This patch is broken, sorry.

Please ignore the patchset. Will follow up.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 13:01   ` David Hildenbrand (Arm)
@ 2026-07-14 14:53     ` Kiryl Shutsemau
  2026-07-14 14:58       ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 14:53 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: Andrew Morton, Lorenzo Stoakes, Miaohe Lin, Naoya Horiguchi,
	Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, stable

On Tue, Jul 14, 2026 at 03:01:46PM +0200, David Hildenbrand (Arm) wrote:
> On 7/14/26 14:23, Kiryl Shutsemau wrote:
> > From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> > 
> > try_to_split_thp_page() locked the poisoned page and passed it to
> > split_huge_page_to_order(), which returns that very page locked to the
> > caller.  For a tail page that means __folio_split() runs with @lock_at
> > pointing into the middle of the folio.
> > 
> > __folio_split() dereferences the mapping after the split completes
> > (shmem_uncharge(), i_mmap_unlock_read()).  The only thing keeping the
> > inode alive across that is the locked @lock_at folio: while it stays in
> > the page cache, eviction cannot complete.
> > 
> > But a tail @lock_at can lie beyond EOF -- e.g. part of a shmem THP that
> > reaches past i_size while the file is being truncated.  The split then
> > drops it from the page cache yet still returns it locked, so the pin is
> > gone and a racing final iput() can evict and RCU-free the inode while
> > __folio_split() is still running:
> > 
> >   BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790
> >    i_mmap_unlock_read include/linux/fs.h:537 [inline]
> >    __folio_split+0x732/0x1640 mm/huge_memory.c:4100
> >    try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675
> >    memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470
> > 
> >   Freed by task 4601:
> >    shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177
> >    evict+0x57f/0xac0 fs/inode.c:870
> > 
> > Split the folio as a folio, via split_folio_to_order(), so the head is
> > the anchor left locked.  The head is piece 0, which the beyond-EOF drop
> > loop never removes (it starts at folio_next(folio)), so the split always
> > leaves it in the page cache and the inode stays pinned for the whole of
> > __folio_split().  memory_failure() and soft offline re-lock the poisoned
> > subpage's folio themselves after the split, so they do not depend on it
> > being returned locked.
> > 
> > Reported-by: Hao Zhang <zhanghao1@kylinos.cn>
> > Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc
> > Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
> > Cc: <stable@vger.kernel.org>
> > Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> > ---
> >  mm/memory-failure.c | 13 ++++++++++---
> >  1 file changed, 10 insertions(+), 3 deletions(-)
> > 
> > diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> > index 51508a55c405..68d42cbed458 100644
> > --- a/mm/memory-failure.c
> > +++ b/mm/memory-failure.c
> > @@ -1657,11 +1657,18 @@ static int identify_page_state(unsigned long pfn, struct page *p,
> >  static int try_to_split_thp_page(struct page *page, unsigned int new_order,
> >  		bool release)
> >  {
> > +	struct folio *folio = page_folio(page);
> >  	int ret;
> >  
> > -	lock_page(page);
> > -	ret = split_huge_page_to_order(page, new_order);
> > -	unlock_page(page);
> > +	/*
> > +	 * Lock and split at the head, not the poisoned subpage: __folio_split()
> > +	 * keeps the anchor folio locked and needs it to stay in the page cache
> > +	 * to pin the inode. A tail beyond EOF would be dropped yet returned
> > +	 * locked, losing that pin. The caller re-locks @page afterwards.
> > +	 */
> > +	folio_lock(folio);
> > +	ret = split_folio_to_order(folio, new_order);
> > +	folio_unlock(folio);
> 
> With a non-uniform split it would actually make a difference: we'd want to split
> such that we the other folio pages minimal.
> 
>  split_folio_to_order() always seems to end up in
> __split_huge_page_to_list_to_order() where we do a SPLIT_TYPE_UNIFORM.
> 
> I recall discussing with Zi and Willy that in the future we'd want to convert
> more places to do a non-uniform split.
> 
> So I'm afraid that would just re-introduce the problem then.

Right. Non-uniform split can be useful.

But my patch is completely broken because code expects the pin to be on the
@page, not on the head. put_page() few lines down can explode already.

So the fix does not belong in memory_failure(). It belongs in
__folio_split(), and it is really just 2/5: refuse the split with -EBUSY
when @lock_at is at or beyond the sampled EOF.

The safety then sits in __folio_split() regardless of caller or split type,
which should also cover your non-uniform worry.

The behavioural change is that memory_failure() reports a beyond-EOF
poisoned tail as unsplit (MF_FAILED) instead of recovered, and kills the
mappers instead of splitting the page off. What we give up is salvaging
the folio's healthy pages and the clean unmap -- both low value for a page
that is beyond EOF and getting truncated away. Containment is unaffected:
PageHWPoison is set before the split and free_pages_prepare() keeps a
poisoned page out of the buddy allocator, so the bad page never comes back
regardless.

The alternative, if we would rather keep the recovered outcome, is to leave
@lock_at as the poisoned page and move i_mmap_unlock_read() (and
shmem_uncharge()) ahead of the after-split unlock loop, so every mapping
dereference happens while @folio -- the head, within EOF -- still pins the
inode. That is close to Hao's original patch. It works, but it rests on "no
mapping dereference after the unlock loop", which is its own fragility.

I lean towards the -EBUSY guard.

Comments?

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 14:53     ` Kiryl Shutsemau
@ 2026-07-14 14:58       ` David Hildenbrand (Arm)
  2026-07-14 15:44         ` Zi Yan
  0 siblings, 1 reply; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-14 14:58 UTC (permalink / raw)
  To: Kiryl Shutsemau
  Cc: Andrew Morton, Lorenzo Stoakes, Miaohe Lin, Naoya Horiguchi,
	Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, Lance Yang, Usama Arif, Hao Zhang,
	Hao Zhang, linux-mm, linux-kernel, stable

On 7/14/26 16:53, Kiryl Shutsemau wrote:
> On Tue, Jul 14, 2026 at 03:01:46PM +0200, David Hildenbrand (Arm) wrote:
>> On 7/14/26 14:23, Kiryl Shutsemau wrote:
>>> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
>>>
>>> try_to_split_thp_page() locked the poisoned page and passed it to
>>> split_huge_page_to_order(), which returns that very page locked to the
>>> caller.  For a tail page that means __folio_split() runs with @lock_at
>>> pointing into the middle of the folio.
>>>
>>> __folio_split() dereferences the mapping after the split completes
>>> (shmem_uncharge(), i_mmap_unlock_read()).  The only thing keeping the
>>> inode alive across that is the locked @lock_at folio: while it stays in
>>> the page cache, eviction cannot complete.
>>>
>>> But a tail @lock_at can lie beyond EOF -- e.g. part of a shmem THP that
>>> reaches past i_size while the file is being truncated.  The split then
>>> drops it from the page cache yet still returns it locked, so the pin is
>>> gone and a racing final iput() can evict and RCU-free the inode while
>>> __folio_split() is still running:
>>>
>>>   BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790
>>>    i_mmap_unlock_read include/linux/fs.h:537 [inline]
>>>    __folio_split+0x732/0x1640 mm/huge_memory.c:4100
>>>    try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675
>>>    memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470
>>>
>>>   Freed by task 4601:
>>>    shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177
>>>    evict+0x57f/0xac0 fs/inode.c:870
>>>
>>> Split the folio as a folio, via split_folio_to_order(), so the head is
>>> the anchor left locked.  The head is piece 0, which the beyond-EOF drop
>>> loop never removes (it starts at folio_next(folio)), so the split always
>>> leaves it in the page cache and the inode stays pinned for the whole of
>>> __folio_split().  memory_failure() and soft offline re-lock the poisoned
>>> subpage's folio themselves after the split, so they do not depend on it
>>> being returned locked.
>>>
>>> Reported-by: Hao Zhang <zhanghao1@kylinos.cn>
>>> Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc
>>> Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
>>> Cc: <stable@vger.kernel.org>
>>> Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
>>> ---
>>>  mm/memory-failure.c | 13 ++++++++++---
>>>  1 file changed, 10 insertions(+), 3 deletions(-)
>>>
>>> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
>>> index 51508a55c405..68d42cbed458 100644
>>> --- a/mm/memory-failure.c
>>> +++ b/mm/memory-failure.c
>>> @@ -1657,11 +1657,18 @@ static int identify_page_state(unsigned long pfn, struct page *p,
>>>  static int try_to_split_thp_page(struct page *page, unsigned int new_order,
>>>  		bool release)
>>>  {
>>> +	struct folio *folio = page_folio(page);
>>>  	int ret;
>>>  
>>> -	lock_page(page);
>>> -	ret = split_huge_page_to_order(page, new_order);
>>> -	unlock_page(page);
>>> +	/*
>>> +	 * Lock and split at the head, not the poisoned subpage: __folio_split()
>>> +	 * keeps the anchor folio locked and needs it to stay in the page cache
>>> +	 * to pin the inode. A tail beyond EOF would be dropped yet returned
>>> +	 * locked, losing that pin. The caller re-locks @page afterwards.
>>> +	 */
>>> +	folio_lock(folio);
>>> +	ret = split_folio_to_order(folio, new_order);
>>> +	folio_unlock(folio);
>>
>> With a non-uniform split it would actually make a difference: we'd want to split
>> such that we the other folio pages minimal.
>>
>>  split_folio_to_order() always seems to end up in
>> __split_huge_page_to_list_to_order() where we do a SPLIT_TYPE_UNIFORM.
>>
>> I recall discussing with Zi and Willy that in the future we'd want to convert
>> more places to do a non-uniform split.
>>
>> So I'm afraid that would just re-introduce the problem then.
> 
> Right. Non-uniform split can be useful.
> 
> But my patch is completely broken because code expects the pin to be on the
> @page, not on the head. put_page() few lines down can explode already.

Yeah.

> 
> So the fix does not belong in memory_failure(). It belongs in
> __folio_split(), and it is really just 2/5: refuse the split with -EBUSY
> when @lock_at is at or beyond the sampled EOF.
> 
> The safety then sits in __folio_split() regardless of caller or split type,
> which should also cover your non-uniform worry.
> 
> The behavioural change is that memory_failure() reports a beyond-EOF
> poisoned tail as unsplit (MF_FAILED) instead of recovered, and kills the
> mappers instead of splitting the page off. What we give up is salvaging
> the folio's healthy pages and the clean unmap -- both low value for a page
> that is beyond EOF and getting truncated away. Containment is unaffected:
> PageHWPoison is set before the split and free_pages_prepare() keeps a
> poisoned page out of the buddy allocator, so the bad page never comes back
> regardless.
> 
> The alternative, if we would rather keep the recovered outcome, is to leave
> @lock_at as the poisoned page and move i_mmap_unlock_read() (and
> shmem_uncharge()) ahead of the after-split unlock loop, so every mapping
> dereference happens while @folio -- the head, within EOF -- still pins the
> inode. That is close to Hao's original patch. It works, but it rests on "no
> mapping dereference after the unlock loop", which is its own fragility.

At least it can be well documented.

> 
> I lean towards the -EBUSY guard.

The latter approach seems cleaner to me, but let's hear others as well.

-- 
Cheers,

David

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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 14:58       ` David Hildenbrand (Arm)
@ 2026-07-14 15:44         ` Zi Yan
  2026-07-14 16:40           ` Kiryl Shutsemau
  0 siblings, 1 reply; 17+ messages in thread
From: Zi Yan @ 2026-07-14 15:44 UTC (permalink / raw)
  To: David Hildenbrand (Arm), Kiryl Shutsemau
  Cc: Andrew Morton, Lorenzo Stoakes, Miaohe Lin, Naoya Horiguchi,
	Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Barry Song, Lance Yang, Usama Arif, Hao Zhang, Hao Zhang,
	linux-mm, linux-kernel, stable

On 14 Jul 2026, at 10:58, David Hildenbrand (Arm) wrote:

> On 7/14/26 16:53, Kiryl Shutsemau wrote:
>> On Tue, Jul 14, 2026 at 03:01:46PM +0200, David Hildenbrand (Arm) wrote:
>>> On 7/14/26 14:23, Kiryl Shutsemau wrote:
>>>> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
>>>>
>>>> try_to_split_thp_page() locked the poisoned page and passed it to
>>>> split_huge_page_to_order(), which returns that very page locked to the
>>>> caller.  For a tail page that means __folio_split() runs with @lock_at
>>>> pointing into the middle of the folio.
>>>>
>>>> __folio_split() dereferences the mapping after the split completes
>>>> (shmem_uncharge(), i_mmap_unlock_read()).  The only thing keeping the
>>>> inode alive across that is the locked @lock_at folio: while it stays in
>>>> the page cache, eviction cannot complete.
>>>>
>>>> But a tail @lock_at can lie beyond EOF -- e.g. part of a shmem THP that
>>>> reaches past i_size while the file is being truncated.  The split then
>>>> drops it from the page cache yet still returns it locked, so the pin is
>>>> gone and a racing final iput() can evict and RCU-free the inode while
>>>> __folio_split() is still running:
>>>>
>>>>   BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790
>>>>    i_mmap_unlock_read include/linux/fs.h:537 [inline]
>>>>    __folio_split+0x732/0x1640 mm/huge_memory.c:4100
>>>>    try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675
>>>>    memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470
>>>>
>>>>   Freed by task 4601:
>>>>    shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177
>>>>    evict+0x57f/0xac0 fs/inode.c:870
>>>>
>>>> Split the folio as a folio, via split_folio_to_order(), so the head is
>>>> the anchor left locked.  The head is piece 0, which the beyond-EOF drop
>>>> loop never removes (it starts at folio_next(folio)), so the split always
>>>> leaves it in the page cache and the inode stays pinned for the whole of
>>>> __folio_split().  memory_failure() and soft offline re-lock the poisoned
>>>> subpage's folio themselves after the split, so they do not depend on it
>>>> being returned locked.
>>>>
>>>> Reported-by: Hao Zhang <zhanghao1@kylinos.cn>
>>>> Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc
>>>> Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
>>>> Cc: <stable@vger.kernel.org>
>>>> Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
>>>> ---
>>>>  mm/memory-failure.c | 13 ++++++++++---
>>>>  1 file changed, 10 insertions(+), 3 deletions(-)
>>>>
>>>> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
>>>> index 51508a55c405..68d42cbed458 100644
>>>> --- a/mm/memory-failure.c
>>>> +++ b/mm/memory-failure.c
>>>> @@ -1657,11 +1657,18 @@ static int identify_page_state(unsigned long pfn, struct page *p,
>>>>  static int try_to_split_thp_page(struct page *page, unsigned int new_order,
>>>>  		bool release)
>>>>  {
>>>> +	struct folio *folio = page_folio(page);
>>>>  	int ret;
>>>>
>>>> -	lock_page(page);
>>>> -	ret = split_huge_page_to_order(page, new_order);
>>>> -	unlock_page(page);
>>>> +	/*
>>>> +	 * Lock and split at the head, not the poisoned subpage: __folio_split()
>>>> +	 * keeps the anchor folio locked and needs it to stay in the page cache
>>>> +	 * to pin the inode. A tail beyond EOF would be dropped yet returned
>>>> +	 * locked, losing that pin. The caller re-locks @page afterwards.
>>>> +	 */
>>>> +	folio_lock(folio);
>>>> +	ret = split_folio_to_order(folio, new_order);
>>>> +	folio_unlock(folio);
>>>
>>> With a non-uniform split it would actually make a difference: we'd want to split
>>> such that we the other folio pages minimal.
>>>
>>>  split_folio_to_order() always seems to end up in
>>> __split_huge_page_to_list_to_order() where we do a SPLIT_TYPE_UNIFORM.
>>>
>>> I recall discussing with Zi and Willy that in the future we'd want to convert
>>> more places to do a non-uniform split.
>>>
>>> So I'm afraid that would just re-introduce the problem then.
>>
>> Right. Non-uniform split can be useful.
>>
>> But my patch is completely broken because code expects the pin to be on the
>> @page, not on the head. put_page() few lines down can explode already.
>
> Yeah.
>
>>
>> So the fix does not belong in memory_failure(). It belongs in
>> __folio_split(), and it is really just 2/5: refuse the split with -EBUSY
>> when @lock_at is at or beyond the sampled EOF.

There is an alternative, only igrab() when @lock_at is at or beyond the EOF,
as I was bouncing ideas with Codex.

Some subtlety exists in these two “@lock_at at or beyond EOF” approaches.
When @lock_at can be a tail page of an after-split folio, Patch 2 returns
-EBUSY unnecessarily, since the after-split folio containing @lock_at will
be still in xarray, preventing inode going away. To make a precise decision,
we will need to determine the index of the after-split folio containing @lock_at
when checking against end. It is easy for uniform split, namely

lock_at_folio_index =
	folio->index + round_down(lock_at - &folio->page, 1UL << new_order);

but for non-uniform split, the calculation is more involved and I have not
figured it out yet.

If we go “return -EBUSY if @lock_at index is at or beyond EOF”, are we OK
with not being able to split a folio when it is actually splittable?


>>
>> The safety then sits in __folio_split() regardless of caller or split type,
>> which should also cover your non-uniform worry.
>>
>> The behavioural change is that memory_failure() reports a beyond-EOF
>> poisoned tail as unsplit (MF_FAILED) instead of recovered, and kills the
>> mappers instead of splitting the page off. What we give up is salvaging
>> the folio's healthy pages and the clean unmap -- both low value for a page
>> that is beyond EOF and getting truncated away. Containment is unaffected:
>> PageHWPoison is set before the split and free_pages_prepare() keeps a
>> poisoned page out of the buddy allocator, so the bad page never comes back
>> regardless.
>>
>> The alternative, if we would rather keep the recovered outcome, is to leave
>> @lock_at as the poisoned page and move i_mmap_unlock_read() (and
>> shmem_uncharge()) ahead of the after-split unlock loop, so every mapping
>> dereference happens while @folio -- the head, within EOF -- still pins the
>> inode. That is close to Hao's original patch. It works, but it rests on "no
>> mapping dereference after the unlock loop", which is its own fragility.
>
> At least it can be well documented.

This approach works without changing existing behavior, as long as we document
the requirement well.

I admit that this is another implicit synchronization in folio split process,
in addition to
1) keeping original folio frozen until xarray is updated and
2) do not update xarray with after-split folios until after-split folios are
unfrozen.

>
>>
>> I lean towards the -EBUSY guard.
>
> The latter approach seems cleaner to me, but let's hear others as well.

I prefer moving i_mmap_unlock_read() before the unlock loop (shmem_uncharge()
is already before the loop), but I am more than happy to be convinced that
“return -EBUSY” is better or something else.

Best Regards,
Yan, Zi


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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 15:44         ` Zi Yan
@ 2026-07-14 16:40           ` Kiryl Shutsemau
  2026-07-14 17:31             ` Zi Yan
  0 siblings, 1 reply; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-14 16:40 UTC (permalink / raw)
  To: Zi Yan
  Cc: David Hildenbrand (Arm), Andrew Morton, Lorenzo Stoakes,
	Miaohe Lin, Naoya Horiguchi, Baolin Wang, Liam R . Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
	Usama Arif, Hao Zhang, Hao Zhang, linux-mm, linux-kernel, stable

On Tue, Jul 14, 2026 at 11:44:39AM -0400, Zi Yan wrote:
> There is an alternative, only igrab() when @lock_at is at or beyond the EOF,
> as I was bouncing ideas with Codex.

I saw this option too, but I wound rather not go this path.

iput() still can lead to inode eviction an bunch of random filesystem
complexity under us. I don't think we want to think about other
fs-related locking issues in split context.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 16:40           ` Kiryl Shutsemau
@ 2026-07-14 17:31             ` Zi Yan
  2026-07-15 10:42               ` Kiryl Shutsemau
  0 siblings, 1 reply; 17+ messages in thread
From: Zi Yan @ 2026-07-14 17:31 UTC (permalink / raw)
  To: Kiryl Shutsemau
  Cc: David Hildenbrand (Arm), Andrew Morton, Lorenzo Stoakes,
	Miaohe Lin, Naoya Horiguchi, Baolin Wang, Liam R . Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
	Usama Arif, Hao Zhang, Hao Zhang, linux-mm, linux-kernel, stable

On Tue Jul 14, 2026 at 12:40 PM EDT, Kiryl Shutsemau wrote:
> On Tue, Jul 14, 2026 at 11:44:39AM -0400, Zi Yan wrote:
>> There is an alternative, only igrab() when @lock_at is at or beyond the EOF,
>> as I was bouncing ideas with Codex.
>
> I saw this option too, but I wound rather not go this path.
>
> iput() still can lead to inode eviction an bunch of random filesystem
> complexity under us. I don't think we want to think about other
> fs-related locking issues in split context.

Your reasoning makes sense to me. Let's ignore this option.

For your patch 2, we might want something like below to avoid over
rejecting splits. WDYT?

offset = folio_page_idx(folio, lock_at);

if (split_type == SPLIT_TYPE_UNIFORM)
	lock_at_index = folio->index + round_down(offset, 1UL << new_order);
else
	/* @lock_at in non uniform split is always @folio */
	lock_at_index = folio->index;

if (lock_at_index >= end) {
	ret = -EBUSY;
	goto out_unlock;
}


Also to keep a record in case we want to have @lock_at pointing to any
tail page for non uniform split in the future, something like below is
going to be useful (assisted by Codex).

static pgoff_t split_lock_at_index(struct folio *folio,
		unsigned int new_order, struct page *split_at,
		struct page *lock_at, enum split_type split_type)
{
	unsigned long lock = folio_page_idx(folio, lock_at);
	unsigned long split = folio_page_idx(folio, split_at);
	int order;

	if (split_type == SPLIT_TYPE_UNIFORM)
		return folio->index + round_down(lock, 1UL << new_order);

	for (order = folio_order(folio) - 1; order >= new_order; order--) {
		unsigned long size = 1UL << order;
		unsigned long lock_base = round_down(lock, size);
		unsigned long split_base = round_down(split, size);

		/* folio containing @lock_at will not be split any more */
		if (lock_base != split_base)
			return folio->index + lock_base;
	}
	
	return folio->index + round_down(lock, 1UL << new_order);
}

if (split_lock_at_index(folio, new_order, split_at, lock_at, split_type) >= end) {
	ret = -EBUSY;
	goto out_unlock;
}

-- 
Best Regards,
Yan, Zi


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

* [syzbot ci] Re: mm: fix inode UAF when splitting a file folio past EOF
  2026-07-14 12:23 [PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF Kiryl Shutsemau
                   ` (4 preceding siblings ...)
  2026-07-14 12:23 ` [PATCH v2 5/5] mm/huge_memory: fold split_folio_to_list_to_order() into split_folio_to_order() Kiryl Shutsemau
@ 2026-07-15  5:55 ` syzbot ci
  5 siblings, 0 replies; 17+ messages in thread
From: syzbot ci @ 2026-07-15  5:55 UTC (permalink / raw)
  To: akpm, baohua, baolin.wang, david, dev.jain, hao_zhang_kdev, kas,
	kirill, lance.yang, liam, linmiaohe, linux-kernel, linux-mm, ljs,
	nao.horiguchi, npache, ryan.roberts, stable, usama.arif,
	zhanghao1, ziy
  Cc: syzbot, syzkaller-bugs

syzbot ci has tested the following series

[v2] mm: fix inode UAF when splitting a file folio past EOF
https://lore.kernel.org/all/20260714122344.351895-1-kirill@shutemov.name
* [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
* [PATCH v2 2/5] mm/huge_memory: refuse to split a file folio when the anchor is beyond EOF
* [PATCH v2 3/5] mm/huge_memory: remove unused split_huge_page_to_order()
* [PATCH v2 4/5] mm/huge_memory: remove unused can_split_folio()
* [PATCH v2 5/5] mm/huge_memory: fold split_folio_to_list_to_order() into split_folio_to_order()

and found the following issues:
* kernel BUG in __page_table_check_zero
* kernel BUG in folio_isolate_lru
* kernel BUG in memory_failure

Full report is available here:
https://ci.syzbot.org/series/6cad3430-1f9b-4a56-b60a-ef3ed2833e15

***

kernel BUG in __page_table_check_zero

tree:      bpf
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf.git
base:      0e35b9b6ec0ffcc5e23cbdec09f5c622ad532b53
arch:      amd64
compiler:  Debian clang version 22.1.6 (++20260514074242+fc4aad7b5db3-1~exp1~20260514074407.73), Debian LLD 22.1.6
config:    https://ci.syzbot.org/builds/8d6a621e-aae6-4f99-8e0e-d0e02ddbed71/config
syz repro: https://ci.syzbot.org/findings/1619ac56-823a-4cc2-975b-bbb1c1742447/syz_repro

Soft offlining pfn 0x120f0e at process virtual address 0x20000010e000
------------[ cut here ]------------
kernel BUG at mm/page_table_check.c:142!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 0 UID: 0 PID: 5870 Comm: syz.1.18 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:__page_table_check_zero+0x416/0x430 mm/page_table_check.c:142
Code: f3 ff e9 4e fc ff ff e8 78 70 85 ff 89 ea be 01 00 00 00 48 c7 c7 a0 c4 ac 8e e8 45 29 b7 02 e9 48 fd ff ff e8 5b 70 85 ff 90 <0f> 0b e8 53 70 85 ff 90 0f 0b e8 4b 70 85 ff 90 0f 0b 0f 1f 84 00
RSP: 0018:ffffc900017e7948 EFLAGS: 00010293
RAX: ffffffff8240f325 RBX: ffff88810544b4a8 RCX: ffff88810c68bb80
RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000000
RBP: 0000000000000001 R08: ffff88810544b4ab R09: 1ffff11020a89695
R10: dffffc0000000000 R11: ffffed1020a89696 R12: 0000000000000001
R13: ffff88810544b460 R14: 0000000000000000 R15: 0000000000000001
FS:  00007fb17e6b86c0(0000) GS:ffff88818dc0e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f2353670000 CR3: 0000000109db2000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 __folio_put+0x4b3/0x590 mm/swap.c:112
 folio_put include/linux/mm.h:2124 [inline]
 put_page include/linux/mm.h:2193 [inline]
 page_handle_poison+0x35d/0x4a0 mm/memory-failure.c:204
 soft_offline_in_use_page mm/memory-failure.c:2861 [inline]
 soft_offline_page+0xd1d/0x1560 mm/memory-failure.c:2952
 madvise_inject_error mm/madvise.c:1476 [inline]
 madvise_do_behavior+0x319/0x930 mm/madvise.c:1897
 do_madvise+0x327/0x3a0 mm/madvise.c:2005
 __do_sys_madvise mm/madvise.c:2014 [inline]
 __se_sys_madvise mm/madvise.c:2012 [inline]
 __x64_sys_madvise+0xa6/0xc0 mm/madvise.c:2012
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fb17d79ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fb17e6b8028 EFLAGS: 00000246 ORIG_RAX: 000000000000001c
RAX: ffffffffffffffda RBX: 00007fb17da15fa0 RCX: 00007fb17d79ce59
RDX: 0000000000000065 RSI: 0000000000002000 RDI: 000020000010e000
RBP: 00007fb17d832e6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fb17da16038 R14: 00007fb17da15fa0 R15: 00007ffd7e9be1d8
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:__page_table_check_zero+0x416/0x430 mm/page_table_check.c:142
Code: f3 ff e9 4e fc ff ff e8 78 70 85 ff 89 ea be 01 00 00 00 48 c7 c7 a0 c4 ac 8e e8 45 29 b7 02 e9 48 fd ff ff e8 5b 70 85 ff 90 <0f> 0b e8 53 70 85 ff 90 0f 0b e8 4b 70 85 ff 90 0f 0b 0f 1f 84 00
RSP: 0018:ffffc900017e7948 EFLAGS: 00010293
RAX: ffffffff8240f325 RBX: ffff88810544b4a8 RCX: ffff88810c68bb80
RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000000
RBP: 0000000000000001 R08: ffff88810544b4ab R09: 1ffff11020a89695
R10: dffffc0000000000 R11: ffffed1020a89696 R12: 0000000000000001
R13: ffff88810544b460 R14: 0000000000000000 R15: 0000000000000001
FS:  00007fb17e6b86c0(0000) GS:ffff88818dc0e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fb17d7ea540 CR3: 0000000109db2000 CR4: 00000000000006f0


***

kernel BUG in folio_isolate_lru

tree:      bpf
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf.git
base:      0e35b9b6ec0ffcc5e23cbdec09f5c622ad532b53
arch:      amd64
compiler:  Debian clang version 22.1.6 (++20260514074242+fc4aad7b5db3-1~exp1~20260514074407.73), Debian LLD 22.1.6
config:    https://ci.syzbot.org/builds/8d6a621e-aae6-4f99-8e0e-d0e02ddbed71/config
syz repro: https://ci.syzbot.org/findings/7857300c-5c56-4b88-9f60-6f1f3603e3f1/syz_repro

 do_madvise+0x327/0x3a0 mm/madvise.c:2005
 __do_sys_madvise mm/madvise.c:2014 [inline]
 __se_sys_madvise mm/madvise.c:2012 [inline]
 __x64_sys_madvise+0xa6/0xc0 mm/madvise.c:2012
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
------------[ cut here ]------------
kernel BUG at mm/vmscan.c:1803!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 0 UID: 0 PID: 5810 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:folio_isolate_lru+0x8ad/0x990 mm/vmscan.c:1803
Code: 38 c1 0f 8c 75 fd ff ff 4c 89 f7 e8 ad 0e 28 00 e9 68 fd ff ff e8 d3 2b ba ff 4c 89 f7 48 c7 c6 20 5f d8 8b e8 84 d9 1b ff 90 <0f> 0b e8 bc 2b ba ff 4c 89 f7 48 c7 c6 60 8e d8 8b e8 6d d9 1b ff
RSP: 0018:ffffc90003c678d8 EFLAGS: 00010246
RAX: 2554150cb728ac00 RBX: 0000000000000000 RCX: 0000000000000000
RDX: 0000000000000006 RSI: ffffffff8dfe9eba RDI: 00000000ffffffff
RBP: 1ffffd40000f9d26 R08: ffffffff90334ef7 R09: 1ffffffff20669de
R10: dffffc0000000000 R11: fffffbfff20669df R12: dffffc0000000000
R13: 0000000000100010 R14: ffffea00007ce900 R15: ffffea00007ce934
FS:  00007fa1793946c0(0000) GS:ffff88818dc0e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00005640d3daa058 CR3: 00000001696d8000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 delete_from_lru_cache+0x1a/0x1e0 mm/memory-failure.c:900
 me_pagecache_clean+0x58/0x230 mm/memory-failure.c:1015
 page_action mm/memory-failure.c:1298 [inline]
 identify_page_state+0x13c/0x190 mm/memory-failure.c:1649
 memory_failure+0x2fff/0x3380 mm/memory-failure.c:2548
 madvise_inject_error mm/madvise.c:1480 [inline]
 madvise_do_behavior+0x344/0x930 mm/madvise.c:1897
 do_madvise+0x327/0x3a0 mm/madvise.c:2005
 __do_sys_madvise mm/madvise.c:2014 [inline]
 __se_sys_madvise mm/madvise.c:2012 [inline]
 __x64_sys_madvise+0xa6/0xc0 mm/madvise.c:2012
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fa17859ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fa179394028 EFLAGS: 00000246 ORIG_RAX: 000000000000001c
RAX: ffffffffffffffda RBX: 00007fa178815fa0 RCX: 00007fa17859ce59
RDX: 0000000000000064 RSI: 0000000000002000 RDI: 00002000005a4000
RBP: 00007fa178632e6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fa178816038 R14: 00007fa178815fa0 R15: 00007fff254e3208
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:folio_isolate_lru+0x8ad/0x990 mm/vmscan.c:1803
Code: 38 c1 0f 8c 75 fd ff ff 4c 89 f7 e8 ad 0e 28 00 e9 68 fd ff ff e8 d3 2b ba ff 4c 89 f7 48 c7 c6 20 5f d8 8b e8 84 d9 1b ff 90 <0f> 0b e8 bc 2b ba ff 4c 89 f7 48 c7 c6 60 8e d8 8b e8 6d d9 1b ff
RSP: 0018:ffffc90003c678d8 EFLAGS: 00010246
RAX: 2554150cb728ac00 RBX: 0000000000000000 RCX: 0000000000000000
RDX: 0000000000000006 RSI: ffffffff8dfe9eba RDI: 00000000ffffffff
RBP: 1ffffd40000f9d26 R08: ffffffff90334ef7 R09: 1ffffffff20669de
R10: dffffc0000000000 R11: fffffbfff20669df R12: dffffc0000000000
R13: 0000000000100010 R14: ffffea00007ce900 R15: ffffea00007ce934
FS:  00007fa1793946c0(0000) GS:ffff88818dc0e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00005640d3daa058 CR3: 00000001696d8000 CR4: 00000000000006f0


***

kernel BUG in memory_failure

tree:      bpf
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf.git
base:      0e35b9b6ec0ffcc5e23cbdec09f5c622ad532b53
arch:      amd64
compiler:  Debian clang version 22.1.6 (++20260514074242+fc4aad7b5db3-1~exp1~20260514074407.73), Debian LLD 22.1.6
config:    https://ci.syzbot.org/builds/8d6a621e-aae6-4f99-8e0e-d0e02ddbed71/config
syz repro: https://ci.syzbot.org/findings/6feafe6c-6796-475f-9aca-225a9d35747c/syz_repro

 do_madvise+0x327/0x3a0 mm/madvise.c:2005
 __do_sys_madvise mm/madvise.c:2014 [inline]
 __se_sys_madvise mm/madvise.c:2012 [inline]
 __x64_sys_madvise+0xa6/0xc0 mm/madvise.c:2012
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
------------[ cut here ]------------
kernel BUG at mm/memory-failure.c:2483!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 1 UID: 0 PID: 5850 Comm: syz.1.18 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:memory_failure+0x3373/0x3380 mm/memory-failure.c:2483
Code: cb 87 ff 48 89 df 48 c7 c6 e0 a2 dd 8b e8 45 79 e9 fe 90 0f 0b e8 7d cb 87 ff 4c 89 f7 48 c7 c6 40 93 dd 8b e8 2e 79 e9 fe 90 <0f> 0b 66 66 2e 0f 1f 84 00 00 00 00 00 90 90 90 90 90 90 90 90 90
RSP: 0018:ffffc900036efa00 EFLAGS: 00010246
RAX: 9a71b80f9d8a2200 RBX: 0000000000000000 RCX: 0000000000000000
RDX: 0000000000000006 RSI: ffffffff8dfe9eba RDI: 00000000ffffffff
RBP: ffffc900036efb90 R08: ffffffff90334ef7 R09: 1ffffffff20669de
R10: dffffc0000000000 R11: fffffbfff20669df R12: ffffea00045f6ac0
R13: ffffea00045f0008 R14: ffffea00045f6ac0 R15: 1ffff920006ddf54
FS:  00007fed60fc56c0(0000) GS:ffff8882a920e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f3eee905440 CR3: 0000000165fdc000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 madvise_inject_error mm/madvise.c:1480 [inline]
 madvise_do_behavior+0x344/0x930 mm/madvise.c:1897
 do_madvise+0x327/0x3a0 mm/madvise.c:2005
 __do_sys_madvise mm/madvise.c:2014 [inline]
 __se_sys_madvise mm/madvise.c:2012 [inline]
 __x64_sys_madvise+0xa6/0xc0 mm/madvise.c:2012
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fed6019ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fed60fc5028 EFLAGS: 00000246 ORIG_RAX: 000000000000001c
RAX: ffffffffffffffda RBX: 00007fed60415fa0 RCX: 00007fed6019ce59
RDX: 0000000000000064 RSI: 0000000000001000 RDI: 0000200000ffe000
RBP: 00007fed60232e6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fed60416038 R14: 00007fed60415fa0 R15: 00007ffc1c6c3eb8
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:memory_failure+0x3373/0x3380 mm/memory-failure.c:2483
Code: cb 87 ff 48 89 df 48 c7 c6 e0 a2 dd 8b e8 45 79 e9 fe 90 0f 0b e8 7d cb 87 ff 4c 89 f7 48 c7 c6 40 93 dd 8b e8 2e 79 e9 fe 90 <0f> 0b 66 66 2e 0f 1f 84 00 00 00 00 00 90 90 90 90 90 90 90 90 90
RSP: 0018:ffffc900036efa00 EFLAGS: 00010246
RAX: 9a71b80f9d8a2200 RBX: 0000000000000000 RCX: 0000000000000000
RDX: 0000000000000006 RSI: ffffffff8dfe9eba RDI: 00000000ffffffff
RBP: ffffc900036efb90 R08: ffffffff90334ef7 R09: 1ffffffff20669de
R10: dffffc0000000000 R11: fffffbfff20669df R12: ffffea00045f6ac0
R13: ffffea00045f0008 R14: ffffea00045f6ac0 R15: 1ffff920006ddf54
FS:  00007fed60fc56c0(0000) GS:ffff8882a920e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000001b33863fff CR3: 0000000165fdc000 CR4: 00000000000006f0


***

If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
  Tested-by: syzbot@syzkaller.appspotmail.com

---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.

To test a patch for this bug, please reply with `#syz test`
(should be on a separate line).

The patch should be attached to the email.
Note: arguments like custom git repos and branches are not supported.

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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-14 17:31             ` Zi Yan
@ 2026-07-15 10:42               ` Kiryl Shutsemau
  2026-07-15 13:01                 ` David Hildenbrand (Arm)
  2026-07-15 13:27                 ` Zi Yan
  0 siblings, 2 replies; 17+ messages in thread
From: Kiryl Shutsemau @ 2026-07-15 10:42 UTC (permalink / raw)
  To: Zi Yan
  Cc: David Hildenbrand (Arm), Andrew Morton, Lorenzo Stoakes,
	Miaohe Lin, Naoya Horiguchi, Baolin Wang, Liam R . Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
	Usama Arif, Hao Zhang, Hao Zhang, linux-mm, linux-kernel, stable

On Tue, Jul 14, 2026 at 01:31:54PM -0400, Zi Yan wrote:
> On Tue Jul 14, 2026 at 12:40 PM EDT, Kiryl Shutsemau wrote:
> > On Tue, Jul 14, 2026 at 11:44:39AM -0400, Zi Yan wrote:
> >> There is an alternative, only igrab() when @lock_at is at or beyond the EOF,
> >> as I was bouncing ideas with Codex.
> >
> > I saw this option too, but I wound rather not go this path.
> >
> > iput() still can lead to inode eviction an bunch of random filesystem
> > complexity under us. I don't think we want to think about other
> > fs-related locking issues in split context.
> 
> Your reasoning makes sense to me. Let's ignore this option.
> 
> For your patch 2, we might want something like below to avoid over
> rejecting splits. WDYT?
> 
> offset = folio_page_idx(folio, lock_at);
> 
> if (split_type == SPLIT_TYPE_UNIFORM)
> 	lock_at_index = folio->index + round_down(offset, 1UL << new_order);
> else
> 	/* @lock_at in non uniform split is always @folio */
> 	lock_at_index = folio->index;
> 
> if (lock_at_index >= end) {
> 	ret = -EBUSY;
> 	goto out_unlock;
> }
> 

Right. With the -EBUSY condition growing this hairy -- and having to stay
correct for non-uniform splits too -- just moving i_mmap_unlock_read() out
of the window looks more attractive.

This is really Hao's original patch with the reasoning corrected, so I kept
him as author. v3 below.

----------------------------------------------------------------------

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
Subject: [PATCH v3] mm/huge_memory: unlock i_mmap_rwsem before releasing
 after-split folios

__folio_split() keeps dereferencing the mapping after the split:
shmem_uncharge(mapping->host) and remap_page() while the folios are still
frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the
after-split folios have been unlocked and freed.

Nothing holds an inode reference across that. The split relies on @folio
-- which the beyond-EOF drop loop never removes, as it starts at
folio_next(folio) -- staying locked and in the page cache to hold off
eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read()
runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure()
passes when splitting a poisoned tail of a shmem THP that reaches past
i_size during truncation, it too is gone from the page cache; so once
@folio is unlocked no locked, in-cache folio pins the inode, and a
concurrent final iput() can evict and RCU-free it before
i_mmap_unlock_read() touches i_mmap_rwsem:

  BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790
   i_mmap_unlock_read include/linux/fs.h:537 [inline]
   __folio_split+0x732/0x1640 mm/huge_memory.c:4100
   try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675
   memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470

  Freed by task 4601:
   shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177
   evict+0x57f/0xac0 fs/inode.c:870

Do every mapping dereference while @folio still pins the inode: drop
i_mmap_rwsem right after remap_page(), before the loop that unlocks and
frees the after-split folios, and clear @mapping so the exit path does not
unlock it again. shmem_uncharge() and remap_page() already run before that
point, so after this nothing past the unlock loop touches the inode or the
mapping.

This is now a rule the split depends on, alongside keeping @folio frozen
until the page cache is updated: no inode or mapping dereference once the
after-split folios start being unlocked.

Reported-by: Hao Zhang <zhanghao1@kylinos.cn>
Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc
Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
Cc: <stable@vger.kernel.org>
Co-developed-by: Hao Zhang <zhanghao1@kylinos.cn>
Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
 mm/huge_memory.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2bccb0a53a0a..abaea34ef558 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4109,6 +4109,18 @@ static int __folio_split(struct folio *folio, unsigned int new_order,

 	remap_page(folio, 1 << old_order, ttu_flags);

+	/*
+	 * Drop the mapping while the inode is still pinned. @folio stays
+	 * locked and present in the page cache until the loop below, so
+	 * eviction cannot free the inode yet; @lock_at is not enough, it may
+	 * be a tail beyond EOF that the split already dropped from the page
+	 * cache. Nothing past this point may touch the inode or the mapping.
+	 */
+	if (mapping) {
+		i_mmap_unlock_read(mapping);
+		mapping = NULL;
+	}
+
 	/*
 	 * Unlock all after-split folios except the one containing
 	 * @lock_at page. If @folio is not split, it will be kept locked.
-- 
  Kiryl Shutsemau / Kirill A. Shutemov

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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-15 10:42               ` Kiryl Shutsemau
@ 2026-07-15 13:01                 ` David Hildenbrand (Arm)
  2026-07-15 13:27                 ` Zi Yan
  1 sibling, 0 replies; 17+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-15 13:01 UTC (permalink / raw)
  To: Kiryl Shutsemau, Zi Yan
  Cc: Andrew Morton, Lorenzo Stoakes, Miaohe Lin, Naoya Horiguchi,
	Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Barry Song, Lance Yang, Usama Arif, Hao Zhang, Hao Zhang,
	linux-mm, linux-kernel, stable

On 7/15/26 12:42, Kiryl Shutsemau wrote:
> On Tue, Jul 14, 2026 at 01:31:54PM -0400, Zi Yan wrote:
>> On Tue Jul 14, 2026 at 12:40 PM EDT, Kiryl Shutsemau wrote:
>>>
>>> I saw this option too, but I wound rather not go this path.
>>>
>>> iput() still can lead to inode eviction an bunch of random filesystem
>>> complexity under us. I don't think we want to think about other
>>> fs-related locking issues in split context.
>>
>> Your reasoning makes sense to me. Let's ignore this option.
>>
>> For your patch 2, we might want something like below to avoid over
>> rejecting splits. WDYT?
>>
>> offset = folio_page_idx(folio, lock_at);
>>
>> if (split_type == SPLIT_TYPE_UNIFORM)
>> 	lock_at_index = folio->index + round_down(offset, 1UL << new_order);
>> else
>> 	/* @lock_at in non uniform split is always @folio */
>> 	lock_at_index = folio->index;
>>
>> if (lock_at_index >= end) {
>> 	ret = -EBUSY;
>> 	goto out_unlock;
>> }
>>
> 
> Right. With the -EBUSY condition growing this hairy -- and having to stay
> correct for non-uniform splits too -- just moving i_mmap_unlock_read() out
> of the window looks more attractive.
> 
> This is really Hao's original patch with the reasoning corrected, so I kept
> him as author. v3 below.
> 
> ----------------------------------------------------------------------
> 
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> Subject: [PATCH v3] mm/huge_memory: unlock i_mmap_rwsem before releasing
>  after-split folios
> 
> __folio_split() keeps dereferencing the mapping after the split:
> shmem_uncharge(mapping->host) and remap_page() while the folios are still
> frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the
> after-split folios have been unlocked and freed.
> 
> Nothing holds an inode reference across that. The split relies on @folio
> -- which the beyond-EOF drop loop never removes, as it starts at
> folio_next(folio) -- staying locked and in the page cache to hold off
> eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read()
> runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure()
> passes when splitting a poisoned tail of a shmem THP that reaches past
> i_size during truncation, it too is gone from the page cache; so once
> @folio is unlocked no locked, in-cache folio pins the inode, and a
> concurrent final iput() can evict and RCU-free it before
> i_mmap_unlock_read() touches i_mmap_rwsem:
> 
>   BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790
>    i_mmap_unlock_read include/linux/fs.h:537 [inline]
>    __folio_split+0x732/0x1640 mm/huge_memory.c:4100
>    try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675
>    memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470
> 
>   Freed by task 4601:
>    shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177
>    evict+0x57f/0xac0 fs/inode.c:870
> 
> Do every mapping dereference while @folio still pins the inode: drop
> i_mmap_rwsem right after remap_page(), before the loop that unlocks and
> frees the after-split folios, and clear @mapping so the exit path does not
> unlock it again. shmem_uncharge() and remap_page() already run before that
> point, so after this nothing past the unlock loop touches the inode or the
> mapping.
> 
> This is now a rule the split depends on, alongside keeping @folio frozen
> until the page cache is updated: no inode or mapping dereference once the
> after-split folios start being unlocked.
> 
> Reported-by: Hao Zhang <zhanghao1@kylinos.cn>
> Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc
> Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
> Cc: <stable@vger.kernel.org>
> Co-developed-by: Hao Zhang <zhanghao1@kylinos.cn>
> Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
> Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> ---
>  mm/huge_memory.c | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
> 
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 2bccb0a53a0a..abaea34ef558 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4109,6 +4109,18 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
> 
>  	remap_page(folio, 1 << old_order, ttu_flags);
> 
> +	/*
> +	 * Drop the mapping while the inode is still pinned. @folio stays
> +	 * locked and present in the page cache until the loop below, so
> +	 * eviction cannot free the inode yet; @lock_at is not enough, it may
> +	 * be a tail beyond EOF that the split already dropped from the page
> +	 * cache. Nothing past this point may touch the inode or the mapping.
> +	 */
> +	if (mapping) {
> +		i_mmap_unlock_read(mapping);
> +		mapping = NULL;
> +	}
> +
>  	/*
>  	 * Unlock all after-split folios except the one containing
>  	 * @lock_at page. If @folio is not split, it will be kept locked.

LGTM

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David

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

* Re: [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split
  2026-07-15 10:42               ` Kiryl Shutsemau
  2026-07-15 13:01                 ` David Hildenbrand (Arm)
@ 2026-07-15 13:27                 ` Zi Yan
  1 sibling, 0 replies; 17+ messages in thread
From: Zi Yan @ 2026-07-15 13:27 UTC (permalink / raw)
  To: Kiryl Shutsemau
  Cc: David Hildenbrand (Arm), Andrew Morton, Lorenzo Stoakes,
	Miaohe Lin, Naoya Horiguchi, Baolin Wang, Liam R . Howlett,
	Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
	Usama Arif, Hao Zhang, Hao Zhang, linux-mm, linux-kernel, stable

On 15 Jul 2026, at 6:42, Kiryl Shutsemau wrote:

> On Tue, Jul 14, 2026 at 01:31:54PM -0400, Zi Yan wrote:
>> On Tue Jul 14, 2026 at 12:40 PM EDT, Kiryl Shutsemau wrote:
>>> On Tue, Jul 14, 2026 at 11:44:39AM -0400, Zi Yan wrote:
>>>> There is an alternative, only igrab() when @lock_at is at or beyond the EOF,
>>>> as I was bouncing ideas with Codex.
>>>
>>> I saw this option too, but I wound rather not go this path.
>>>
>>> iput() still can lead to inode eviction an bunch of random filesystem
>>> complexity under us. I don't think we want to think about other
>>> fs-related locking issues in split context.
>>
>> Your reasoning makes sense to me. Let's ignore this option.
>>
>> For your patch 2, we might want something like below to avoid over
>> rejecting splits. WDYT?
>>
>> offset = folio_page_idx(folio, lock_at);
>>
>> if (split_type == SPLIT_TYPE_UNIFORM)
>> 	lock_at_index = folio->index + round_down(offset, 1UL << new_order);
>> else
>> 	/* @lock_at in non uniform split is always @folio */
>> 	lock_at_index = folio->index;
>>
>> if (lock_at_index >= end) {
>> 	ret = -EBUSY;
>> 	goto out_unlock;
>> }
>>
>
> Right. With the -EBUSY condition growing this hairy -- and having to stay
> correct for non-uniform splits too -- just moving i_mmap_unlock_read() out
> of the window looks more attractive.
>
> This is really Hao's original patch with the reasoning corrected, so I kept
> him as author. v3 below.
>
> ----------------------------------------------------------------------
>
> From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
> Subject: [PATCH v3] mm/huge_memory: unlock i_mmap_rwsem before releasing
>  after-split folios
>
> __folio_split() keeps dereferencing the mapping after the split:
> shmem_uncharge(mapping->host) and remap_page() while the folios are still
> frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the
> after-split folios have been unlocked and freed.
>
> Nothing holds an inode reference across that. The split relies on @folio
> -- which the beyond-EOF drop loop never removes, as it starts at
> folio_next(folio) -- staying locked and in the page cache to hold off
> eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read()
> runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure()
> passes when splitting a poisoned tail of a shmem THP that reaches past
> i_size during truncation, it too is gone from the page cache; so once
> @folio is unlocked no locked, in-cache folio pins the inode, and a
> concurrent final iput() can evict and RCU-free it before
> i_mmap_unlock_read() touches i_mmap_rwsem:
>
>   BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790
>    i_mmap_unlock_read include/linux/fs.h:537 [inline]
>    __folio_split+0x732/0x1640 mm/huge_memory.c:4100
>    try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675
>    memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470
>
>   Freed by task 4601:
>    shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177
>    evict+0x57f/0xac0 fs/inode.c:870
>
> Do every mapping dereference while @folio still pins the inode: drop
> i_mmap_rwsem right after remap_page(), before the loop that unlocks and
> frees the after-split folios, and clear @mapping so the exit path does not
> unlock it again. shmem_uncharge() and remap_page() already run before that
> point, so after this nothing past the unlock loop touches the inode or the
> mapping.
>
> This is now a rule the split depends on, alongside keeping @folio frozen
> until the page cache is updated: no inode or mapping dereference once the
> after-split folios start being unlocked.
>
> Reported-by: Hao Zhang <zhanghao1@kylinos.cn>
> Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc
> Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()")
> Cc: <stable@vger.kernel.org>
> Co-developed-by: Hao Zhang <zhanghao1@kylinos.cn>
> Signed-off-by: Hao Zhang <zhanghao1@kylinos.cn>
> Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> ---
>  mm/huge_memory.c | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 2bccb0a53a0a..abaea34ef558 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4109,6 +4109,18 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>
>  	remap_page(folio, 1 << old_order, ttu_flags);
>
> +	/*
> +	 * Drop the mapping while the inode is still pinned. @folio stays
> +	 * locked and present in the page cache until the loop below, so
> +	 * eviction cannot free the inode yet; @lock_at is not enough, it may
> +	 * be a tail beyond EOF that the split already dropped from the page
> +	 * cache. Nothing past this point may touch the inode or the mapping.
> +	 */
> +	if (mapping) {
> +		i_mmap_unlock_read(mapping);
> +		mapping = NULL;
> +	}
> +
>  	/*
>  	 * Unlock all after-split folios except the one containing
>  	 * @lock_at page. If @folio is not split, it will be kept locked.
> -- 
>   Kiryl Shutsemau / Kirill A. Shutemov

LGTM. Thanks.

Reviewed-by: Zi Yan <ziy@nvidia.com>

Best Regards,
Yan, Zi


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

end of thread, other threads:[~2026-07-15 13:27 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-14 12:23 [PATCH v2 0/5] mm: fix inode UAF when splitting a file folio past EOF Kiryl Shutsemau
2026-07-14 12:23 ` [PATCH v2 1/5] mm/memory-failure: keep the folio, not the poisoned subpage, locked across split Kiryl Shutsemau
2026-07-14 13:01   ` David Hildenbrand (Arm)
2026-07-14 14:53     ` Kiryl Shutsemau
2026-07-14 14:58       ` David Hildenbrand (Arm)
2026-07-14 15:44         ` Zi Yan
2026-07-14 16:40           ` Kiryl Shutsemau
2026-07-14 17:31             ` Zi Yan
2026-07-15 10:42               ` Kiryl Shutsemau
2026-07-15 13:01                 ` David Hildenbrand (Arm)
2026-07-15 13:27                 ` Zi Yan
2026-07-14 13:05   ` Kiryl Shutsemau
2026-07-14 12:23 ` [PATCH v2 2/5] mm/huge_memory: refuse to split a file folio when the anchor is beyond EOF Kiryl Shutsemau
2026-07-14 12:23 ` [PATCH v2 3/5] mm/huge_memory: remove unused split_huge_page_to_order() Kiryl Shutsemau
2026-07-14 12:23 ` [PATCH v2 4/5] mm/huge_memory: remove unused can_split_folio() Kiryl Shutsemau
2026-07-14 12:23 ` [PATCH v2 5/5] mm/huge_memory: fold split_folio_to_list_to_order() into split_folio_to_order() Kiryl Shutsemau
2026-07-15  5:55 ` [syzbot ci] Re: mm: fix inode UAF when splitting a file folio past EOF syzbot ci

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.