[PATCH] nbd: fix race between nbd_pending_cmd_work and socket teardown
From: Weisson
Date: Fri Jul 31 2026 - 04:30:00 EST
nbd_pending_cmd_work() dereferences nsock->pending without any
synchronization. If nbd_mark_nsock_dead() clears nsock->pending
concurrently, the worker hits a NULL pointer dereference:
BUG: kernel NULL pointer dereference, address: 00000000000000f8
Workqueue: events nbd_pending_cmd_work [nbd]
RIP: 0010:nbd_pending_cmd_work+0x22/0x110 [nbd]
The worker reads nsock->pending (NULL) and immediately passes it to
blk_mq_rq_to_pdu(), which computes (req + 1). With req == NULL this
accesses address 0 + sizeof(struct request) = 0xf8, triggering the
page fault.
The race sequence is:
1. nbd_send_cmd() is interrupted with sent > 0, calls
nbd_sched_pending_work() which sets nsock->pending = req and
calls schedule_work(). The originating thread returns
BLK_STS_OK immediately -- it no longer owns the request.
2. Before the kworker picks up the work item, userspace issues a
disconnect (nbd-client -d). sock_shutdown() takes tx_lock and
calls nbd_mark_nsock_dead() which sets nsock->pending = NULL.
3. The kworker finally runs nbd_pending_cmd_work(), reads the now-
NULL nsock->pending, and passes it to blk_mq_rq_to_pdu() which
dereferences NULL + 0xf8.
Fix this by establishing single ownership: once nbd_sched_pending_work()
schedules the worker, only the worker may clear nsock->pending, release
the config_refs, and terminate the request. Socket teardown
(nbd_mark_nsock_dead) only marks the connection dead but does not touch
the pending request owned by the worker.
The worker checks nsock->dead after each send attempt, and if set,
completes the request with BLK_STS_IOERR. On deadline expiry, the
worker also marks the socket dead since the TCP stream contains an
incomplete NBD message and cannot be reused.
Additionally, nbd_reconnect_socket() now skips nsock slots that still
have a pending request, preventing a new TCP connection from inheriting
stale partial-send state.
Signed-off-by: Weisson <hanxiaobupt@xxxxxxx>
---
drivers/block/nbd.c | 34 +++++++++++++++++++++++++++++++---
1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c
index 8f10762e90ef..7a462a626306 100644
--- a/drivers/block/nbd.c
+++ b/drivers/block/nbd.c
@@ -327,8 +327,8 @@ static void nbd_mark_nsock_dead(struct nbd_device *nbd, struct nbd_sock *nsock,
}
}
nsock->dead = true;
- nsock->pending = NULL;
- nsock->sent = 0;
+ if (!nsock->pending)
+ nsock->sent = 0;
}
static int nbd_set_size(struct nbd_device *nbd, loff_t bytesize, loff_t blksize)
@@ -793,8 +793,10 @@ static blk_status_t nbd_send_cmd(struct nbd_device *nbd, struct nbd_cmd *cmd,
*
* We must run from pending work function.
* */
- if (test_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags))
+ if (test_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags)) {
+ nbd_mark_nsock_dead(nbd, nsock, 1);
return BLK_STS_OK;
+ }
/* retry on a different socket */
dev_err_ratelimited(disk_to_dev(nbd->disk),
@@ -826,8 +828,18 @@ static void nbd_pending_cmd_work(struct work_struct *work)
if (!nsock->pending)
break;
+ if (nsock->dead)
+ goto dead;
+
/* don't bother timeout handler for partial sending */
if (READ_ONCE(jiffies) + msecs_to_jiffies(wait_ms) >= deadline) {
+ /*
+ * The socket contains a partially transmitted request
+ * and cannot be reused for another NBD request.
+ */
+ nbd_mark_nsock_dead(nbd, nsock, 1);
+ nsock->pending = NULL;
+ nsock->sent = 0;
cmd->status = BLK_STS_IOERR;
blk_mq_complete_request(req);
break;
@@ -840,6 +852,18 @@ static void nbd_pending_cmd_work(struct work_struct *work)
out:
mutex_unlock(&cmd->lock);
nbd_config_put(nbd);
+ return;
+
+ /* Complete the request here; nbd_clear_req() will not handle it. */
+dead:
+ nsock->pending = NULL;
+ nsock->sent = 0;
+ mutex_unlock(&nsock->tx_lock);
+ clear_bit(NBD_CMD_PARTIAL_SEND, &cmd->flags);
+ cmd->status = BLK_STS_IOERR;
+ mutex_unlock(&cmd->lock);
+ blk_mq_complete_request(req);
+ nbd_config_put(nbd);
}
static int nbd_read_reply(struct nbd_device *nbd, struct socket *sock,
@@ -1376,6 +1400,10 @@ static int nbd_reconnect_socket(struct nbd_device *nbd, unsigned long arg)
mutex_unlock(&nsock->tx_lock);
continue;
}
+ if (nsock->pending) {
+ mutex_unlock(&nsock->tx_lock);
+ continue;
+ }
sk_set_memalloc(sock->sk);
if (nbd->tag_set.timeout)
sock->sk->sk_sndtimeo = nbd->tag_set.timeout;
--
2.47.3