From 71555ae8c7572fd99158013ca2e29187c5d2508d Mon Sep 17 00:00:00 2001 From: Samiullah Khawaja Date: Sat, 31 Oct 2026 20:35:40 +0000 Subject: [PATCH] mm/gup: check folio->mapping under folio lock in memfd_pin_folios() memfd_pin_folios() pins folios from the page cache without taking the folio lock. khugepaged or MADV_COLLAPSE can therefore replace a folio after checking its refcount while memfd_pin_folios() still pins the old folio. The caller then receives a folio which is no longer part of the memfd. For udmabuf and iommufd, this can cause data corruption: DMA accesses the detached folio rather than the live memfd contents. DMA reads can return stale data, while DMA writes update the detached copy and are lost from the live memfd. The detached folio remains pinned, so these accesses do not constitute a use-after-free or target another application's memory. For memfd live update, the stale contents can reappear in the memfd after handover. Lock the folio and recheck folio->mapping before pinning it, like other page cache users do. If the folio was removed, retry the folio lookup from the index that was removed. Fixes: 89c1905d9c14 ("mm/gup: introduce memfd_pin_folios() for pinning memfd folios") Reported-by: Andy Lutomirski Closes: https://lore.kernel.org/all/CALCETrV5eqg6d9=+BQhCpRwBNe3aCQiut8vxPBs8b7PToqjaHg@mail.gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Samiullah Khawaja Reviewed-by: Andy Lutomirski Link: https://patch.msgid.link/20261031203540.2282507-1-skhawaja@google.com Signed-off-by: David Hildenbrand (Arm) --- mm/gup.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/mm/gup.c b/mm/gup.c index ba4907b35232..2b405cce2ca5 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -3471,7 +3471,7 @@ long memfd_pin_folios(struct file *memfd, loff_t start, loff_t end, { unsigned int flags, nr_folios, nr_found; unsigned int i, pgshift = PAGE_SHIFT; - pgoff_t start_idx, end_idx; + pgoff_t start_idx, end_idx, retry_idx; struct folio *folio = NULL; struct folio_batch fbatch; struct hstate *h; @@ -3512,6 +3512,7 @@ long memfd_pin_folios(struct file *memfd, loff_t start, loff_t end, * reason, we try to allocate them and add them to the * page cache. */ + retry_idx = start_idx; nr_found = filemap_get_folios_contig(memfd->f_mapping, &start_idx, end_idx, @@ -3524,7 +3525,21 @@ long memfd_pin_folios(struct file *memfd, loff_t start, loff_t end, for (i = 0; i < nr_found; i++) { folio = fbatch.folios[i]; + /* + * The folio might have been removed from the + * page cache (e.g., truncated or collapsed) + * after the lookup. Recheck under the folio + * lock. + */ + folio_lock(folio); + if (unlikely(folio->mapping != memfd->f_mapping)) { + folio_unlock(folio); + start_idx = retry_idx; + break; + } + if (try_grab_folio(folio, 1, FOLL_PIN)) { + folio_unlock(folio); folio_batch_release(&fbatch); ret = -EINVAL; goto err; @@ -3535,14 +3550,12 @@ long memfd_pin_folios(struct file *memfd, loff_t start, loff_t end, * page cache that were never cleared. */ if (!folio_test_uptodate(folio)) { - folio_lock(folio); - if (!folio_test_uptodate(folio)) { - folio_zero_range(folio, 0, folio_size(folio)); - flush_dcache_folio(folio); - folio_mark_uptodate(folio); - } - folio_unlock(folio); + folio_zero_range(folio, 0, folio_size(folio)); + flush_dcache_folio(folio); + folio_mark_uptodate(folio); } + retry_idx = folio_next_index(folio); + folio_unlock(folio); if (nr_folios == 0) *offset = offset_in_folio(folio, start);