From 07ad246529d136d5ef441d5ab4c305d132ff3090 Mon Sep 17 00:00:00 2001 From: Daming Li Date: Thu, 30 Jul 2026 22:55:52 +0800 Subject: [PATCH] net: smc: fix splice entry lifetime imbalance in smc_rx_splice commit 5d9686af2976741bbd79b150d1c9e60b81e7f12e upstream. smc_rx_splice() passes pages to splice_to_pipe() before taking the references that cover the lifetime of each splice entry. In the VM-backed RMB path, splice_to_pipe() may drop unqueued entries through smc_rx_spd_release(), while queued entries are released later via the pipe buffer callback. The old post-splice accounting also derives the number of queued VM pages from an offset mutated while building the descriptor, and a multi-page splice pairs one sock_hold() with multiple sock_put() calls. Take the page and socket references for every candidate entry before splice_to_pipe(), and drop the matching private state, page reference, and socket reference from smc_rx_spd_release() for entries that never get queued. This fixes a refcount imbalance that can underflow page refcounts and trigger a use-after-free. Fixes: 9014db202cb7 ("smc: add support for splice()") Cc: stable@vger.kernel.org Reported-by: Vega Co-developed-by: Xiao Liu Signed-off-by: Xiao Liu Signed-off-by: Daming Li Signed-off-by: Ren Wei Reviewed-by: Dust Li Reviewed-by: Sidraya Jayagond Link: https://patch.msgid.link/20260730145552.360287-2-enjou1224z@gmail.com Signed-off-by: Jakub Kicinski [ collapsed the multi-page get_page()/sock_hold() loop to a single get_page()/sock_hold() pair and dropped the trailing kfree() calls, as 5.15 lacks the multi-page pages[]/partial[]/priv[] arrays ] Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- net/smc/smc_rx.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/net/smc/smc_rx.c b/net/smc/smc_rx.c index 3468c58bbeeb..33b5bcafdec7 100644 --- a/net/smc/smc_rx.c +++ b/net/smc/smc_rx.c @@ -145,7 +145,12 @@ static const struct pipe_buf_operations smc_pipe_ops = { static void smc_rx_spd_release(struct splice_pipe_desc *spd, unsigned int i) { + struct smc_spd_priv *priv = (struct smc_spd_priv *)spd->partial[i].private; + struct sock *sk = &priv->smc->sk; + + kfree(priv); put_page(spd->pages[i]); + sock_put(sk); } static int smc_rx_splice(struct pipe_inode_info *pipe, char *src, size_t len, @@ -165,6 +170,8 @@ static int smc_rx_splice(struct pipe_inode_info *pipe, char *src, size_t len, partial.len = len; partial.private = (unsigned long)priv; + get_page(smc->conn.rmb_desc->pages); + sock_hold(&smc->sk); spd.nr_pages_max = 1; spd.nr_pages = 1; spd.pages = &smc->conn.rmb_desc->pages; @@ -173,11 +180,8 @@ static int smc_rx_splice(struct pipe_inode_info *pipe, char *src, size_t len, spd.spd_release = smc_rx_spd_release; bytes = splice_to_pipe(pipe, &spd); - if (bytes > 0) { - sock_hold(&smc->sk); - get_page(smc->conn.rmb_desc->pages); + if (bytes > 0) atomic_add(bytes, &smc->conn.splice_pending); - } return bytes; }