All of lore.kernel.org
 help / color / mirror / Atom feed
From: Sasha Levin <sashal@kernel.org>
To: stable@vger.kernel.org
Cc: Josef Bacik <josef@toxicpanda.com>,
	Rik van Riel <riel@surriel.com>,
	Muhammad Usama Anjum <usama.anjum@arm.com>,
	Zi Yan <ziy@nvidia.com>,
	"David Hildenbrand (Arm)" <david@kernel.org>,
	Sasha Levin <sashal@kernel.org>
Subject: [PATCH 6.1.y] mm/filemap: read mapping->a_ops->free_folio under i_lock
Date: Fri, 13 Nov 2026 23:46:07 -0500	[thread overview]
Message-ID: <20261114044555.2958175-1-sashal@kernel.org> (raw)
In-Reply-To: <20261109201558-failed-6.1-dc24e273244e@kernel.org>

From: Josef Bacik <josef@toxicpanda.com>

[ Upstream commit dc24e273244e9d712257d5a6c20a32c7f5ca84e6 ]

memory_failure() -> ... -> filemap_remove_folio() can read mapping->a_ops
after the inode has been freed. Once the folio is deleted from the
pagecache it no longer pins the mapping, so after i_lock is dropped
e.g., a concurrent final iput() can evict and free the inode before
filemap_free_folio() looks up mapping->a_ops->free_folio.

Most callers hold a reference on the inode, but not all.

Documentation/filesystems/vfs.rst explicitly states that ->free_folio()
must not assume the original address_space mapping still exists.

Fix it by loading ->free_folio while i_lock is still held, similar to
what folio_unmap_invalidate() does.

Inline filemap_free_folio() into its remaining caller such that
delete_from_page_cache_batch() also just loads the callback before
dropping the i_lock.

The bug was found by code inspection and has not been reproduced.

Fix generated by an LLM and sent by Josef Bacik in private.

Fixes: 59c66c5f8c4f ("mm: factor out page cache page freeing into a separate function")
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
[ david: get rid of filemap_free_folio() entirely; improve patch description ]
Reviewed-by: Rik van Riel <riel@surriel.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Link: https://patch.msgid.link/20261031-fixes_core-v3-6-d6437171ecc3@kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
[ sashal: preserved hugetlb-aware reference counting in the inlined cleanup, including the additional invalidate_complete_folio2() caller ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 mm/filemap.c  | 37 ++++++++++++++++++++-----------------
 mm/internal.h |  1 -
 mm/truncate.c | 10 +++++++++-
 3 files changed, 29 insertions(+), 19 deletions(-)

diff --git a/mm/filemap.c b/mm/filemap.c
index 7c996dc4dad28..61581b59d0ff3 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -223,20 +223,6 @@ void __filemap_remove_folio(struct folio *folio, void *shadow)
 	page_cache_delete(mapping, folio, shadow);
 }
 
-void filemap_free_folio(struct address_space *mapping, struct folio *folio)
-{
-	void (*free_folio)(struct folio *);
-	int refs = 1;
-
-	free_folio = mapping->a_ops->free_folio;
-	if (free_folio)
-		free_folio(folio);
-
-	if (folio_test_large(folio) && !folio_test_hugetlb(folio))
-		refs = folio_nr_pages(folio);
-	folio_put_refs(folio, refs);
-}
-
 /**
  * filemap_remove_folio - Remove folio from page cache.
  * @folio: The folio.
@@ -248,6 +234,8 @@ void filemap_free_folio(struct address_space *mapping, struct folio *folio)
 void filemap_remove_folio(struct folio *folio)
 {
 	struct address_space *mapping = folio->mapping;
+	void (*free_folio)(struct folio *folio);
+	int refs = 1;
 
 	BUG_ON(!folio_test_locked(folio));
 	spin_lock(&mapping->host->i_lock);
@@ -256,9 +244,14 @@ void filemap_remove_folio(struct folio *folio)
 	xa_unlock_irq(&mapping->i_pages);
 	if (mapping_shrinkable(mapping))
 		inode_add_lru(mapping->host);
+	free_folio = mapping->a_ops->free_folio;
 	spin_unlock(&mapping->host->i_lock);
 
-	filemap_free_folio(mapping, folio);
+	if (free_folio)
+		free_folio(folio);
+	if (folio_test_large(folio) && !folio_test_hugetlb(folio))
+		refs = folio_nr_pages(folio);
+	folio_put_refs(folio, refs);
 }
 
 /*
@@ -318,6 +311,7 @@ static void page_cache_delete_batch(struct address_space *mapping,
 void delete_from_page_cache_batch(struct address_space *mapping,
 				  struct folio_batch *fbatch)
 {
+	void (*free_folio)(struct folio *folio);
 	int i;
 
 	if (!folio_batch_count(fbatch))
@@ -335,10 +329,19 @@ void delete_from_page_cache_batch(struct address_space *mapping,
 	xa_unlock_irq(&mapping->i_pages);
 	if (mapping_shrinkable(mapping))
 		inode_add_lru(mapping->host);
+	free_folio = mapping->a_ops->free_folio;
 	spin_unlock(&mapping->host->i_lock);
 
-	for (i = 0; i < folio_batch_count(fbatch); i++)
-		filemap_free_folio(mapping, fbatch->folios[i]);
+	for (i = 0; i < folio_batch_count(fbatch); i++) {
+		struct folio *folio = fbatch->folios[i];
+		int refs = 1;
+
+		if (free_folio)
+			free_folio(folio);
+		if (folio_test_large(folio) && !folio_test_hugetlb(folio))
+			refs = folio_nr_pages(folio);
+		folio_put_refs(folio, refs);
+	}
 }
 
 int filemap_check_errors(struct address_space *mapping)
diff --git a/mm/internal.h b/mm/internal.h
index f99d9812c48d7..dd27162f310c1 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -129,7 +129,6 @@ unsigned find_lock_entries(struct address_space *mapping, pgoff_t *start,
 		pgoff_t end, struct folio_batch *fbatch, pgoff_t *indices);
 unsigned find_get_entries(struct address_space *mapping, pgoff_t start,
 		pgoff_t end, struct folio_batch *fbatch, pgoff_t *indices);
-void filemap_free_folio(struct address_space *mapping, struct folio *folio);
 int truncate_inode_folio(struct address_space *mapping, struct folio *folio);
 bool truncate_inode_partial_folio(struct folio *folio, loff_t start,
 		loff_t end);
diff --git a/mm/truncate.c b/mm/truncate.c
index cd6768ca5b975..785bfc4147e8b 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -597,6 +597,9 @@ EXPORT_SYMBOL(invalidate_mapping_pages);
 static int invalidate_complete_folio2(struct address_space *mapping,
 					struct folio *folio)
 {
+	void (*free_folio)(struct folio *folio);
+	int refs = 1;
+
 	if (folio->mapping != mapping)
 		return 0;
 
@@ -613,9 +616,14 @@ static int invalidate_complete_folio2(struct address_space *mapping,
 	xa_unlock_irq(&mapping->i_pages);
 	if (mapping_shrinkable(mapping))
 		inode_add_lru(mapping->host);
+	free_folio = mapping->a_ops->free_folio;
 	spin_unlock(&mapping->host->i_lock);
 
-	filemap_free_folio(mapping, folio);
+	if (free_folio)
+		free_folio(folio);
+	if (folio_test_large(folio) && !folio_test_hugetlb(folio))
+		refs = folio_nr_pages(folio);
+	folio_put_refs(folio, refs);
 	return 1;
 failed:
 	xa_unlock_irq(&mapping->i_pages);
-- 
2.53.0


      reply	other threads:[~2026-11-14  4:46 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10  1:15 FAILED: patch "[PATCH] mm/filemap: read mapping->a_ops->free_folio under i_lock" failed to apply to 6.1-stable tree Sasha Levin
2026-11-14  4:46 ` Sasha Levin [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261114044555.2958175-1-sashal@kernel.org \
    --to=sashal@kernel.org \
    --cc=david@kernel.org \
    --cc=josef@toxicpanda.com \
    --cc=riel@surriel.com \
    --cc=stable@vger.kernel.org \
    --cc=usama.anjum@arm.com \
    --cc=ziy@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.