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 5.15.y] mm/filemap: read mapping->a_ops->free_folio under i_lock
Date: Fri, 13 Nov 2026 23:56:44 -0500 [thread overview]
Message-ID: <20261114045632.2986009-1-sashal@kernel.org> (raw)
In-Reply-To: <20261109202107-failed-5.15-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: Adapted callback capture from free_folio under i_lock to freepage under i_pages. ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
mm/filemap.c | 44 ++++++++++++++++++++++++--------------------
1 file changed, 24 insertions(+), 20 deletions(-)
diff --git a/mm/filemap.c b/mm/filemap.c
index 9fec53a3f5505..eccc5318b6644 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -232,23 +232,6 @@ void __delete_from_page_cache(struct page *page, void *shadow)
page_cache_delete(mapping, page, shadow);
}
-static void page_cache_free_page(struct address_space *mapping,
- struct page *page)
-{
- void (*freepage)(struct page *);
-
- freepage = mapping->a_ops->freepage;
- if (freepage)
- freepage(page);
-
- if (PageTransHuge(page) && !PageHuge(page)) {
- page_ref_sub(page, thp_nr_pages(page));
- VM_BUG_ON_PAGE(page_count(page) <= 0, page);
- } else {
- put_page(page);
- }
-}
-
/**
* delete_from_page_cache - delete page from page cache
* @page: the page which the kernel is trying to remove from page cache
@@ -260,13 +243,22 @@ static void page_cache_free_page(struct address_space *mapping,
void delete_from_page_cache(struct page *page)
{
struct address_space *mapping = page_mapping(page);
+ void (*freepage)(struct page *page);
BUG_ON(!PageLocked(page));
xa_lock_irq(&mapping->i_pages);
__delete_from_page_cache(page, NULL);
+ freepage = mapping->a_ops->freepage;
xa_unlock_irq(&mapping->i_pages);
- page_cache_free_page(mapping, page);
+ if (freepage)
+ freepage(page);
+ if (PageTransHuge(page) && !PageHuge(page)) {
+ page_ref_sub(page, thp_nr_pages(page));
+ VM_BUG_ON_PAGE(page_count(page) <= 0, page);
+ } else {
+ put_page(page);
+ }
}
EXPORT_SYMBOL(delete_from_page_cache);
@@ -335,6 +327,7 @@ static void page_cache_delete_batch(struct address_space *mapping,
void delete_from_page_cache_batch(struct address_space *mapping,
struct pagevec *pvec)
{
+ void (*freepage)(struct page *page);
int i;
if (!pagevec_count(pvec))
@@ -347,10 +340,21 @@ void delete_from_page_cache_batch(struct address_space *mapping,
unaccount_page_cache_page(mapping, pvec->pages[i]);
}
page_cache_delete_batch(mapping, pvec);
+ freepage = mapping->a_ops->freepage;
xa_unlock_irq(&mapping->i_pages);
- for (i = 0; i < pagevec_count(pvec); i++)
- page_cache_free_page(mapping, pvec->pages[i]);
+ for (i = 0; i < pagevec_count(pvec); i++) {
+ struct page *page = pvec->pages[i];
+
+ if (freepage)
+ freepage(page);
+ if (PageTransHuge(page) && !PageHuge(page)) {
+ page_ref_sub(page, thp_nr_pages(page));
+ VM_BUG_ON_PAGE(page_count(page) <= 0, page);
+ } else {
+ put_page(page);
+ }
+ }
}
int filemap_check_errors(struct address_space *mapping)
--
2.53.0
prev parent reply other threads:[~2026-11-14 4:56 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 1:21 FAILED: patch "[PATCH] mm/filemap: read mapping->a_ops->free_folio under i_lock" failed to apply to 5.15-stable tree Sasha Levin
2026-11-14 4:56 ` 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=20261114045632.2986009-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.