* [PATCH v3 0/9] mm: limit pagecache folio refcount
@ 2026-11-10 20:35 David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 1/9] mm: limit pagecache folio refcount when mapping pages David Hildenbrand (Arm)
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, Chris Mason, stable, Jason Gunthorpe
Note: I really would prefer doing something else right now, but this is
rather urgent.
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 mostly relevant for large folios / thps in the page cache, where
a single VMA can hold multiple folio references, allowing for many folio
references. But with sufficient (memory+process) resources, it is also
possible with small pagecache folios, just not that relevant in
practice.
With anon folios (including THPs), we should not be able to trigger folio
refcount overflows, as one would need so many processes that it
becomes impossible in practice. 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 recover a problematic system). We still leave some
headroom to allow for some slight limit overshooting, to make the
implementation easier.
But to make that work reliably, we have to make anybody that can take
an unbounded number of folio references obey these limits.
Patch #1 handles mapping references from page tables. 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), which we do in patch #3.
Patch #4 fixes iommufd cleanup when adding folio pins fails, which these
limits make easier to trigger.
Splicing pagecache folios to a pipe (patch #2) also needs care. After
feedback from Linus, these users really should be using try_get_page(),
where we now also implement the new limit check. We also teach GUP in
patch #5 to stay below the limit, to avoid negative refcounts that cause
trouble.
Doing some digging after getting a hint from Andrew, there are various
paths in net that can also take an unbounded number of page refs and are
problematic. I'm attaching RFC patches as I am not really a net expert.
These patches similarly now use try_get_page(). These patches can go
in separately.
Net paths that take page refs that I don't touch because the references
are either bounded or the paths are not relevant in practice:
- skb_split(): can add one reference per split, but each split
allocates a new skb that is charged to the socket, and the references
go away once the data was acked. Reworking that to be able to fail
requires more plumbing.
- IP-TFS reassembly: needs one new inner skb per additional reference.
- io_uring zerocopy send (managed frag references): each zerocopy piece
is charged at least 4 KiB of TCP memory, so not relevant in practice.
- kTLS software splicing: only transfers references from the pipe, so
not a problem.
- AF_XDP zerocopy TX, nfsd splice reads, sockmap / sk_msg, espintcp:
bounded, or require privileges / special setups, so not a problem in
practice.
- Driver-specific paths (e.g., kTLS device offload): require special
hardware, didn't look into these.
- page_pool and net_iov frags: not pagecache pages, so not a problem.
AI tells me that with the current changes, we should cover all relevant
unbounded page references. Needs more investigation.
For page table mappings, this is only a temporary solution for
upstream+stable, until we have something better, which will be harder to
backport. I have patches to:
- 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
After that, most of patch #1 will be reverted, everything else will
stay.
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.
The net patches were tested similarly briefly tested, but more
excessive testing is required.
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
Changes in v3:
- "mm: limit pagecache folio refcount when mapping pages"
- Move IRQ disabled check over from GUP patch.
- Add in_task() check to prepare for networking patches.
- Only check for invalid refcounts in folio_get() and drop
folio_ref_zero_or_close_to_overflow().
- Improve documentation and patch description.
- "mm: limit pagecache folio refcount when splicing into pipes"
- Introduce folio_get_checked() and use it in splice_folio_into_pipe(),
fuse_ref_folio() and try_get_page().
- Return -EFAULT and don't fall back to copying anymore.
- Stop filemap_splice_read() after a partially spliced folio.
- Update patch description.
- "mm/gup: limit pagecache folio refcount when grabbing references"
- Moved the IRQs-disabled check to patch #1.
- Update documentation.
- New RFC patches handling networking paths that copy or add frag
references:
- "net: limit folio refcount when copying skb frag references"
- "net: limit folio refcount when rearranging skb frags"
- "net: limit folio refcount when splicing pages into skbs"
- "net: limit folio refcount when splicing from sockets"
- Link to v2: https://lore.kernel.org/20261107-refcount_overflow_stable-v2-0-c38f6809b106@kernel.org
Changes in v2:
- "mm: limit pagecache folio refcount when mapping pages"
- Try not to fail fork() by skipping the PTE/PUD instead. We still have
to fail in two scenarios.
- Split helper into fast and slow path. Slow path in mm/util.c,
exported for Fuse usage in patch #2.
- Check has_capability_noaudit(current, CAP_SYS_RESOURCE) instead of
capable(CAP_SYS_RESOURCE)
- Extended patch description and improved function documentation.
- Dropped RBs due to changes.
- "mm: limit pagecache folio refcount when splicing into pipes"
- Return -EOVERFLOW; from filemap_splice_read() and
shmem_file_splice_read(), and handle it in do_splice_read() by falling
back to a copy.
- Check in fuse_copy_folio() as well
- Updated patch description and updated+improved comments/doc
- Dropped RB due to changes
- "mm/mmu_gather: flush pending references early before the folio limit"
- Adjust some comments
- Update Fixes:
- Extend patch description
- "mm/gup: limit pagecache folio refcount when grabbing references"
- Keep all WARN_ON_ONCE() checks for negative/null refcounts
- Skip has_capability_noaudit(current, CAP_SYS_RESOURCE) with disabled
interrupts, skipping it for GUP-fast.
- Properly forward try_grab_folio() errors
- Adjust some comments
- Link to v1: https://patch.msgid.link/20261105-refcount_overflow_stable-v1-0-70b9c655da3e@kernel.org
To: David Hildenbrand <david@kernel.org>
To: Lorenzo Stoakes <ljs@kernel.org>
To: "Liam R. Howlett" <liam@infradead.org>
To: Vlastimil Babka <vbabka@kernel.org>
To: Mike Rapoport <rppt@kernel.org>
To: Suren Baghdasaryan <surenb@google.com>
To: Michal Hocko <mhocko@suse.com>
To: "Matthew Wilcox (Oracle)" <willy@infradead.org>
To: Jan Kara <jack@suse.cz>
To: Andrew Morton <akpm@linux-foundation.org>
To: Zi Yan <ziy@nvidia.com>
To: Baolin Wang <baolin.wang@linux.alibaba.com>
To: Nico Pache <nico.pache@linux.dev>
To: Ryan Roberts <ryan.roberts@arm.com>
To: Dev Jain <dev.jain@arm.com>
To: Barry Song <baohua@kernel.org>
To: Lance Yang <lance.yang@linux.dev>
To: Usama Arif <usama.arif@linux.dev>
To: Kiryl Shutsemau <kas@kernel.org>
To: Matthew Brost <matthew.brost@intel.com>
To: Joshua Hahn <joshua.hahnjy@gmail.com>
To: Byungchul Park <byungchul@sk.com>
To: Gregory Price <gourry@gourry.net>
To: Ying Huang <ying.huang@linux.alibaba.com>
To: Alistair Popple <apopple@nvidia.com>
To: Peter Xu <peterx@redhat.com>
To: Johannes Weiner <hannes@cmpxchg.org>
To: Song Liu <songliubraving@fb.com>
To: Rik van Riel <riel@surriel.com>
To: Miklos Szeredi <miklos@szeredi.hu>
To: Alexander Viro <viro@zeniv.linux.org.uk>
To: Christian Brauner <brauner@kernel.org>
To: Hugh Dickins <hughd@google.com>
To: Will Deacon <will@kernel.org>
To: "Aneesh Kumar K.V" <aneesh.kumar@kernel.org>
To: Nick Piggin <npiggin@gmail.com>
To: Peter Zijlstra <peterz@infradead.org>
To: Arnd Bergmann <arnd@arndb.de>
To: Jason Gunthorpe <jgg@ziepe.ca>
To: Kevin Tian <kevin.tian@intel.com>
To: "Joerg Roedel (AMD)" <joro@8bytes.org>
To: Robin Murphy <robin.murphy@arm.com>
To: Steve Sistare <steven.sistare@oracle.com>
To: John Hubbard <jhubbard@nvidia.com>
To: Jose A. Perez de Azpillaga <azpijr@gmail.com>
To: Josef Bacik <josef@toxicpanda.com>
To: "David S. Miller" <davem@davemloft.net>
To: Eric Dumazet <edumazet@kernel.org>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
To: Simon Horman <horms@kernel.org>
To: Neal Cardwell <ncardwell@google.com>
To: Kuniyuki Iwashima <kuniyu@google.com>
To: Linus Torvalds <torvalds@linux-foundation.org>
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-fsdevel@vger.kernel.org
Cc: fuse-devel@lists.linux.dev
Cc: linux-arch@vger.kernel.org
Cc: iommu@lists.linux.dev
Cc: netdev@vger.kernel.org
---
David Hildenbrand (Arm) (9):
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
RFC: net: limit folio refcount when copying skb frag references
RFC: net: limit folio refcount when rearranging skb frags
RFC: net: limit folio refcount when splicing pages into skbs
RFC: net: limit folio refcount when splicing from sockets
drivers/iommu/iommufd/pages.c | 15 +++-
fs/fuse/dev.c | 4 +-
include/asm-generic/tlb.h | 4 +-
include/linux/mm.h | 73 ++++++++++++++----
include/linux/skbuff_ref.h | 53 +++++++++++++
mm/filemap.c | 32 +++++---
mm/gup.c | 24 +++---
mm/huge_memory.c | 29 +++++++-
mm/internal.h | 4 +-
mm/memory.c | 37 +++++++--
mm/migrate.c | 11 +++
mm/mmu_gather.c | 10 ++-
mm/shmem.c | 7 +-
mm/userfaultfd.c | 4 +
mm/util.c | 18 +++++
net/core/skbuff.c | 169 ++++++++++++++++++++++++++----------------
net/ipv4/tcp_output.c | 8 +-
17 files changed, 378 insertions(+), 124 deletions(-)
---
base-commit: 2683df3b7eac1d91380194955ad0c9751454610c
change-id: 20261105-refcount_overflow_stable-542e024130fe
Best regards,
--
David Hildenbrand (Arm) <david@kernel.org>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 1/9] mm: limit pagecache folio refcount when mapping pages
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 2/9] mm: limit pagecache folio refcount when splicing into pipes David Hildenbrand (Arm)
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, Chris Mason, stable
With large file folios, overflowing the folio mapcount and refcount
became possible more easily. For anon folios it is currently not really
an issue: each large anon folio can only be mapped once into a process; on
4k kernels it would be more processes than usually allowed on a system,
and on 16k or 64k kernels the processes alone would consume hundreds of
GiB. Something to be sorted out at some point, but it's not as urgent.
Being able to overflow the refcount is 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.
During fork(), prefer not copying page table entries over failing fork.
Export __folio_ref_count_over_limit() right away as that will be needed
from fuse code next. Use has_capability_noaudit(current, CAP_SYS_RESOURCE),
so filesystem credential overrides do not affect access to the admin
reserve, and to not generate auditing messages simply because we access
optional reserves.
Don't grant access to the admin reserve from kernel threads, from
interrupt context, where "current" is just the interrupted task, or with
IRQs disabled, as even unaudited capability checks can take locks.
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. Keep the mapping checks explicit and
separate from reference acquisition: most are expected to disappear when
we rework the underlying refcounting.
Check order 0 folios as well for consistency: each
folio_add_file_rmap*() and folio_dup_file_rmap*() needs a prior check.
Adjust the check folio_get() to just check for invalid refcounts and
drop folio_ref_zero_or_close_to_overflow(): we really don't want
negative refcounts or negative mapcounts. Convert to VM_WARN_ON_FOLIO as
we touch it.
Also, just document -EHWPOISON for copy_present_ptes() while at it.
Note: When zapping file migration entries, uffd-wp is preserved using PTE
markers, but uffd-rwp protection is lost because there is no corresponding
marker. try_to_unmap() already has the same problem when unmapping file
folios, so we'll happily ignore it.
Note2: For now we don't let migration or PTE table collapse exceed any
limits. Admins can always refault, and if we are already operating in
admin reserves, something is already off.
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
Reviewed-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
Acked-by: Zi Yan <ziy@nvidia.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/mm.h | 35 +++++++++++++++++++++++++++++++----
mm/filemap.c | 6 ++++++
mm/huge_memory.c | 29 +++++++++++++++++++++++++++--
mm/memory.c | 33 +++++++++++++++++++++++++++++----
mm/migrate.c | 11 +++++++++++
mm/userfaultfd.c | 4 ++++
mm/util.c | 18 ++++++++++++++++++
7 files changed, 126 insertions(+), 10 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index e6da76fbe8e83..6c7c29a086bd1 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2229,9 +2229,36 @@ vm_fault_t finish_fault(struct vm_fault *vmf);
* back into memory.
*/
-/* 127: arbitrary random number, small enough to assemble well */
-#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))
+
+bool __folio_ref_count_over_limit(unsigned int ref_count);
+
+/**
+ * folio_ref_count_over_limit - Check whether to refuse more folio references.
+ * @folio: The folio.
+ *
+ * Check before adding page table mappings or other significant refcount users
+ * to pagecache folios (!hugetlb), either before acquiring references or
+ * afterwards with rollback if the operation is refused.
+ *
+ * Checking other folios does no harm.
+ *
+ * This check is an approximation; we leave some headroom such that concurrent
+ * increments, batches, temporary references, or other bounded refcount
+ * consumers are not a problem.
+ *
+ * Return: True if the caller should refuse the operation, 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;
+ return __folio_ref_count_over_limit(ref_count);
+}
/**
* folio_get - Increment the reference count on a folio.
@@ -2243,7 +2270,7 @@ vm_fault_t finish_fault(struct vm_fault *vmf);
*/
static inline void folio_get(struct folio *folio)
{
- VM_BUG_ON_FOLIO(folio_ref_zero_or_close_to_overflow(folio), folio);
+ VM_WARN_ON_FOLIO(folio_ref_count(folio) <= 0, folio);
folio_ref_inc(folio);
}
diff --git a/mm/filemap.c b/mm/filemap.c
index 9871b4f32a916..0f6d57432a7e5 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 4608540e40d46..439cfe446cea4 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;
}
/**
@@ -1758,6 +1763,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;
@@ -1783,6 +1789,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);
@@ -1798,7 +1808,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;
}
/**
@@ -2093,6 +2103,11 @@ int copy_huge_pud(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma
if (page) {
struct folio *folio = page_folio(page);
+ if (unlikely(folio_ref_count_over_limit(folio))) {
+ /* Skip this PUD; it can be faulted in later. */
+ ret = (dst_vma->vm_flags & VM_MIXEDMAP) ? -ENOMEM : 0;
+ goto out_unlock;
+ }
folio_get(folio);
/*
* TODO: once we support anonymous pages, use
@@ -5223,6 +5238,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 c9d64170e47ab..977e037388963 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -1168,7 +1168,10 @@ 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.
- * Otherwise, returns the number of copied PTEs (at least 1).
+ * Returns -ENOMEM if the file folio refcount is too high and the PTE must
+ * be copied.
+ * Returns -EHWPOISON if copying a page fails.
+ * Otherwise, returns the number of processed PTEs (at least 1).
*/
static inline int
copy_present_ptes(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,
@@ -1186,6 +1189,16 @@ 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))) {
+ if (dst_vma->vm_flags & VM_MIXEDMAP)
+ return -ENOMEM;
+ if (userfaultfd_pte_rwp(dst_vma, pte))
+ return -ENOMEM;
+ /* Preserve uffd-wp protection when faulting the page back in. */
+ cond_install_uffd_wp_ptes(dst_vma, addr, dst_pte, pte, 1);
+ return 1;
+ }
+
/*
* 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 +1380,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 +1412,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);
@@ -2474,6 +2487,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) {
@@ -5754,6 +5769,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.
@@ -5816,6 +5834,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 installing PTEs.
*/
void set_pte_range(struct vm_fault *vmf, struct folio *folio,
struct page *page, unsigned int nr, unsigned long addr)
@@ -5982,6 +6003,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 a5ac7192f0578..e241a594c3eed 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);
diff --git a/mm/util.c b/mm/util.c
index 80f6c714844f2..a5140b3b70f33 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -791,6 +791,24 @@ int folio_mc_copy(struct folio *dst, struct folio *src)
}
EXPORT_SYMBOL(folio_mc_copy);
+bool __folio_ref_count_over_limit(unsigned int ref_count)
+{
+ if (ref_count >= FOLIO_REF_COUNT_ADMIN_LIMIT)
+ return true;
+ /* Background threads must not consume admin reserves. */
+ if (current->flags & PF_KTHREAD)
+ return true;
+ /* Neither must interrupt contexts on behalf of the interrupted task. */
+ if (!in_task())
+ return true;
+ /* Capability checks are not safe with IRQs disabled. */
+ if (irqs_disabled())
+ return true;
+ /* Use the task's credentials, which filesystem overrides cannot change. */
+ return !has_capability_noaudit(current, CAP_SYS_RESOURCE);
+}
+EXPORT_SYMBOL_GPL(__folio_ref_count_over_limit);
+
int sysctl_overcommit_memory __read_mostly = OVERCOMMIT_GUESS;
static int sysctl_overcommit_ratio __read_mostly = 50;
static unsigned long sysctl_overcommit_kbytes __read_mostly;
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 2/9] mm: limit pagecache folio refcount when splicing into pipes
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 1/9] mm: limit pagecache folio refcount when mapping pages David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 3/9] mm/mmu_gather: flush pending references early before the folio limit David Hildenbrand (Arm)
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, stable
Similar to mapping pagecache folios into page tables, let's reject
additional references when splicing folios into a pipe if the refcount
is at its limit, similar to how we handle it during GUP.
This will prevent more cases of possible folio refcount overflows with
large pagecache folios.
Introduce folio_get_checked() for checked reference acquisition when
the caller already holds a reference. It warns and fails on a non-positive
refcount and rejects additional references at the limit.
Use folio_get_checked() in splice_folio_into_pipe() and
fuse_ref_folio(), returning -EFAULT on failure, like we already do for
pipe-buffer duplication. Similarly, make try_get_page() use it instead.
Likely try_get_page() should be renamed (or removed) later, as it doesn't
match the folio_try_get() semantics and is confusing.
If we hit the limit after splicing some data, return the number of bytes
already spliced. Teach the callers to handle errors, and stop
filemap_splice_read() if we only spliced part of a folio, so we don't
skip ahead to the next folio.
The check in try_get_page() also covers pipe-buffer duplication by tee()
and partial pipe-to-pipe splicing.
Note that we deliberately will not fall back to the slow path of copying
the folio, particularly as the folio might have an invalid refcount. If
it is ever really required, we can revisit.
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>
---
fs/fuse/dev.c | 4 +++-
include/linux/mm.h | 26 ++++++++++++++++++++++----
mm/filemap.c | 26 ++++++++++++++++----------
mm/internal.h | 4 ++--
mm/shmem.c | 7 +++++--
5 files changed, 48 insertions(+), 19 deletions(-)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 3641abb90d6bb..50f41bb287832 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1230,7 +1230,9 @@ static int fuse_ref_folio(struct fuse_copy_state *cs, struct folio *folio,
if (cs->nr_segs >= cs->pipe->max_usage)
return -EIO;
- folio_get(folio);
+ if (!folio_get_checked(folio))
+ return -EFAULT;
+
err = unlock_request(cs->req);
if (err) {
folio_put(folio);
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 6c7c29a086bd1..33bc57342590c 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2284,15 +2284,33 @@ static inline void get_page(struct page *page)
folio_get(folio);
}
-static inline __must_check bool try_get_page(struct page *page)
+/**
+ * folio_get_checked - Try to acquire a reference to a live folio.
+ * @folio: The folio.
+ *
+ * The caller must already hold a reference to @folio. Warn and fail on a
+ * non-positive refcount, and refuse additional references at the limit.
+ *
+ * Use folio_try_get() for speculative reference acquisition where the
+ * refcount can legitimately be zero.
+ *
+ * Return: True if the refcount was incremented, false otherwise.
+ */
+static inline __must_check bool folio_get_checked(struct folio *folio)
{
- page = compound_head(page);
- if (WARN_ON_ONCE(page_ref_count(page) <= 0))
+ 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;
}
+static inline __must_check bool try_get_page(struct page *page)
+{
+ return folio_get_checked(page_folio(page));
+}
+
/**
* folio_put - Decrement the reference count on a folio.
* @folio: The folio.
diff --git a/mm/filemap.c b/mm/filemap.c
index 0f6d57432a7e5..e6e73e37f3ea4 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3047,10 +3047,11 @@ generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
EXPORT_SYMBOL(generic_file_read_iter);
/*
- * Splice subpages from a folio into a pipe.
+ * Splice subpages from a folio into a pipe. Return the number of bytes spliced,
+ * or -EFAULT if we cannot splice from the folio.
*/
-size_t splice_folio_into_pipe(struct pipe_inode_info *pipe,
- struct folio *folio, loff_t fpos, size_t size)
+ssize_t splice_folio_into_pipe(struct pipe_inode_info *pipe,
+ struct folio *folio, loff_t fpos, size_t size)
{
struct page *page;
size_t spliced = 0, offset = offset_in_folio(folio, fpos);
@@ -3063,13 +3064,15 @@ size_t splice_folio_into_pipe(struct pipe_inode_info *pipe,
struct pipe_buffer *buf = pipe_head_buf(pipe);
size_t part = min_t(size_t, PAGE_SIZE - offset, size - spliced);
+ if (!folio_get_checked(folio))
+ return spliced ? spliced : -EFAULT;
+
*buf = (struct pipe_buffer) {
.ops = &page_cache_pipe_buf_ops,
.page = page,
.offset = offset,
.len = part,
};
- folio_get(folio);
pipe->head++;
page++;
spliced += part;
@@ -3094,9 +3097,10 @@ size_t splice_folio_into_pipe(struct pipe_inode_info *pipe,
* Return: On success, the number of bytes read will be returned and *@ppos
* will be updated if appropriate; 0 will be returned if there is no more data
* to be read; -EAGAIN will be returned if the pipe had no space, and some
- * other negative error code will be returned on error. A short read may occur
- * if the pipe has insufficient space, we reach the end of the data or we hit a
- * hole.
+ * other negative error code will be returned on error; -EFAULT will be returned
+ * if acquiring a folio reference fails and no data has been spliced.
+ * A short read may occur if the pipe has insufficient space, we reach the
+ * end of the data, hit a hole or hit a folio refcount limit.
*/
ssize_t filemap_splice_read(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe,
@@ -3154,7 +3158,7 @@ ssize_t filemap_splice_read(struct file *in, loff_t *ppos,
for (i = 0; i < folio_batch_count(&fbatch); i++) {
struct folio *folio = fbatch.folios[i];
- size_t n;
+ ssize_t n;
if (folio_pos(folio) >= end_offset)
goto out;
@@ -3170,13 +3174,15 @@ ssize_t filemap_splice_read(struct file *in, loff_t *ppos,
n = min_t(loff_t, len, isize - *ppos);
n = splice_folio_into_pipe(pipe, folio, *ppos, n);
- if (!n)
+ if (n <= 0) {
+ error = n;
goto out;
+ }
len -= n;
total_spliced += n;
*ppos += n;
in->f_ra.prev_pos = *ppos;
- if (pipe_is_full(pipe))
+ if (pipe_is_full(pipe) || *ppos < folio_next_pos(folio))
goto out;
}
diff --git a/mm/internal.h b/mm/internal.h
index b129510b92e2a..08e259758937b 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1276,8 +1276,8 @@ struct migration_target_control {
/*
* mm/filemap.c
*/
-size_t splice_folio_into_pipe(struct pipe_inode_info *pipe,
- struct folio *folio, loff_t fpos, size_t size);
+ssize_t splice_folio_into_pipe(struct pipe_inode_info *pipe,
+ struct folio *folio, loff_t fpos, size_t size);
static inline bool vma_is_single_threaded_private(struct vm_area_struct *vma)
{
diff --git a/mm/shmem.c b/mm/shmem.c
index be4ab228cf67c..8b94877c36c18 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -3751,7 +3751,8 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
struct inode *inode = file_inode(in);
struct address_space *mapping = inode->i_mapping;
struct folio *folio = NULL;
- size_t total_spliced = 0, used, npages, n, part;
+ size_t total_spliced = 0, used, npages, part;
+ ssize_t n;
loff_t isize;
int error = 0;
@@ -3837,8 +3838,10 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
n = splice_zeropage_into_pipe(pipe, *ppos, part);
}
- if (!n)
+ if (n <= 0) {
+ error = n;
break;
+ }
len -= n;
total_spliced += n;
*ppos += n;
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 3/9] mm/mmu_gather: flush pending references early before the folio limit
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 1/9] mm: limit pagecache folio refcount when mapping pages David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 2/9] mm: limit pagecache folio refcount when splicing into pipes David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 4/9] iommufd: fix cleanup when adding memfd folio pins fails David Hildenbrand (Arm)
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, 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. These deferred references can build up over multiple VMAs.
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.
When returning "true" from __tlb_remove_folio_pages_size(), PTE zapping
will flush the TLB before dropping the PT lock, but drop the references
afterwards. Refaults can still race with that, but flushing early reduces
the risk of letting others hit the limit.
This is a temporary measure until we have something better.
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
Reviewed-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/asm-generic/tlb.h | 4 ++--
include/linux/mm.h | 1 +
mm/memory.c | 4 ++--
mm/mmu_gather.c | 10 ++++++----
4 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h
index 3f4a180b48f3c..4bba02fd5d543 100644
--- a/include/asm-generic/tlb.h
+++ b/include/asm-generic/tlb.h
@@ -75,8 +75,8 @@
* - __tlb_remove_folio_pages_size()
*
* __tlb_remove_folio_pages_size() is the basic primitive that queues pages
- * for freeing. It will return a boolean indicating if the queue is (now)
- * full and a call to tlb_flush_mmu() is required.
+ * for freeing. It returns true when a call to tlb_flush_mmu() is required,
+ * either because the queue is full or a folio is near its refcount limit.
*
* tlb_remove_page() and tlb_remove_page_size() imply the call to
* tlb_flush_mmu() when required and has no return value.
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 33bc57342590c..a36ff83589c61 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))
bool __folio_ref_count_over_limit(unsigned int ref_count);
diff --git a/mm/memory.c b/mm/memory.c
index 977e037388963..a1a9ba2430f83 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -2072,8 +2072,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..e98397ac577c4 100644
--- a/mm/mmu_gather.c
+++ b/mm/mmu_gather.c
@@ -179,10 +179,7 @@ static bool __tlb_remove_folio_pages_size(struct mmu_gather *tlb,
#endif
batch = tlb->active;
- /*
- * Add the page and check if we are full. If so
- * force a flush.
- */
+ /* Add the pages before deciding whether we need to flush. */
if (likely(nr_pages == 1)) {
batch->encoded_pages[batch->nr++] = encode_page(page, flags);
} else {
@@ -190,6 +187,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.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 4/9] iommufd: fix cleanup when adding memfd folio pins fails
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (2 preceding siblings ...)
2026-11-10 20:35 ` [PATCH v3 3/9] mm/mmu_gather: flush pending references early before the folio limit David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 5/9] mm/gup: limit pagecache folio refcount when grabbing references David Hildenbrand (Arm)
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, stable, Jason Gunthorpe
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
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
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.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 5/9] mm/gup: limit pagecache folio refcount when grabbing references
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (3 preceding siblings ...)
2026-11-10 20:35 ` [PATCH v3 4/9] iommufd: fix cleanup when adding memfd folio pins fails David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 6/9] RFC: net: limit folio refcount when copying skb frag references David Hildenbrand (Arm)
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, stable
Let's add a 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.
Also propagate try_grab_folio() errors when grabbing additional
references in __get_user_pages() and in memfd_pin_folios(), so hitting
the limit returns -ENOMEM instead of -EFAULT and -EINVAL, respectively.
Keep the existing invalid-refcount warnings.
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
Reviewed-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/mm.h | 11 +++++------
mm/gup.c | 24 +++++++++++++++---------
2 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index a36ff83589c61..9b11af88de2b9 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2244,6 +2244,9 @@ bool __folio_ref_count_over_limit(unsigned int ref_count);
* to pagecache folios (!hugetlb), either before acquiring references or
* afterwards with rollback if the operation is refused.
*
+ * Also required when pinning folios without a dedicated pincount that use
+ * GUP_PIN_COUNTING_BIAS.
+ *
* Checking other folios does no harm.
*
* This check is an approximation; we leave some headroom such that concurrent
@@ -2951,12 +2954,8 @@ static inline bool folio_maybe_dma_pinned(struct folio *folio)
return atomic_read(&folio->_pincount) > 0;
/*
- * folio_ref_count() is signed. If that refcount overflows, then
- * folio_ref_count() returns a negative value, and callers will avoid
- * further incrementing the refcount.
- *
- * Here, for that overflow case, use the sign bit to count a little
- * bit higher via unsigned math, and thus still get an accurate result.
+ * Use unsigned math so even an unexpected negative refcount is
+ * treated as possibly pinned.
*/
return ((unsigned int)folio_ref_count(folio)) >=
GUP_PIN_COUNTING_BIAS;
diff --git a/mm/gup.c b/mm/gup.c
index ebbfee09b7278..531ef2469228e 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -79,6 +79,8 @@ static inline struct folio *try_get_folio(struct page *page, int refs)
folio = page_folio(page);
if (WARN_ON_ONCE(folio_ref_count(folio) < 0))
return NULL;
+ if (unlikely(folio_ref_count_over_limit(folio)))
+ return NULL;
if (unlikely(!folio_ref_try_add(folio, refs)))
return NULL;
@@ -143,6 +145,10 @@ int __must_check try_grab_folio(struct folio *folio, int refs,
if (WARN_ON_ONCE(folio_ref_count(folio) <= 0))
return -ENOMEM;
+ if ((flags & (FOLL_GET | FOLL_PIN)) &&
+ unlikely(folio_ref_count_over_limit(folio)))
+ return -ENOMEM;
+
if (unlikely(!(flags & FOLL_PCI_P2PDMA) && folio_is_pci_p2pdma(folio)))
return -EREMOTEIO;
@@ -1482,17 +1488,17 @@ 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)) {
+ ret = try_grab_folio(folio, page_increm - 1,
+ gup_flags);
+ if (ret) {
/*
- * Release the 1st page ref if the
- * folio is problematic, fail hard.
+ * Release the folio reference we
+ * already took.
*/
gup_put_folio(folio, 1, gup_flags);
- ret = -EFAULT;
goto out;
}
}
@@ -3541,10 +3547,10 @@ long memfd_pin_folios(struct file *memfd, loff_t start, loff_t end,
break;
}
- if (try_grab_folio(folio, 1, FOLL_PIN)) {
+ ret = try_grab_folio(folio, 1, FOLL_PIN);
+ if (ret) {
folio_unlock(folio);
folio_batch_release(&fbatch);
- ret = -EINVAL;
goto err;
}
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 6/9] RFC: net: limit folio refcount when copying skb frag references
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (4 preceding siblings ...)
2026-11-10 20:35 ` [PATCH v3 5/9] mm/gup: limit pagecache folio refcount when grabbing references David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 7/9] RFC: net: limit folio refcount when rearranging skb frags David Hildenbrand (Arm)
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, stable
Various networking features copy frag references of an skb, for example
when unsharing a cloned skb (AF_PACKET trimming a clone), when copying an
skb (nft dup) or when building NFQUEUE messages. This is an effective
way for multiplying folio references held by an skb, allowing for
exceeding folio refcount limits.
This is not specific to large pagecache folios, but with them it is a
lot easier to reach the refcount limit, as page table mappings can cheaply
create many references.
Let's introduce skb_frag_try_ref() and skb_frags_try_ref(), which use
try_get_page(), and use them in pskb_expand_head(), __pskb_copy_fclone(),
skb_zerocopy() and pskb_carve_inside_*(). All of these can already fail,
and their callers handle that.
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/skbuff_ref.h | 53 ++++++++++++++++++++++++++++++++++++++++++++++
net/core/skbuff.c | 35 +++++++++++++++++++-----------
2 files changed, 76 insertions(+), 12 deletions(-)
diff --git a/include/linux/skbuff_ref.h b/include/linux/skbuff_ref.h
index b6f6da556ee90..560ddc863bbec 100644
--- a/include/linux/skbuff_ref.h
+++ b/include/linux/skbuff_ref.h
@@ -44,6 +44,33 @@ static __always_inline void skb_frag_ref(struct sk_buff *skb, int f)
skb->pp_recycle);
}
+/**
+ * skb_frag_try_ref - try to acquire a reference on a paged fragment of an skb
+ * @skb: the buffer
+ * @f: the fragment offset
+ *
+ * Like skb_frag_ref(), but acquires non-page_pool page references using
+ * try_get_page(), refusing additional references to a page whose refcount is
+ * at its limit.
+ *
+ * Return: True if a reference was acquired, otherwise false.
+ */
+static __always_inline __must_check bool skb_frag_try_ref(struct sk_buff *skb,
+ int f)
+{
+ netmem_ref netmem = skb_frag_netmem(&skb_shinfo(skb)->frags[f]);
+
+#ifdef CONFIG_PAGE_POOL
+ if (skb->pp_recycle && napi_pp_get_page(netmem))
+ return true;
+#endif
+ if (netmem_is_net_iov(netmem)) {
+ net_iov_inc_ref(netmem_to_net_iov(netmem));
+ return true;
+ }
+ return try_get_page(netmem_to_page(netmem));
+}
+
bool napi_pp_put_page(netmem_ref netmem);
/**
@@ -83,4 +110,30 @@ static __always_inline void skb_frag_unref(struct sk_buff *skb, int f)
skb->pp_recycle);
}
+/**
+ * skb_frags_try_ref - try to acquire references on all paged fragments
+ * @skb: the buffer
+ *
+ * Try to acquire a reference on each paged fragment of @skb using
+ * skb_frag_try_ref(). On failure, acquired references are dropped.
+ *
+ * Must not be used on skbs with managed frag references.
+ *
+ * Return: True if all references were acquired, otherwise false.
+ */
+static inline __must_check bool skb_frags_try_ref(struct sk_buff *skb)
+{
+ int i;
+
+ DEBUG_NET_WARN_ON_ONCE(skb_zcopy_managed(skb));
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ if (unlikely(!skb_frag_try_ref(skb, i))) {
+ while (i--)
+ skb_frag_unref(skb, i);
+ return false;
+ }
+ }
+ return true;
+}
+
#endif /* _LINUX_SKBUFF_REF_H */
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 8da53d5b76fc2..1d44e9a4152e5 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -2254,7 +2254,13 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom,
n->pp_recycle = skb->pp_recycle;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
- skb_frag_ref(n, i);
+ if (!skb_frag_try_ref(n, i)) {
+ /* Drop the references acquired so far. */
+ skb_shinfo(n)->nr_frags = i;
+ kfree_skb(n);
+ n = NULL;
+ goto out;
+ }
}
skb_shinfo(n)->nr_frags = i;
skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG;
@@ -2298,7 +2304,6 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
unsigned int size = osize + nhead + ntail;
long off;
u8 *data;
- int i;
if (unlikely(nhead < 0)) {
DEBUG_NET_WARN_ON_ONCE(1);
@@ -2335,12 +2340,10 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
* be since all we did is relocate the values
*/
if (skb_cloned(skb)) {
- if (skb_orphan_frags(skb, gfp_mask))
+ if (skb_orphan_frags(skb, gfp_mask) || !skb_frags_try_ref(skb))
goto nofrags;
if (skb_zcopy(skb))
net_zcopy_get(skb_uarg(skb));
- for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
- skb_frag_ref(skb, i);
if (skb_has_frag_list(skb))
skb_clone_fraglist(skb);
@@ -3896,7 +3899,8 @@ EXPORT_SYMBOL_GPL(skb_zerocopy_headlen);
*
* Return value:
* 0: everything is OK
- * -ENOMEM: couldn't orphan frags of @from due to lack of memory
+ * -ENOMEM: couldn't orphan frags of @from due to lack of memory, or
+ * couldn't take additional references to the frags of @from
* -EFAULT: skb_copy_bits() found some problem with skb geometry, or readable head
* payload would be mixed with unreadable frags.
*/
@@ -3961,7 +3965,11 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
len);
skb_frag_size_set(&skb_shinfo(to)->frags[j], size);
len -= size;
- skb_frag_ref(to, j);
+ if (!skb_frag_try_ref(to, j)) {
+ /* The caller frees @to, dropping the acquired refs. */
+ skb_shinfo(to)->nr_frags = j;
+ return -ENOMEM;
+ }
j++;
}
skb_shinfo(to)->nr_frags = j;
@@ -6966,7 +6974,6 @@ static void skb_carve_reset_headers(struct sk_buff *skb)
static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off,
const int headlen, gfp_t gfp_mask)
{
- int i;
unsigned int size = skb_end_offset(skb);
int new_hlen = headlen - off;
u8 *data;
@@ -6994,14 +7001,12 @@ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off,
frags[skb_shinfo(skb)->nr_frags]));
if (skb_cloned(skb)) {
/* drop the old head gracefully */
- if (skb_orphan_frags(skb, gfp_mask)) {
+ if (skb_orphan_frags(skb, gfp_mask) || !skb_frags_try_ref(skb)) {
skb_kfree_head(data);
return -ENOMEM;
}
if (skb_zcopy(skb))
net_zcopy_get(skb_uarg(skb));
- for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
- skb_frag_ref(skb, i);
if (skb_has_frag_list(skb))
skb_clone_fraglist(skb);
skb_release_data(skb, SKB_CONSUMED);
@@ -7131,7 +7136,13 @@ static int pskb_carve_inside_nonlinear(struct sk_buff *skb, const u32 off,
skb_frag_off_add(&shinfo->frags[0], off - pos);
skb_frag_size_sub(&shinfo->frags[0], off - pos);
}
- skb_frag_ref(skb, i);
+ if (!skb_frag_try_ref(skb, i)) {
+ /* We referenced frags i - k ... i - 1 so far. */
+ while (k--)
+ skb_frag_unref(skb, --i);
+ skb_kfree_head(data);
+ return -ENOMEM;
+ }
k++;
}
pos += fsize;
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 7/9] RFC: net: limit folio refcount when rearranging skb frags
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (5 preceding siblings ...)
2026-11-10 20:35 ` [PATCH v3 6/9] RFC: net: limit folio refcount when copying skb frag references David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 8/9] RFC: net: limit folio refcount when splicing pages into skbs David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 9/9] RFC: net: limit folio refcount when splicing from sockets David Hildenbrand (Arm)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, stable
Various networking features take additional references to frag pages
that are still referenced elsewhere when rearranging skb frags, for
example software GSO segmentation, TCP receive coalescing of clones, SACK
shifting and MTU probing. This is an effective way for multiplying folio
references held by an skb, allowing for exceeding folio refcount limits.
This is not specific to large pagecache folios, but with them it is a
lot easier to reach the refcount limit, as page table mappings can cheaply
create many references.
Let's use skb_frag_try_ref() and skb_frags_try_ref() in skb_segment(),
skb_try_coalesce(), skb_shift() and tcp_clone_payload(). All of these can
already fail, and their callers handle that.
skb_split() can still add one reference when splitting a frag. But each
split also allocates a new skb that is charged to the socket, reaching
the refcount limit that way would require a lot of socket memory, and the
references go away once the data was acked, so it's not that relevant in
practice. Making skb_split() fail is more involved, so let's leave it alone
for now.
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>
---
net/core/skbuff.c | 32 +++++++++++++++++++++-----------
net/ipv4/tcp_output.c | 8 +++++---
2 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 1d44e9a4152e5..f473c74956b8b 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4326,7 +4326,8 @@ static int skb_prepare_for_shift(struct sk_buff *skb)
* the length of the skb, from skb to tgt. Returns number bytes shifted.
* It's up to caller to free skb if everything was shifted.
*
- * If @tgt runs out of frags, the whole operation is aborted.
+ * If @tgt runs out of frags, or we cannot take an additional frag reference,
+ * the whole operation is aborted.
*
* Skb cannot include anything else but paged data while tgt is allowed
* to have non-paged data as well.
@@ -4409,7 +4410,9 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)
} else {
skb_frag_page_copy(fragto, fragfrom);
- skb_frag_ref(tgt, to);
+ /* Nothing has been committed yet. */
+ if (!skb_frag_try_ref(tgt, to))
+ return 0;
skb_frag_off_copy(fragto, fragfrom);
skb_frag_size_set(fragto, todo);
@@ -5148,7 +5151,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
if (!skb_shinfo(nskb)->nr_frags)
nskb->pp_recycle = frag_skb->pp_recycle;
*nskb_frag = (i < 0) ? skb_head_frag_to_page_desc(frag_skb) : *frag;
- skb_frag_ref(nskb, skb_shinfo(nskb)->nr_frags);
+ if (!skb_frag_try_ref(nskb, skb_shinfo(nskb)->nr_frags)) {
+ err = -ENOMEM;
+ goto err;
+ }
size = skb_frag_size(nskb_frag);
if (pos < offset) {
@@ -6289,7 +6295,8 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
bool *fragstolen, int *delta_truesize)
{
struct skb_shared_info *to_shinfo, *from_shinfo;
- int i, delta, len = from->len;
+ int delta, len = from->len;
+ bool cloned;
*fragstolen = false;
@@ -6362,6 +6369,15 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
WARN_ON_ONCE(delta < len);
+ /*
+ * A clone keeps referencing its frags, so we need our own references.
+ * Nothing was added to @to yet: we only steal the head of @from above
+ * if it is not locked, and the head of a clone is always locked.
+ */
+ cloned = skb_cloned(from);
+ if (cloned && !skb_frags_try_ref(from))
+ return false;
+
memcpy(to_shinfo->frags + to_shinfo->nr_frags,
from_shinfo->frags,
from_shinfo->nr_frags * sizeof(skb_frag_t));
@@ -6369,15 +6385,9 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
if (from_shinfo->nr_frags)
to_shinfo->flags |= from_shinfo->flags & SKBFL_SHARED_FRAG;
- if (!skb_cloned(from))
+ if (!cloned)
from_shinfo->nr_frags = 0;
- /* if the skb is not cloned this does nothing
- * since we set nr_frags to 0.
- */
- for (i = 0; i < from_shinfo->nr_frags; i++)
- skb_frag_ref(from, i);
-
to->truesize += delta;
to->len += len;
to->data_len += len;
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index cf3ddc055d48d..043838d996404 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -2710,10 +2710,12 @@ static int tcp_clone_payload(struct sock *sk, struct sk_buff *to,
}
commit:
WARN_ON_ONCE(len != probe_size);
- for (i = 0; i < nr_frags; i++)
- skb_frag_ref(to, i);
-
skb_shinfo(to)->nr_frags = nr_frags;
+ if (!skb_frags_try_ref(to)) {
+ skb_shinfo(to)->nr_frags = 0;
+ return -ENOMEM;
+ }
+
to->truesize += probe_size;
to->len += probe_size;
to->data_len += probe_size;
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 8/9] RFC: net: limit folio refcount when splicing pages into skbs
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (6 preceding siblings ...)
2026-11-10 20:35 ` [PATCH v3 7/9] RFC: net: limit folio refcount when rearranging skb frags David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 9/9] RFC: net: limit folio refcount when splicing from sockets David Hildenbrand (Arm)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, stable
Various networking features use MSG_SPLICE_PAGES to add pages to skbs,
whereby skb_append_pagefrags() takes a page reference for each fragment it
adds. Pages spliced from pipes were already checked when entering the
pipe, but in-kernel users like sunrpc pass pagecache pages directly. This
allows for exceeding folio refcount limits.
This is not specific to large pagecache folios, but with them it is a
lot easier to reach the refcount limit, as page table mappings can cheaply
create many references.
Let's use try_get_page() in skb_append_pagefrags() and fail with -EFAULT.
Don't use -EMSGSIZE, as TCP would just retry with a new skb. The only
caller, skb_splice_from_iter(), handles that.
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>
---
net/core/skbuff.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index f473c74956b8b..5e3f910eeea0b 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4702,8 +4702,13 @@ int skb_append_pagefrags(struct sk_buff *skb, struct page *page,
if (skb_can_coalesce(skb, i, page, offset)) {
skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], size);
} else if (i < max_frags) {
+ /*
+ * Don't use -EMSGSIZE, as callers would just retry with a new
+ * skb.
+ */
+ if (!try_get_page(page))
+ return -EFAULT;
skb_zcopy_downgrade_managed(skb);
- get_page(page);
skb_fill_page_desc_noacc(skb, i, page, offset, size);
} else {
return -EMSGSIZE;
@@ -7575,8 +7580,9 @@ static void skb_splice_csum_page(struct sk_buff *skb, struct page *page,
* possible, copying them to fragments if not possible (such as if they're slab
* pages).
*
- * Returns the amount of data spliced/copied or -EMSGSIZE if there's
- * insufficient space in the buffer to transfer anything.
+ * Returns the amount of data spliced/copied, -EMSGSIZE if there's
+ * insufficient space in the buffer to transfer anything, or -EFAULT if we
+ * could not take a page reference.
*/
ssize_t skb_splice_from_iter(struct sk_buff *skb, struct iov_iter *iter,
ssize_t maxsize)
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 9/9] RFC: net: limit folio refcount when splicing from sockets
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
` (7 preceding siblings ...)
2026-11-10 20:35 ` [PATCH v3 8/9] RFC: net: limit folio refcount when splicing pages into skbs David Hildenbrand (Arm)
@ 2026-11-10 20:35 ` David Hildenbrand (Arm)
8 siblings, 0 replies; 10+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-10 20:35 UTC (permalink / raw)
To: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Matthew Wilcox (Oracle),
Jan Kara, Andrew Morton, 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, Miklos Szeredi,
Alexander Viro, Christian Brauner, Hugh Dickins, Will Deacon,
Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra, Arnd Bergmann,
Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Robin Murphy, Steve Sistare, John Hubbard,
Jose A. Perez de Azpillaga, Josef Bacik, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Neal Cardwell, Kuniyuki Iwashima, Linus Torvalds
Cc: David Hildenbrand (Arm),
linux-mm, linux-kernel, linux-fsdevel, fuse-devel, linux-arch,
iommu, netdev, stable
Various networking features support splicing data from sockets into
pipes (TCP, AF_UNIX, KCM, kTLS), whereby spd_fill_page() takes an
additional page reference for each fragment, while the skb keeps its own
reference until it was fully consumed. This allows for exceeding folio
refcount limits.
This is not specific to large pagecache folios, but with them it is a
lot easier to reach the refcount limit, as page table mappings can cheaply
create many references.
Let's use try_get_page() in spd_fill_page() and stop splicing with
-EFAULT if that fails. To propagate the error, let spd_fill_page(),
__splice_segment() and __skb_splice_bits() return an int instead of a
bool. If we could not splice anything, return the error from
skb_splice_bits(): returning 0 would make the callers (TCP, AF_UNIX, KCM,
kTLS) report EOF, although data is still queued. The callers already
handle errors.
Use -EFAULT, consistent with splice_folio_into_pipe().
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>
---
net/core/skbuff.c | 90 +++++++++++++++++++++++++++++++------------------------
1 file changed, 51 insertions(+), 39 deletions(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 5e3f910eeea0b..a5965f8fa1b00 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3150,11 +3150,13 @@ static bool spd_can_coalesce(const struct splice_pipe_desc *spd,
}
/*
- * Fill page/offset/length into spd, if it can hold more pages.
+ * Fill page/offset/length into spd, if it can hold more pages and we can
+ * take a page reference. Returns non-zero to stop, or a negative error
+ * if we cannot take a page reference.
*/
-static bool spd_fill_page(struct splice_pipe_desc *spd, struct page *page,
- unsigned int *len, unsigned int offset, bool linear,
- struct sock *sk)
+static int spd_fill_page(struct splice_pipe_desc *spd, struct page *page,
+ unsigned int *len, unsigned int offset, bool linear,
+ struct sock *sk)
{
if (unlikely(spd->nr_pages == MAX_SKB_FRAGS))
return true;
@@ -3168,7 +3170,8 @@ static bool spd_fill_page(struct splice_pipe_desc *spd, struct page *page,
spd->partial[spd->nr_pages - 1].len += *len;
return false;
}
- get_page(page);
+ if (!try_get_page(page))
+ return -EFAULT;
spd->pages[spd->nr_pages] = page;
spd->partial[spd->nr_pages].len = *len;
spd->partial[spd->nr_pages].offset = offset;
@@ -3177,12 +3180,14 @@ static bool spd_fill_page(struct splice_pipe_desc *spd, struct page *page,
return false;
}
-static bool __splice_segment(struct page *page, unsigned int poff,
- unsigned int plen, unsigned int *off,
- unsigned int *len,
- struct splice_pipe_desc *spd, bool linear,
- struct sock *sk)
+static int __splice_segment(struct page *page, unsigned int poff,
+ unsigned int plen, unsigned int *off,
+ unsigned int *len,
+ struct splice_pipe_desc *spd, bool linear,
+ struct sock *sk)
{
+ int ret;
+
if (!*len)
return true;
@@ -3200,8 +3205,9 @@ static bool __splice_segment(struct page *page, unsigned int poff,
do {
unsigned int flen = min(*len, plen);
- if (spd_fill_page(spd, page, &flen, poff, linear, sk))
- return true;
+ ret = spd_fill_page(spd, page, &flen, poff, linear, sk);
+ if (ret)
+ return ret;
poff += flen;
plen -= flen;
*len -= flen;
@@ -3213,28 +3219,30 @@ static bool __splice_segment(struct page *page, unsigned int poff,
}
/*
- * Map linear and fragment data from the skb to spd. It reports true if the
- * pipe is full or if we already spliced the requested length.
+ * Map linear and fragment data from the skb to spd. It returns non-zero to
+ * stop: true if the pipe is full or if we already spliced the requested
+ * length, or a negative error if we cannot take a page reference.
*/
-static bool __skb_splice_bits(struct sk_buff *skb, struct pipe_inode_info *pipe,
- unsigned int *offset, unsigned int *len,
- struct splice_pipe_desc *spd, struct sock *sk)
+static int __skb_splice_bits(struct sk_buff *skb, struct pipe_inode_info *pipe,
+ unsigned int *offset, unsigned int *len,
+ struct splice_pipe_desc *spd, struct sock *sk)
{
struct sk_buff *iter;
- int seg;
+ int seg, ret;
/* map the linear part :
* If skb->head_frag is set, this 'linear' part is backed by a
* fragment, and if the head is not shared with any clones then
* we can avoid a copy since we own the head portion of this page.
*/
- if (__splice_segment(virt_to_page(skb->data),
- (unsigned long) skb->data & (PAGE_SIZE - 1),
- skb_headlen(skb),
- offset, len, spd,
- skb_head_is_locked(skb),
- sk))
- return true;
+ ret = __splice_segment(virt_to_page(skb->data),
+ (unsigned long)skb->data & (PAGE_SIZE - 1),
+ skb_headlen(skb),
+ offset, len, spd,
+ skb_head_is_locked(skb),
+ sk);
+ if (ret)
+ return ret;
/*
* then map the fragments
@@ -3248,10 +3256,11 @@ static bool __skb_splice_bits(struct sk_buff *skb, struct pipe_inode_info *pipe,
if (WARN_ON_ONCE(!skb_frag_page(f)))
return false;
- if (__splice_segment(skb_frag_page(f),
- skb_frag_off(f), skb_frag_size(f),
- offset, len, spd, false, sk))
- return true;
+ ret = __splice_segment(skb_frag_page(f),
+ skb_frag_off(f), skb_frag_size(f),
+ offset, len, spd, false, sk);
+ if (ret)
+ return ret;
}
skb_walk_frags(skb, iter) {
@@ -3260,11 +3269,12 @@ static bool __skb_splice_bits(struct sk_buff *skb, struct pipe_inode_info *pipe,
continue;
}
/* __skb_splice_bits() only fails if the output has no room
- * left, so no point in going over the frag_list for the error
- * case.
+ * left or on error, so no point in going over the frag_list
+ * for the error case.
*/
- if (__skb_splice_bits(iter, pipe, offset, len, spd, sk))
- return true;
+ ret = __skb_splice_bits(iter, pipe, offset, len, spd, sk);
+ if (ret)
+ return ret;
}
return false;
@@ -3272,7 +3282,9 @@ static bool __skb_splice_bits(struct sk_buff *skb, struct pipe_inode_info *pipe,
/*
* Map data from the skb to a pipe. Should handle both the linear part,
- * the fragments, and the frag list.
+ * the fragments, and the frag list. Returns the number of bytes spliced,
+ * or an error if we could not splice anything because we could not take
+ * a page reference.
*/
int skb_splice_bits(struct sk_buff *skb, struct sock *sk, unsigned int offset,
struct pipe_inode_info *pipe, unsigned int tlen,
@@ -3287,14 +3299,14 @@ int skb_splice_bits(struct sk_buff *skb, struct sock *sk, unsigned int offset,
.ops = &nosteal_pipe_buf_ops,
.spd_release = sock_spd_release,
};
- int ret = 0;
+ int ret;
- __skb_splice_bits(skb, pipe, &offset, &tlen, &spd, sk);
+ ret = __skb_splice_bits(skb, pipe, &offset, &tlen, &spd, sk);
if (spd.nr_pages)
- ret = splice_to_pipe(pipe, &spd);
-
- return ret;
+ return splice_to_pipe(pipe, &spd);
+ /* Returning 0 would make callers report EOF. */
+ return ret < 0 ? ret : 0;
}
EXPORT_SYMBOL_GPL(skb_splice_bits);
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-10 20:39 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 20:35 [PATCH v3 0/9] mm: limit pagecache folio refcount David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 1/9] mm: limit pagecache folio refcount when mapping pages David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 2/9] mm: limit pagecache folio refcount when splicing into pipes David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 3/9] mm/mmu_gather: flush pending references early before the folio limit David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 4/9] iommufd: fix cleanup when adding memfd folio pins fails David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 5/9] mm/gup: limit pagecache folio refcount when grabbing references David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 6/9] RFC: net: limit folio refcount when copying skb frag references David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 7/9] RFC: net: limit folio refcount when rearranging skb frags David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 8/9] RFC: net: limit folio refcount when splicing pages into skbs David Hildenbrand (Arm)
2026-11-10 20:35 ` [PATCH v3 9/9] RFC: net: limit folio refcount when splicing from sockets 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
all inboxes | Powered by JetHome®