From: Chuck Lever <cel@kernel.org>
To: Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>,
Olga Kornievskaia <okorniev@redhat.com>,
Dai Ngo <Dai.Ngo@oracle.com>, Tom Talpey <tom@talpey.com>,
"J. Bruce Fields" <bfields@fieldses.org>,
Scott Mayhew <smayhew@redhat.com>,
Trond Myklebust <trondmy@kernel.org>,
Anna Schumaker <anna@kernel.org>
Cc: linux-nfs@vger.kernel.org, Chuck Lever <cel@kernel.org>
Subject: [PATCH 5/5] pnfs/blocklayout: Complete a device upcall only on its own reply
Date: Sun, 30 Aug 2026 20:38:42 -0400 [thread overview]
Message-ID: <20260830-alemi-v1-5-463f80b9e9a8@kernel.org> (raw)
In-Reply-To: <20260830-alemi-v1-0-463f80b9e9a8@kernel.org>
bl_pipe_downcall() wakes bl_resolve_deviceid() on any write of the
right size, without checking that blkmapd has read the upcall being
replied to. A write that arrives first returns the waiter while its
struct rpc_pipe_msg is still queued on the pipe, which then holds a
list_head into a dead stack frame. Reaching the pipe takes root, so
the trigger is a broken or hostile blkmapd.
The wait has two further defects. bl_resolve_deviceid() sets
TASK_UNINTERRUPTIBLE only after rpc_queue_upcall() has made the
message visible, so a reply that lands in between wakes a running
task and the schedule() that follows sleeps forever holding
bl_mutex. And nn->bl_mount_reply is never reset, so when the pipe
purges an unread upcall the waiter takes the previous reply as its
own.
Move the message into struct nfs_net, where bl_mutex already limits
the pipe to one upcall per net namespace. Accept a reply only while
blkmapd has read the whole message and no earlier reply has been
taken, and reject any other write with -EINVAL. Wait for that reply
on a completion so it cannot slip past the sleep, and check
msg->errno before trusting it.
Fixes: fe0a9b740881 ("pnfsblock: add device operations")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs/blocklayout/blocklayout.h | 5 ----
fs/nfs/blocklayout/rpc_pipefs.c | 56 +++++++++++++++++++++++++---------------
fs/nfs/netns.h | 5 +++-
3 files changed, 39 insertions(+), 27 deletions(-)
diff --git a/fs/nfs/blocklayout/blocklayout.h b/fs/nfs/blocklayout/blocklayout.h
index 6da40ca19570..e242b1d5b4bd 100644
--- a/fs/nfs/blocklayout/blocklayout.h
+++ b/fs/nfs/blocklayout/blocklayout.h
@@ -161,11 +161,6 @@ BLK_LSEG2EXT(struct pnfs_layout_segment *lseg)
return BLK_LO2EXT(lseg->pls_layout);
}
-struct bl_pipe_msg {
- struct rpc_pipe_msg msg;
- wait_queue_head_t *bl_wq;
-};
-
struct bl_msg_hdr {
u8 type;
u16 totallen; /* length of entire message, including hdr itself */
diff --git a/fs/nfs/blocklayout/rpc_pipefs.c b/fs/nfs/blocklayout/rpc_pipefs.c
index d526f5ba7887..63db51759658 100644
--- a/fs/nfs/blocklayout/rpc_pipefs.c
+++ b/fs/nfs/blocklayout/rpc_pipefs.c
@@ -55,17 +55,15 @@ bl_resolve_deviceid(struct nfs_server *server, struct pnfs_block_volume *b,
struct net *net = server->nfs_client->cl_net;
struct nfs_net *nn = net_generic(net, nfs_net_id);
struct bl_dev_msg *reply = &nn->bl_mount_reply;
- struct bl_pipe_msg bl_pipe_msg;
- struct rpc_pipe_msg *msg = &bl_pipe_msg.msg;
+ struct rpc_pipe_msg *msg = &nn->bl_pipe_msg;
+ struct rpc_pipe *pipe = nn->bl_device_pipe;
struct bl_msg_hdr *bl_msg;
- DECLARE_WAITQUEUE(wq, current);
dev_t dev = 0;
int rc;
dprintk("%s CREATING PIPEFS MESSAGE\n", __func__);
mutex_lock(&nn->bl_mutex);
- bl_pipe_msg.bl_wq = &nn->bl_wq;
b->simple.len += 4; /* single volume */
if (b->simple.len > PAGE_SIZE)
@@ -83,17 +81,20 @@ bl_resolve_deviceid(struct nfs_server *server, struct pnfs_block_volume *b,
nfs4_encode_simple(msg->data + sizeof(*bl_msg), b);
dprintk("%s CALLING USERSPACE DAEMON\n", __func__);
- add_wait_queue(&nn->bl_wq, &wq);
- rc = rpc_queue_upcall(nn->bl_device_pipe, msg);
- if (rc < 0) {
- remove_wait_queue(&nn->bl_wq, &wq);
+ reinit_completion(&nn->bl_done);
+ rc = rpc_queue_upcall(pipe, msg);
+ if (rc < 0)
goto out_free_data;
- }
- set_current_state(TASK_UNINTERRUPTIBLE);
- schedule();
- remove_wait_queue(&nn->bl_wq, &wq);
+ wait_for_completion(&nn->bl_done);
+ /* Retire the upcall so bl_pipe_downcall() rejects a later write. */
+ spin_lock(&pipe->lock);
+ msg->copied = 0;
+ spin_unlock(&pipe->lock);
+
+ if (msg->errno < 0)
+ goto out_free_data;
if (reply->status != BL_DEVICE_REQUEST_PROC) {
printk(KERN_WARNING "%s failed to decode device: %d\n",
__func__, reply->status);
@@ -113,26 +114,39 @@ static ssize_t bl_pipe_downcall(struct file *filp, const char __user *src,
{
struct nfs_net *nn = net_generic(file_inode(filp)->i_sb->s_fs_info,
nfs_net_id);
+ struct rpc_pipe *pipe = nn->bl_device_pipe;
+ struct bl_dev_msg reply;
+ bool accepted;
- if (mlen != sizeof (struct bl_dev_msg))
+ if (mlen != sizeof(reply))
return -EINVAL;
-
- if (copy_from_user(&nn->bl_mount_reply, src, mlen) != 0)
+ if (copy_from_user(&reply, src, mlen) != 0)
return -EFAULT;
- wake_up(&nn->bl_wq);
-
+ /*
+ * Only the first reply counts, and only after blkmapd has read
+ * the whole upcall and before bl_resolve_deviceid() retires it.
+ */
+ spin_lock(&pipe->lock);
+ accepted = rpc_msg_is_inflight(&nn->bl_pipe_msg) &&
+ !completion_done(&nn->bl_done);
+ if (accepted) {
+ nn->bl_mount_reply = reply;
+ complete(&nn->bl_done);
+ }
+ spin_unlock(&pipe->lock);
+ if (!accepted)
+ return -EINVAL;
return mlen;
}
static void bl_pipe_destroy_msg(struct rpc_pipe_msg *msg)
{
- struct bl_pipe_msg *bl_pipe_msg =
- container_of(msg, struct bl_pipe_msg, msg);
+ struct nfs_net *nn = container_of(msg, struct nfs_net, bl_pipe_msg);
if (msg->errno >= 0)
return;
- wake_up(bl_pipe_msg->bl_wq);
+ complete(&nn->bl_done);
}
static const struct rpc_pipe_ops bl_upcall_ops = {
@@ -221,7 +235,7 @@ static int nfs4blocklayout_net_init(struct net *net)
int err;
mutex_init(&nn->bl_mutex);
- init_waitqueue_head(&nn->bl_wq);
+ init_completion(&nn->bl_done);
nn->bl_device_pipe = rpc_mkpipe_data(&bl_upcall_ops, 0);
if (IS_ERR(nn->bl_device_pipe))
return PTR_ERR(nn->bl_device_pipe);
diff --git a/fs/nfs/netns.h b/fs/nfs/netns.h
index 36658579100d..e1decff366d4 100644
--- a/fs/nfs/netns.h
+++ b/fs/nfs/netns.h
@@ -10,6 +10,8 @@
#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <linux/sunrpc/stats.h>
+#include <linux/sunrpc/rpc_pipe_fs.h>
+#include <linux/completion.h>
struct bl_dev_msg {
int32_t status;
@@ -21,8 +23,9 @@ struct nfs_netns_client;
struct nfs_net {
struct cache_detail *nfs_dns_resolve;
struct rpc_pipe *bl_device_pipe;
+ struct rpc_pipe_msg bl_pipe_msg;
struct bl_dev_msg bl_mount_reply;
- wait_queue_head_t bl_wq;
+ struct completion bl_done;
struct mutex bl_mutex;
struct list_head nfs_client_list;
struct list_head nfs_volume_list;
--
2.54.0
prev parent reply other threads:[~2026-08-31 0:38 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-31 0:38 [PATCH 0/5] Fix premature completion of rpc_pipefs upcalls Chuck Lever
2026-08-31 0:38 ` [PATCH 1/5] NFSD: Don't complete a cld upcall the daemon has not read Chuck Lever
2026-08-31 0:38 ` [PATCH 2/5] NFSD: Move the cld upcall message out of the caller's stack frame Chuck Lever
2026-08-31 0:38 ` [PATCH 3/5] NFSD: Complete a cld upcall when copying its reply fails Chuck Lever
2026-08-31 0:38 ` [PATCH 4/5] NFSD: Reject an oversized principal hash from nfsdcld Chuck Lever
2026-08-31 0:38 ` Chuck Lever [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260830-alemi-v1-5-463f80b9e9a8@kernel.org \
--to=cel@kernel.org \
--cc=Dai.Ngo@oracle.com \
--cc=anna@kernel.org \
--cc=bfields@fieldses.org \
--cc=jlayton@kernel.org \
--cc=linux-nfs@vger.kernel.org \
--cc=neil@brown.name \
--cc=okorniev@redhat.com \
--cc=smayhew@redhat.com \
--cc=tom@talpey.com \
--cc=trondmy@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).