* [PATCH 0/5] mm: limit pagecache folio refcount
@ 2026-11-05 13:10 David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 1/5] mm: limit pagecache folio refcount when mapping pages David Hildenbrand (Arm)
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 13:10 UTC (permalink / raw)
To: Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Matthew Wilcox (Oracle), Jan Kara, Zi Yan, Baolin Wang,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Peter Xu, Johannes Weiner, Song Liu, Rik van Riel, Hugh Dickins,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD), Robin Murphy,
Steve Sistare, John Hubbard
Cc: linux-mm, linux-kernel, linux-fsdevel, linux-arch, iommu,
David Hildenbrand (Arm), Chris Mason, stable
Users can create a lot of folio references to pagecache folios, making
it possible to overflow the refcount of large folios.
While we knew that one could in theory overflow a folio's refcount, we
mostly assumed that bad things would happen and it wouldn't go
unnoticed.
Not so sure if that is really always the case, and leaving that problem
unsolved is irresponsible, so let's limit pagecache folio refcounts.
This is really only relevant for large folios / thps in the page cache.
With anon folios (including THPs), we should not be
able to trigger folio refcount overflows (until we get PUD-sized ones ;) ).
It was likely a problem for anon folios as well before we had a
dedicated pincount.
Let's limit the refcount before it turns negative, giving admin processes
some extra reserves (to reover a problematic system). We still leave some
headroom to allow for some slight limit overshooting, to make the
implementation easier.
Then, we also check when splicing pagecache folios to a pipe, and
improve our existing checks in gup. We also have to take care of
mmu_gather, to not hold too many references when zapping (whereby new
page faults could remap pages concurrently).
This is only a temporary solution for upstream+stable, until we have
something better. These fixes will be a bit challenging to backport to
stable, but it will be much easier compared to the RFC series I have here
were we do things properly by (what we discussed a couple of times
earlier):
- Letting the mapcount of large folios be a long
- Holding a single refcount if the folio is mapped, across all mappings
- Reverting the limits introduced in this series
I'll polish that up and send it once we merged something that can be
backported to stable.
Tested on x86-64 by lowering the folio refcount limits such that limits
can easily be exceeded, to then test fault (SIGBUS), fork(), splice and GUp
behavior briefly when we run into refcount limits on a shmem THP.
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
David Hildenbrand (Arm) (5):
mm: limit pagecache folio refcount when mapping pages
mm: limit pagecache folio refcount when splicing into pipes
mm/mmu_gather: flush pending references early before the folio limit
iommufd: fix cleanup when adding memfd folio pins fails
mm/gup: limit pagecache folio refcount when grabbing references
drivers/iommu/iommufd/pages.c | 15 +++++++++++----
include/linux/mm.h | 43 ++++++++++++++++++++++++++++++++++++++++---
mm/filemap.c | 10 ++++++++++
mm/gup.c | 12 ++++++++----
mm/huge_memory.c | 24 ++++++++++++++++++++++--
mm/memory.c | 27 ++++++++++++++++++++++-----
mm/migrate.c | 11 +++++++++++
mm/mmu_gather.c | 5 +++++
mm/shmem.c | 4 ++++
mm/userfaultfd.c | 4 ++++
10 files changed, 137 insertions(+), 18 deletions(-)
---
base-commit: 02f72aa35c5e7dff443c62cd9964686396c8b201
change-id: 20261105-refcount_overflow_stable-542e024130fe
--
Cheers,
David
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/5] mm: limit pagecache folio refcount when mapping pages
2026-11-05 13:10 [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
@ 2026-11-05 13:10 ` David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 2/5] mm: limit pagecache folio refcount when splicing into pipes David Hildenbrand (Arm)
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 13:10 UTC (permalink / raw)
To: Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Matthew Wilcox (Oracle), Jan Kara, Zi Yan, Baolin Wang,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Peter Xu, Johannes Weiner, Song Liu, Rik van Riel, Hugh Dickins,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD), Robin Murphy,
Steve Sistare, John Hubbard
Cc: linux-mm, linux-kernel, linux-fsdevel, linux-arch, iommu,
David Hildenbrand (Arm), Chris Mason, stable
With large file folios, overflowing the folio mapcount and refcount
became possible. For anon folios it is currently not really an issue:
each large anon folio can only be mapped once into a process.
That's not really big news, but it's about time that we do something
about it because some modern technology might find ways to abuse it :)
One of the enablers for that was commit ddc58f27f9ee ("mm: drop tail page
refcounting"), however, file thp support was added later.
File folios can be mapped multiple times into the same process, allowing
for processes to hold many references to the same folio.
As a short-term solution, let's introduce two folio refcount limits:
one for ordinary consumers and one for admins. Leave some headroom
before the refcount goes negative, which we really don't want (having
the mapcount go negative is also not expected).
Start reporting SIGBUS if we run into limits. When restoring mappings
from migration entries, instead zap the pte/pmd entry. We allow for
slightly exceeding the limit to keep things simpler.
This is far from ideal, but better than all easy alternatives we can
backport comfortably. For now it fixes the big problem: letting
the refcount (+mapcount) go negative and cause trouble.
This is only a temporary fix, we'll follow up with something better.
While the refcount checks are not required for order 0 folios, let's
just add them for consistency: each folio_add_file_rmap*() and
folio_dup_file_rmap*() needs a prior check.
Also, just document -EHWPOISON for copy_present_ptes() while at it.
Reported-by: Chris Mason <mason@kernel.org>
Fixes: 800d8c63b2e9 ("shmem: add huge pages support")
Fixes: 99cb0dbd47a1 ("mm,thp: add read-only THP support for (non-shmem) FS")
Fixes: 793917d997df ("mm/readahead: Add large folio readahead")
Cc: stable@vger.kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/mm.h | 33 +++++++++++++++++++++++++++++++++
mm/filemap.c | 6 ++++++
mm/huge_memory.c | 24 ++++++++++++++++++++++--
mm/memory.c | 23 ++++++++++++++++++++---
mm/migrate.c | 11 +++++++++++
mm/userfaultfd.c | 4 ++++
6 files changed, 96 insertions(+), 5 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index a2dd76cc5bbfe..79ae8fc0d6bb4 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -8,6 +8,7 @@
#include <linux/gfp.h>
#include <linux/pgalloc_tag.h>
#include <linux/bug.h>
+#include <linux/capability.h>
#include <linux/list.h>
#include <linux/mmzone.h>
#include <linux/rbtree.h>
@@ -2228,6 +2229,38 @@ vm_fault_t finish_fault(struct vm_fault *vmf);
#define folio_ref_zero_or_close_to_overflow(folio) \
((unsigned int) folio_ref_count(folio) + 127u <= 127u)
+/* Leave separate reserves for administrators and concurrent reference gains. */
+#define FOLIO_REF_COUNT_LIMIT (INT_MAX - (1U << 26))
+#define FOLIO_REF_COUNT_ADMIN_LIMIT (INT_MAX - (1U << 25))
+
+/**
+ * folio_ref_count_over_limit - Check whether to refuse more folio references.
+ * @folio: The folio.
+ *
+ * Relevant for large pagecache folios (!hugetlb). For these, we have to check
+ * before adding page table mappings or other significant refcount users whether
+ * we are close to our folio ref count limit, to avoid overflows.
+ *
+ * Checking this for other folios does no harm but is mostly unnecessary.
+ *
+ * This check is an approximation; we leave some headroom such that concurrent
+ * increments, batches and temporary references are not a problem.
+ *
+ * Return: True if the caller should refuse additional references, false
+ * otherwise.
+ */
+static inline bool folio_ref_count_over_limit(const struct folio *folio)
+{
+ const unsigned int ref_count = folio_ref_count(folio);
+
+ if (likely(ref_count < FOLIO_REF_COUNT_LIMIT))
+ return false;
+ if (ref_count >= FOLIO_REF_COUNT_ADMIN_LIMIT)
+ return true;
+ /* Background threads must not consume admin reserves. */
+ return (current->flags & PF_KTHREAD) || !capable(CAP_SYS_RESOURCE);
+}
+
/**
* folio_get - Increment the reference count on a folio.
* @folio: The folio.
diff --git a/mm/filemap.c b/mm/filemap.c
index 86297841f9955..920bbf86dfdff 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3968,6 +3968,11 @@ vm_fault_t filemap_map_pages(struct vm_fault *vmf,
unsigned long end;
vm_fault_t map_ret;
+ if (unlikely(folio_ref_count_over_limit(folio))) {
+ folio_ref_dec(folio);
+ goto next;
+ }
+
addr += (xas.xa_index - last_pgoff) << PAGE_SHIFT;
vmf->pte += xas.xa_index - last_pgoff;
last_pgoff = xas.xa_index;
@@ -4009,6 +4014,7 @@ vm_fault_t filemap_map_pages(struct vm_fault *vmf,
mmap_miss - 1);
}
+next:
folio_unlock(folio);
} while ((folio = next_uptodate_folio(&xas, mapping, end_pgoff)) != NULL);
add_mm_counter(vma->vm_mm, folio_type, rss);
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index d0e19d9e6b651..2596ff0f0e185 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -1626,6 +1626,7 @@ static vm_fault_t insert_pmd(struct vm_area_struct *vma, unsigned long addr,
{
struct mm_struct *mm = vma->vm_mm;
pgtable_t pgtable = NULL;
+ vm_fault_t ret = VM_FAULT_NOPAGE;
spinlock_t *ptl;
pmd_t entry;
@@ -1662,6 +1663,10 @@ static vm_fault_t insert_pmd(struct vm_area_struct *vma, unsigned long addr,
if (is_huge_zero_folio(fop.folio)) {
entry = pmd_mkspecial(entry);
} else {
+ if (unlikely(folio_ref_count_over_limit(fop.folio))) {
+ ret = VM_FAULT_SIGBUS;
+ goto out_unlock;
+ }
folio_get(fop.folio);
folio_add_file_rmap_pmd(fop.folio, &fop.folio->page, vma);
add_mm_counter(mm, mm_counter_file(fop.folio), HPAGE_PMD_NR);
@@ -1688,7 +1693,7 @@ static vm_fault_t insert_pmd(struct vm_area_struct *vma, unsigned long addr,
spin_unlock(ptl);
if (pgtable)
pte_free(mm, pgtable);
- return VM_FAULT_NOPAGE;
+ return ret;
}
/**
@@ -1759,6 +1764,7 @@ static vm_fault_t insert_pud(struct vm_area_struct *vma, unsigned long addr,
pud_t *pud, struct folio_or_pfn fop, pgprot_t prot, bool write)
{
struct mm_struct *mm = vma->vm_mm;
+ vm_fault_t ret = VM_FAULT_NOPAGE;
spinlock_t *ptl;
pud_t entry;
@@ -1784,6 +1790,10 @@ static vm_fault_t insert_pud(struct vm_area_struct *vma, unsigned long addr,
if (fop.is_folio) {
entry = folio_mk_pud(fop.folio, vma->vm_page_prot);
+ if (unlikely(folio_ref_count_over_limit(fop.folio))) {
+ ret = VM_FAULT_SIGBUS;
+ goto out_unlock;
+ }
folio_get(fop.folio);
folio_add_file_rmap_pud(fop.folio, &fop.folio->page, vma);
add_mm_counter(mm, mm_counter_file(fop.folio), HPAGE_PUD_NR);
@@ -1799,7 +1809,7 @@ static vm_fault_t insert_pud(struct vm_area_struct *vma, unsigned long addr,
update_mmu_cache_pud(vma, addr, pud);
out_unlock:
spin_unlock(ptl);
- return VM_FAULT_NOPAGE;
+ return ret;
}
/**
@@ -5215,6 +5225,16 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct folio *folio
if (!(pvmw->pmd && !pvmw->pte))
return;
+ if (!folio_test_anon(folio) && unlikely(folio_ref_count_over_limit(folio))) {
+ /* Leave the file mapping absent so it can be faulted in. */
+ pmd_clear(pvmw->pmd);
+ if (arch_needs_pgtable_deposit())
+ zap_deposited_table(mm, pvmw->pmd);
+ update_hiwater_rss(mm);
+ add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);
+ return;
+ }
+
entry = softleaf_from_pmd(*pvmw->pmd);
folio_get(folio);
pmde = folio_mk_pmd(folio, READ_ONCE(vma->vm_page_prot));
diff --git a/mm/memory.c b/mm/memory.c
index becf53a2a0944..4800a0da14a32 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -1168,6 +1168,8 @@ static __always_inline void __copy_present_ptes(struct vm_area_struct *dst_vma,
* consecutive pages of the same folio by copying them as well.
*
* Returns -EAGAIN if one preallocated page is required to copy the next PTE.
+ * Returns -ENOMEM if the file folio refcount is too high.
+ * Returns -EHWPOISON if copying a page fails.
* Otherwise, returns the number of copied PTEs (at least 1).
*/
static inline int
@@ -1186,6 +1188,9 @@ copy_present_ptes(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma
folio = page_folio(page);
+ if (!folio_test_anon(folio) && unlikely(folio_ref_count_over_limit(folio)))
+ return -ENOMEM;
+
/*
* If we likely have to copy, just don't bother with batching. Make
* sure that the common "small folio" case is as fast as possible
@@ -1367,9 +1372,9 @@ copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,
/*
* If we need a pre-allocated page for this pte, drop the
* locks, allocate, and try again.
- * If copy failed due to hwpoison in source page, break out.
+ * Other errors abort the copy after releasing the page table locks.
*/
- if (unlikely(ret == -EAGAIN || ret == -EHWPOISON))
+ if (unlikely(ret < 0))
break;
if (unlikely(prealloc)) {
/*
@@ -1399,7 +1404,7 @@ copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,
goto out;
}
entry.val = 0;
- } else if (ret == -EBUSY || unlikely(ret == -EHWPOISON)) {
+ } else if (ret == -EBUSY || ret == -ENOMEM || unlikely(ret == -EHWPOISON)) {
goto out;
} else if (ret == -EAGAIN) {
prealloc = folio_prealloc(src_mm, src_vma, addr, false);
@@ -2469,6 +2474,8 @@ static int insert_page_into_pte_locked(struct vm_area_struct *vma, pte_t *pte,
if (unlikely(is_zero_folio(folio))) {
pteval = pte_mkspecial(pteval);
} else {
+ if (unlikely(folio_ref_count_over_limit(folio)))
+ return -EOVERFLOW;
folio_get(folio);
pteval = mk_pte(page, prot);
if (mkwrite) {
@@ -5717,6 +5724,9 @@ vm_fault_t do_set_pmd(struct vm_fault *vmf, struct folio *folio, struct page *pa
if (unlikely(folio_test_has_hwpoisoned(folio)))
return ret;
+ if (unlikely(folio_ref_count_over_limit(folio)))
+ return VM_FAULT_SIGBUS;
+
/*
* Archs like ppc64 need additional space to store information
* related to pte entry. Use the preallocated table for that.
@@ -5771,6 +5781,9 @@ vm_fault_t do_set_pmd(struct vm_fault *vmf, struct folio *folio, struct page *pa
* @page: The first page to create a PTE for.
* @nr: The number of PTEs to create.
* @addr: The first address to create a PTE for.
+ *
+ * For file mappings, the caller must check folio_ref_count_over_limit()
+ * before adding the mapping references and installing PTEs.
*/
void set_pte_range(struct vm_fault *vmf, struct folio *folio,
struct page *page, unsigned int nr, unsigned long addr)
@@ -5937,6 +5950,10 @@ vm_fault_t finish_fault(struct vm_fault *vmf)
goto fallback;
}
+ if (!is_cow && unlikely(folio_ref_count_over_limit(folio))) {
+ ret = VM_FAULT_SIGBUS;
+ goto unlock;
+ }
folio_ref_add(folio, nr_pages - 1);
set_pte_range(vmf, folio, page, nr_pages, addr);
type = is_cow ? MM_ANONPAGES : mm_counter_file(folio);
diff --git a/mm/migrate.c b/mm/migrate.c
index 6a96e3136fb2a..ca7471aa7a76a 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -385,6 +385,17 @@ static bool remove_migration_pte(struct folio *folio,
try_to_map_unused_to_zeropage(&pvmw, folio, old_pte, idx))
continue;
+ if (!folio_test_anon(folio) && !folio_test_hugetlb(folio) &&
+ unlikely(folio_ref_count_over_limit(folio))) {
+ /* File contents remain available for a later fault. */
+ pte_clear(vma->vm_mm, pvmw.address, pvmw.pte);
+ cond_install_uffd_wp_ptes(vma, pvmw.address,
+ pvmw.pte, old_pte, 1);
+ update_hiwater_rss(vma->vm_mm);
+ dec_mm_counter(vma->vm_mm, mm_counter_file(folio));
+ continue;
+ }
+
folio_get(folio);
new = folio_page(folio, idx);
pte = mk_pte(new, READ_ONCE(vma->vm_page_prot));
diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c
index 169f681f9849b..89853c157fa91 100644
--- a/mm/userfaultfd.c
+++ b/mm/userfaultfd.c
@@ -373,6 +373,10 @@ static int mfill_atomic_install_pte(pmd_t *dst_pmd,
goto out_unlock;
if (page_in_cache) {
+ if (unlikely(folio_ref_count_over_limit(folio))) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
folio_add_file_rmap_pte(folio, page, dst_vma);
} else {
folio_add_new_anon_rmap(folio, dst_vma, dst_addr, RMAP_EXCLUSIVE);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/5] mm: limit pagecache folio refcount when splicing into pipes
2026-11-05 13:10 [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 1/5] mm: limit pagecache folio refcount when mapping pages David Hildenbrand (Arm)
@ 2026-11-05 13:10 ` David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 3/5] mm/mmu_gather: flush pending references early before the folio limit David Hildenbrand (Arm)
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 13:10 UTC (permalink / raw)
To: Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Matthew Wilcox (Oracle), Jan Kara, Zi Yan, Baolin Wang,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Peter Xu, Johannes Weiner, Song Liu, Rik van Riel, Hugh Dickins,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD), Robin Murphy,
Steve Sistare, John Hubbard
Cc: linux-mm, linux-kernel, linux-fsdevel, linux-arch, iommu,
David Hildenbrand (Arm), stable
Similar to mapping pagecache folios into page tables, let's reject splicing
folios into a pipe if the refcount is at it's limit, similar to how we
handle it during GUP.
This will prevent more cases of possible folio refcount overflows with
large pagecache folios.
Let's keep it simple and check in the two callers of
splice_folio_into_pipe().
Also check in try_get_page() to cover pipe-buffer duplication by tee()
and partial pipe-to-pipe splicing.
Fixes: 800d8c63b2e9 ("shmem: add huge pages support")
Fixes: 99cb0dbd47a1 ("mm,thp: add read-only THP support for (non-shmem) FS")
Fixes: 793917d997df ("mm/readahead: Add large folio readahead")
Cc: stable@vger.kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/mm.h | 9 ++++++---
mm/filemap.c | 4 ++++
mm/shmem.c | 4 ++++
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 79ae8fc0d6bb4..a82cfbce18215 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2287,10 +2287,13 @@ static inline void get_page(struct page *page)
static inline __must_check bool try_get_page(struct page *page)
{
- page = compound_head(page);
- if (WARN_ON_ONCE(page_ref_count(page) <= 0))
+ struct folio *folio = page_folio(page);
+
+ if (WARN_ON_ONCE(folio_ref_count(folio) <= 0))
return false;
- page_ref_inc(page);
+ if (unlikely(folio_ref_count_over_limit(folio)))
+ return false;
+ folio_ref_inc(folio);
return true;
}
diff --git a/mm/filemap.c b/mm/filemap.c
index 920bbf86dfdff..0f2ec790a5b65 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3168,6 +3168,10 @@ ssize_t filemap_splice_read(struct file *in, loff_t *ppos,
if (writably_mapped)
flush_dcache_folio(folio);
+ if (unlikely(folio_ref_count_over_limit(folio))) {
+ error = -ENOMEM;
+ goto out;
+ }
n = min_t(loff_t, len, isize - *ppos);
n = splice_folio_into_pipe(pipe, folio, *ppos, n);
if (!n)
diff --git a/mm/shmem.c b/mm/shmem.c
index cda1a0f7db99d..1c0f6bceac486 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -3819,6 +3819,10 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
* Ok, we have the page, and it's up-to-date, so we can
* now splice it into the pipe.
*/
+ if (unlikely(folio_ref_count_over_limit(folio))) {
+ error = -ENOMEM;
+ break;
+ }
n = splice_folio_into_pipe(pipe, folio, *ppos, part);
folio_put(folio);
folio = NULL;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/5] mm/mmu_gather: flush pending references early before the folio limit
2026-11-05 13:10 [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 1/5] mm: limit pagecache folio refcount when mapping pages David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 2/5] mm: limit pagecache folio refcount when splicing into pipes David Hildenbrand (Arm)
@ 2026-11-05 13:10 ` David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 4/5] iommufd: fix cleanup when adding memfd folio pins fails David Hildenbrand (Arm)
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 13:10 UTC (permalink / raw)
To: Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Matthew Wilcox (Oracle), Jan Kara, Zi Yan, Baolin Wang,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Peter Xu, Johannes Weiner, Song Liu, Rik van Riel, Hugh Dickins,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD), Robin Murphy,
Steve Sistare, John Hubbard
Cc: linux-mm, linux-kernel, linux-fsdevel, linux-arch, iommu,
David Hildenbrand (Arm), Chris Mason, stable
When a task zaps mappings (e.g., MADV_DONTNEED), it can drop the PT lock
and defer dropping the folio references. In the meantime, page faults
can refault pagecache folios, creating more references for the same
folios.
Let's just request a flush after enqueueing a pagecache folio whose
refcount reached a certain threshold. Start flushing well before new
references are refused, regardless of the caller's privileges.
This is a temporary measure until we have something better.
Reported-by: Chris Mason <mason@kernel.org>
Fixes: 10ebac4f95e7 ("mm/memory: optimize unmap/zap with PTE-mapped THP")
Cc: stable@vger.kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/mm.h | 1 +
mm/memory.c | 4 ++--
mm/mmu_gather.c | 5 +++++
3 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index a82cfbce18215..87f7646a7f3ba 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2232,6 +2232,7 @@ vm_fault_t finish_fault(struct vm_fault *vmf);
/* Leave separate reserves for administrators and concurrent reference gains. */
#define FOLIO_REF_COUNT_LIMIT (INT_MAX - (1U << 26))
#define FOLIO_REF_COUNT_ADMIN_LIMIT (INT_MAX - (1U << 25))
+#define FOLIO_REF_COUNT_FLUSH_LIMIT (INT_MAX - (1U << 27))
/**
* folio_ref_count_over_limit - Check whether to refuse more folio references.
diff --git a/mm/memory.c b/mm/memory.c
index 4800a0da14a32..d2eb313832559 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -2061,8 +2061,8 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb,
pte_unmap_unlock(start_pte, ptl);
/*
- * If we forced a TLB flush (either due to running out of
- * batch buffers or because we needed to flush dirty TLB
+ * If we forced a TLB flush (either to release batched
+ * references or because we needed to flush dirty TLB
* entries before releasing the ptl), free the batched
* memory too. Come back again if we didn't do everything.
*/
diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c
index 9f353f0e2ef4d..5988c42cc5144 100644
--- a/mm/mmu_gather.c
+++ b/mm/mmu_gather.c
@@ -190,6 +190,11 @@ static bool __tlb_remove_folio_pages_size(struct mmu_gather *tlb,
batch->encoded_pages[batch->nr++] = encode_page(page, flags);
batch->encoded_pages[batch->nr++] = encode_nr_pages(nr_pages);
}
+ /* Flush pending references well before new references are refused. */
+ if (unlikely((unsigned int)folio_ref_count(page_folio(page)) >=
+ FOLIO_REF_COUNT_FLUSH_LIMIT))
+ return true;
+
/*
* Make sure that we can always add another "page" + "nr_pages",
* requiring two entries instead of only a single one.
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 4/5] iommufd: fix cleanup when adding memfd folio pins fails
2026-11-05 13:10 [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (2 preceding siblings ...)
2026-11-05 13:10 ` [PATCH 3/5] mm/mmu_gather: flush pending references early before the folio limit David Hildenbrand (Arm)
@ 2026-11-05 13:10 ` David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 5/5] mm/gup: limit pagecache folio refcount when grabbing references David Hildenbrand (Arm)
2026-11-05 17:51 ` [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
5 siblings, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 13:10 UTC (permalink / raw)
To: Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Matthew Wilcox (Oracle), Jan Kara, Zi Yan, Baolin Wang,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Peter Xu, Johannes Weiner, Song Liu, Rik van Riel, Hugh Dickins,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD), Robin Murphy,
Steve Sistare, John Hubbard
Cc: linux-mm, linux-kernel, linux-fsdevel, linux-arch, iommu,
David Hildenbrand (Arm), stable
folio_add_pins() can fail after memfd_pin_folios() succeeded pinning a
folio. Upcoming folio refcount limit checks will make this easier to
trigger.
Let's fix three problems in the error handling:
1. pfn_reader_release_pins() uses the allocated array size instead of the
number of folios returned by memfd_pin_folios(). With large folios,
cleanup can unpin uninitialized entries. Remember the number of returned
folios and use that during cleanup.
2. pin_memfd_pages() returns on failure without releasing the pins acquired
by the current call. Release the per-page pins for folios already
expanded into the page array, and the single pins for the remaining
folios.
3. pfn_reader_next() returns on failure before updating batch_end_index,
even if the batch already contains successfully expanded folios. Update
it before propagating the error so cleanup releases those pins as well.
Fixes: 92687c793644 ("iommufd: pfn_reader for file mappings")
Cc: stable@vger.kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
drivers/iommu/iommufd/pages.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index 2f6153108add7..fcdbde007a9ab 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -781,6 +781,7 @@ struct pfn_reader_user {
struct file *file;
struct folio **ufolios;
size_t ufolios_len;
+ unsigned long ufolios_count;
unsigned long ufolios_offset;
struct folio **ufolios_next;
};
@@ -800,6 +801,7 @@ static void pfn_reader_user_init(struct pfn_reader_user *user,
user->file = (pages->type == IOPT_ADDRESS_FILE) ? pages->file : NULL;
user->ufolios = NULL;
user->ufolios_len = 0;
+ user->ufolios_count = 0;
user->ufolios_next = NULL;
user->ufolios_offset = 0;
}
@@ -843,6 +845,7 @@ static long pin_memfd_pages(struct pfn_reader_user *user, unsigned long start,
offset >>= PAGE_SHIFT;
user->ufolios_next = user->ufolios;
+ user->ufolios_count = nfolios;
user->ufolios_offset = offset;
for (i = 0; i < nfolios; i++) {
@@ -859,8 +862,12 @@ static long pin_memfd_pages(struct pfn_reader_user *user, unsigned long start,
} else {
int rc = folio_add_pins(folio, npin - 1);
- if (rc)
+ if (rc) {
+ unpin_user_pages(user->upages,
+ upages - user->upages);
+ unpin_folios(user->ufolios + i, nfolios - i);
return rc;
+ }
while (npin--)
*upages++ = folio_page(folio, offset++);
@@ -1251,14 +1258,14 @@ static int pfn_reader_next(struct pfn_reader *pfns)
return -EINVAL;
rc = pfn_reader_fill_span(pfns);
+ pfns->batch_end_index = pfns->batch_start_index +
+ pfns->batch.total_pfns;
if (rc)
return rc;
if (WARN_ON(!pfns->batch.total_pfns))
return -EINVAL;
- pfns->batch_end_index =
- pfns->batch_start_index + pfns->batch.total_pfns;
if (pfns->batch_end_index == pfns->span.last_used + 1)
interval_tree_double_span_iter_next(&pfns->span);
@@ -1320,7 +1327,7 @@ static void pfn_reader_release_pins(struct pfn_reader *pfns)
if (!user->file) {
unpin_user_pages(user->upages + start_index, npages);
} else {
- long n = user->ufolios_len / sizeof(*user->ufolios);
+ const unsigned long n = user->ufolios_count;
unpin_folios(user->ufolios_next,
user->ufolios + n - user->ufolios_next);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 5/5] mm/gup: limit pagecache folio refcount when grabbing references
2026-11-05 13:10 [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (3 preceding siblings ...)
2026-11-05 13:10 ` [PATCH 4/5] iommufd: fix cleanup when adding memfd folio pins fails David Hildenbrand (Arm)
@ 2026-11-05 13:10 ` David Hildenbrand (Arm)
2026-11-05 17:51 ` [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
5 siblings, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 13:10 UTC (permalink / raw)
To: Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Matthew Wilcox (Oracle), Jan Kara, Zi Yan, Baolin Wang,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Peter Xu, Johannes Weiner, Song Liu, Rik van Riel, Hugh Dickins,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD), Robin Murphy,
Steve Sistare, John Hubbard
Cc: linux-mm, linux-kernel, linux-fsdevel, linux-arch, iommu,
David Hildenbrand (Arm), stable
Let's replace our existing safety check by the new
folio_ref_count_over_limit() check in fast and slow GUP before acquiring
additional references or pins.
Keep it simple and use the check for all folio types.
Drop the WARN_ON_ONCE(), because apparently these things can currently
happen with large pagecache folios.
Keep the zero-refcount rejection if the refcount is 0.
Fixes: 800d8c63b2e9 ("shmem: add huge pages support")
Fixes: 99cb0dbd47a1 ("mm,thp: add read-only THP support for (non-shmem) FS")
Fixes: 793917d997df ("mm/readahead: Add large folio readahead")
Cc: stable@vger.kernel.org
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
mm/gup.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/mm/gup.c b/mm/gup.c
index 2ab42fc6834e5..db9db6b2a4662 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -77,7 +77,7 @@ static inline struct folio *try_get_folio(struct page *page, int refs)
retry:
folio = page_folio(page);
- if (WARN_ON_ONCE(folio_ref_count(folio) < 0))
+ if (unlikely(folio_ref_count_over_limit(folio)))
return NULL;
if (unlikely(!folio_ref_try_add(folio, refs)))
return NULL;
@@ -140,7 +140,11 @@ static void gup_put_folio(struct folio *folio, int refs, unsigned int flags)
int __must_check try_grab_folio(struct folio *folio, int refs,
unsigned int flags)
{
- if (WARN_ON_ONCE(folio_ref_count(folio) <= 0))
+ if ((flags & (FOLL_GET | FOLL_PIN)) &&
+ unlikely(folio_ref_count_over_limit(folio)))
+ return -ENOMEM;
+
+ if (WARN_ON_ONCE(!folio_ref_count(folio)))
return -ENOMEM;
if (unlikely(!(flags & FOLL_PCI_P2PDMA) && folio_is_pci_p2pdma(folio)))
@@ -1482,8 +1486,8 @@ static long __get_user_pages(struct mm_struct *mm,
struct folio *folio = page_folio(page);
/*
- * Since we already hold refcount on the
- * large folio, this should never fail.
+ * Grabbing more references can fail once we
+ * hit folio refcount limits.
*/
if (try_grab_folio(folio, page_increm - 1,
gup_flags)) {
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 0/5] mm: limit pagecache folio refcount
2026-11-05 13:10 [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (4 preceding siblings ...)
2026-11-05 13:10 ` [PATCH 5/5] mm/gup: limit pagecache folio refcount when grabbing references David Hildenbrand (Arm)
@ 2026-11-05 17:51 ` David Hildenbrand (Arm)
5 siblings, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 17:51 UTC (permalink / raw)
To: Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Matthew Wilcox (Oracle), Jan Kara, Zi Yan, Baolin Wang,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Matthew Brost, Joshua Hahn,
Byungchul Park, Gregory Price, Ying Huang, Alistair Popple,
Peter Xu, Johannes Weiner, Song Liu, Rik van Riel, Hugh Dickins,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD), Robin Murphy,
Steve Sistare, John Hubbard
Cc: linux-mm, linux-kernel, linux-fsdevel, linux-arch, iommu,
Chris Mason, stable
On 11/5/26 14:10, David Hildenbrand (Arm) wrote:
> Users can create a lot of folio references to pagecache folios, making
> it possible to overflow the refcount of large folios.
>
> While we knew that one could in theory overflow a folio's refcount, we
> mostly assumed that bad things would happen and it wouldn't go
> unnoticed.
>
> Not so sure if that is really always the case, and leaving that problem
> unsolved is irresponsible, so let's limit pagecache folio refcounts.
>
> This is really only relevant for large folios / thps in the page cache.
> With anon folios (including THPs), we should not be
> able to trigger folio refcount overflows (until we get PUD-sized ones ;) ).
Just a note that I don't think anon THP are as really relevant today, at least
for 4k kernels, as one would usually need that so many processes (consuming so
much memory for everything else) that this is mostly irrelevant in practice as
an attack vector.
At least with 512 pages per THP.
The exception might be arm64 with 16k or 64k page size with 2k. (putting aside
that 512M THPs are barely usable in practice).
#processes to overflow the folio refcount
4k kernel: 512 pages per THP: 4m processes (irrelevant in practice)
16k kernel: 2048 pages per THP: 1m processes (mostly irrelevant in practice)
64k kernel: 8192 pages per THP: 256k processes
The 64k case might be the only relevant one, but I think even there all
processes combined would already consume way more than 100 GiB of kernel memory.
If we really care about the latter case we could throw in some cheap checks
during fork() checks.
Handling migration entry restore / swapin would be a bit more nasty, but not
sure if that's really something to worry about in the big scheme of things.
--
Cheers,
David
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-05 17:52 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-05 13:10 [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 1/5] mm: limit pagecache folio refcount when mapping pages David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 2/5] mm: limit pagecache folio refcount when splicing into pipes David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 3/5] mm/mmu_gather: flush pending references early before the folio limit David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 4/5] iommufd: fix cleanup when adding memfd folio pins fails David Hildenbrand (Arm)
2026-11-05 13:10 ` [PATCH 5/5] mm/gup: limit pagecache folio refcount when grabbing references David Hildenbrand (Arm)
2026-11-05 17:51 ` [PATCH 0/5] mm: limit pagecache folio refcount David Hildenbrand (Arm)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox