A page cache folio that is not uptodate holds data that was never read in or cleared. shmem fallocate(2), for one, adds such folios and clears them only on first use. Mapping one hands userspace whatever the memory held before. Each path that maps page cache checks for this itself: fault-around in next_uptodate_folio(), a fault in filemap_fault() or shmem_get_folio(). Nothing in the mapping code does, so a caller that misses the check leaks stale memory silently. MADV_COLLAPSE did exactly that with fallocated shmem THPs until recently. Warn and refuse the mapping in __do_fault() and do_set_pmd(). __do_fault() checks the folio ->fault() returned while it is still locked. For CoW, that is the page cache folio that do_cow_fault() copies from. do_set_pmd() covers MADV_COLLAPSE, which maps without a fault. do_shared_fault() then unlocks the folio for ->page_mkwrite(). A filesystem that clears PG_uptodate on a folio in the page cache is buggy, so that window gets no check of its own. The fault gets SIGBUS, as for a folio that fails to read in. MADV_COLLAPSE fails and leaves the range to be faulted in. Suggested-by: David Hildenbrand (Arm) Signed-off-by: Kiryl Shutsemau (Meta) --- v3: - Drop "mm/memory: recheck PG_uptodate after ->page_mkwrite()": a filesystem that clears PG_uptodate on a cached folio is buggy (Matthew) - Move file_folio_not_uptodate() into this patch, assert it never sees an anon folio, reword its comment and drop likely() (Lorenzo) - Rebase on mm/core.git for-next, which has the MADV_COLLAPSE fix v2: https://lore.kernel.org/all/20261109120403.82361-1-kas@kernel.org/ v1: https://lore.kernel.org/all/20261106144323.3634477-1-kirill@shutemov.name/ mm/memory.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/mm/memory.c b/mm/memory.c index fcc7ea2a00d1..c79c1c6f6141 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -5641,6 +5641,22 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) return VM_FAULT_OOM; } +/* + * A page cache folio that is not uptodate holds data that was never read in + * or cleared, and mapping it leaks stale memory to userspace. + */ +static bool file_folio_not_uptodate(struct folio *folio) +{ + VM_WARN_ON_ONCE_FOLIO(folio_test_anon(folio), folio); + if (folio_test_uptodate(folio)) + return false; + /* + * ->fault() can also return driver memory that is not in the page + * cache. It never gets PG_uptodate. + */ + return folio->mapping; +} + /* * Either the VMA lock or the mmap_lock must have been held on entry * (see FAULT_FLAG_VMA_LOCK) and may have been released depending on @@ -5700,6 +5716,13 @@ static vm_fault_t __do_fault(struct vm_fault *vmf) else VM_BUG_ON_PAGE(!folio_test_locked(folio), vmf->page); + if (WARN_ON_ONCE(file_folio_not_uptodate(folio))) { + folio_unlock(folio); + folio_put(folio); + vmf->page = NULL; + return VM_FAULT_SIGBUS; + } + return ret; } @@ -5745,6 +5768,9 @@ vm_fault_t do_set_pmd(struct vm_fault *vmf, struct folio *folio, struct page *pa return ret; page = &folio->page; + if (WARN_ON_ONCE(file_folio_not_uptodate(folio))) + return VM_FAULT_SIGBUS; + /* * Just backoff if any subpage of a THP is corrupted otherwise * the corrupted page may mapped by PMD silently to escape the Range-diff against v2: 1: 632ab50e643d < -: ------------ mm/memory: recheck PG_uptodate after ->page_mkwrite() 2: 1e329f330c41 ! 1: 50ea9bc46c05 mm/memory: refuse to map page cache folios that are not uptodate @@ Commit message Warn and refuse the mapping in __do_fault() and do_set_pmd(). __do_fault() checks the folio ->fault() returned while it is still - locked. do_shared_fault() unlocks it for ->page_mkwrite(), and a - writeback error can clear PG_uptodate after that; that case retries the - fault instead. For CoW, __do_fault() sees the page cache folio that - do_cow_fault() copies from. do_set_pmd() covers MADV_COLLAPSE, which - maps without a fault. + locked. For CoW, that is the page cache folio that do_cow_fault() + copies from. do_set_pmd() covers MADV_COLLAPSE, which maps without a + fault. + + do_shared_fault() then unlocks the folio for ->page_mkwrite(). A + filesystem that clears PG_uptodate on a folio in the page cache is + buggy, so that window gets no check of its own. The fault gets SIGBUS, as for a folio that fails to read in. MADV_COLLAPSE fails and leaves the range to be faulted in. @@ Commit message Signed-off-by: Kiryl Shutsemau (Meta) ## mm/memory.c ## +@@ mm/memory.c: static vm_fault_t do_anonymous_page(struct vm_fault *vmf) + return VM_FAULT_OOM; + } + ++/* ++ * A page cache folio that is not uptodate holds data that was never read in ++ * or cleared, and mapping it leaks stale memory to userspace. ++ */ ++static bool file_folio_not_uptodate(struct folio *folio) ++{ ++ VM_WARN_ON_ONCE_FOLIO(folio_test_anon(folio), folio); ++ if (folio_test_uptodate(folio)) ++ return false; ++ /* ++ * ->fault() can also return driver memory that is not in the page ++ * cache. It never gets PG_uptodate. ++ */ ++ return folio->mapping; ++} ++ + /* + * Either the VMA lock or the mmap_lock must have been held on entry + * (see FAULT_FLAG_VMA_LOCK) and may have been released depending on @@ mm/memory.c: static vm_fault_t __do_fault(struct vm_fault *vmf) else VM_BUG_ON_PAGE(!folio_test_locked(folio), vmf->page); base-commit: 8d21e3d3faf19f461f6d0380fe8a1f5bdba97f25 -- 2.54.0