Commit 615d9bb2 authored by Matthew Wilcox (Oracle)'s avatar Matthew Wilcox (Oracle) Committed by Andrew Morton
Browse files

mm: call ->free_folio() directly in folio_unmap_invalidate()

We can only call filemap_free_folio() if we have a reference to (or hold a
lock on) the mapping.  Otherwise, we've already removed the folio from the
mapping so it no longer pins the mapping and the mapping can be removed,
causing a use-after-free when accessing mapping->a_ops.

Follow the same pattern as __remove_mapping() and load the free_folio
function pointer before dropping the lock on the mapping.  That lets us
make filemap_free_folio() static as this was the only caller outside
filemap.c.

Link: https://lore.kernel.org/20260413184314.3419945-1-willy@infradead.org


Fixes: fb7d3bc4 ("mm/filemap: drop streaming/uncached pages when writeback completes")
Signed-off-by: default avatarMatthew Wilcox (Oracle) <willy@infradead.org>
Reported-by: default avatarGoogle Big Sleep <big-sleep-vuln-reports+bigsleep-501448199@google.com>
Cc: Jens Axboe <axboe@kernel.dk>
Cc: Jan Kara <jack@suse.cz>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent 8f5857be
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -228,7 +228,8 @@ 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)
static void filemap_free_folio(const struct address_space *mapping,
		struct folio *folio)
{
	void (*free_folio)(struct folio *);

+0 −1
Original line number Diff line number Diff line
@@ -540,7 +540,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);
+5 −1
Original line number Diff line number Diff line
@@ -622,6 +622,7 @@ static int folio_launder(struct address_space *mapping, struct folio *folio)
int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
			   gfp_t gfp)
{
	void (*free_folio)(struct folio *);
	int ret;

	VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
@@ -648,9 +649,12 @@ int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
	xa_unlock_irq(&mapping->i_pages);
	if (mapping_shrinkable(mapping))
		inode_lru_list_add(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);
	folio_put_refs(folio, folio_nr_pages(folio));
	return 1;
failed:
	xa_unlock_irq(&mapping->i_pages);