[PATCH] mm: limit pagecache folio refcount when splicing into pipes
From: David Hildenbrand (Arm)
Date: Wed Nov 04 2026 - 13:02:48 EST
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.
Let's keep it simple and check in the two callers of
splice_folio_into_pipe(). Return -ETOOMANYREFS if the limit prevents any
progress, so do_splice_read() can fall back to copy_splice_read() when
read_iter is available. This keeps sendfile() and other splice users
working when ordinary reads would still succeed.
Also fall back to copying in FUSE when taking another reference through
fuse_ref_folio() would exceed the limit.
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@xxxxxxxxxxxxxxx
Signed-off-by: David Hildenbrand (Arm) <david@xxxxxxxxxx>
---
fs/fuse/dev.c | 5 +++--
fs/splice.c | 8 +++++++-
include/linux/mm.h | 9 ++++++---
mm/filemap.c | 11 ++++++++---
mm/shmem.c | 4 ++++
5 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 3641abb90d6bb..4f6b7b42bfd3e 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1265,9 +1265,10 @@ int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop,
if (cs->write && cs->pipebufs && folio) {
/*
* Can't control lifetime of pipe buffers, so always
- * copy user pages.
+ * copy user pages. Also copy folios at their refcount limit.
*/
- if (cs->req->args->user_pages) {
+ if (cs->req->args->user_pages ||
+ unlikely(folio_ref_count_over_limit(folio))) {
err = fuse_copy_fill(cs);
if (err)
return err;
diff --git a/fs/splice.c b/fs/splice.c
index e6d43329ed3f0..2fc1279628af5 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -956,6 +956,7 @@ static ssize_t do_splice_read(struct file *in, loff_t *ppos,
unsigned int flags)
{
unsigned int p_space;
+ ssize_t ret;
if (unlikely(!(in->f_mode & FMODE_READ)))
return -EBADF;
@@ -977,7 +978,12 @@ static ssize_t do_splice_read(struct file *in, loff_t *ppos,
*/
if ((in->f_flags & O_DIRECT) || IS_DAX(in->f_mapping->host))
return copy_splice_read(in, ppos, pipe, len, flags);
- return in->f_op->splice_read(in, ppos, pipe, len, flags);
+ ret = in->f_op->splice_read(in, ppos, pipe, len, flags);
+
+ /* Fallback to copying when hitting folio refcount limits. */
+ if (unlikely(ret == -ETOOMANYREFS) && in->f_op->read_iter)
+ ret = copy_splice_read(in, ppos, pipe, len, flags);
+ return ret;
}
/**
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 3356f7a670d1e..dd50262a70758 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2288,10 +2288,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..44b23c51ea45f 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3094,9 +3094,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; -ETOOMANYREFS will be
+ * returned if the folio's refcount is too high and the caller can fall back to
+ * copying. A short read may occur if the pipe has insufficient space, we reach
+ * the end of the data or we hit a hole.
*/
ssize_t filemap_splice_read(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe,
@@ -3168,6 +3169,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 = -ETOOMANYREFS;
+ 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 be4ab228cf67c..0b30b1de81a7c 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -3830,6 +3830,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 = -ETOOMANYREFS;
+ break;
+ }
n = splice_folio_into_pipe(pipe, folio, *ppos, part);
folio_put(folio);
folio = NULL;
--
2.56.0
--
Cheers,
David