mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
nvme-tcp: Validate the queue state under the queue lock in the address attribute
Reading the "address" sysfs attribute calls kernel_getsockname() on the admin queue socket, which can race with the queue teardown during connection error recovery: a reader can observe NVME_TCP_Q_LIVE set, then block on the queue lock while nvme_tcp_free_queue() releases the socket and clears queue->sock without holding the lock. Once the reader acquires the lock it dereferences the NULL socket pointer. Commit76d54bf20c("nvme-tcp: don't access released socket during error recovery") had the queue liveness check inside the lock. Commit782373ba27("nvme: tcp: avoid race between queue_lock lock and destroy") moved it back out because the lock itself is destroyed in nvme_tcp_free_queue(), so a reader arriving late trips mutex_destroy(). Check the queue liveness inside the queue lock, and clear queue->sock under the same lock. The teardown clears NVME_TCP_Q_LIVE under the lock well before the socket is released, so a reader either sees the bit cleared or a socket that cannot be released while it holds the lock. To also close the lock lifetime race, the queue locks are now owned by the controller: they are initialized with the queue array and only destroyed when the controller is freed, which cannot happen while a sysfs attribute read is still in progress. The queue count is computed by the NVME_TCP_OPT_NR_QUEUES() macro from the original connect options for both initialization and destruction, as reconnects may shrink ctrl->queue_count. Fixes:782373ba27("nvme: tcp: avoid race between queue_lock lock and destroy") Cc: stable@vger.kernel.org Signed-off-by: Guixin Liu <kanie@linux.alibaba.com> Reviewed-by: Nilay Shroff <nilay@linux.ibm.com> Signed-off-by: Keith Busch <kbusch@kernel.org>
This commit is contained in:
parent
5db358f3a7
commit
2eeeaa9c1f
|
|
@ -103,6 +103,9 @@ enum nvme_tcp_queue_flags {
|
|||
NVME_TCP_Q_IO_CPU_SET = 3,
|
||||
};
|
||||
|
||||
#define NVME_TCP_OPT_NR_QUEUES(opts) ((opts)->nr_io_queues + \
|
||||
(opts)->nr_write_queues + (opts)->nr_poll_queues + 1)
|
||||
|
||||
enum nvme_tcp_recv_state {
|
||||
NVME_TCP_RECV_PDU = 0,
|
||||
NVME_TCP_RECV_DATA,
|
||||
|
|
@ -1549,14 +1552,21 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid)
|
|||
* nvme_reset_ctrl_work(), queue teardown is immediately followed by
|
||||
* re-allocation. fput() defers socket cleanup to delayed_fput_work
|
||||
* in workqueue context, which can race with new queue setup.
|
||||
*
|
||||
* Clear queue->sock under the queue lock so that a sysfs reader in
|
||||
* nvme_tcp_get_address() either observes the NVME_TCP_Q_LIVE bit
|
||||
* cleared or a still valid socket. The queue lock itself is owned
|
||||
* by the controller and only destroyed when the controller is
|
||||
* freed, so readers blocked on it cannot race its teardown.
|
||||
*/
|
||||
mutex_lock(&queue->queue_lock);
|
||||
__fput_sync(queue->sock->file);
|
||||
queue->sock = NULL;
|
||||
mutex_unlock(&queue->queue_lock);
|
||||
memalloc_noio_restore(noio_flag);
|
||||
|
||||
kfree(queue->pdu);
|
||||
mutex_destroy(&queue->send_mutex);
|
||||
mutex_destroy(&queue->queue_lock);
|
||||
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
lockdep_unregister_key(&queue->nvme_tcp_sk_key);
|
||||
|
|
@ -1992,7 +2002,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid,
|
|||
int ret, rcv_pdu_size;
|
||||
struct file *sock_file;
|
||||
|
||||
mutex_init(&queue->queue_lock);
|
||||
queue->ctrl = ctrl;
|
||||
init_llist_head(&queue->req_list);
|
||||
INIT_LIST_HEAD(&queue->send_list);
|
||||
|
|
@ -2136,7 +2145,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid,
|
|||
#endif
|
||||
err_destroy_mutex:
|
||||
mutex_destroy(&queue->send_mutex);
|
||||
mutex_destroy(&queue->queue_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2823,6 +2831,9 @@ static void nvme_tcp_stop_ctrl(struct nvme_ctrl *ctrl)
|
|||
static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl)
|
||||
{
|
||||
struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(nctrl);
|
||||
/* the same count used to size and initialize the queue array */
|
||||
unsigned int nr_queues = NVME_TCP_OPT_NR_QUEUES(nctrl->opts);
|
||||
unsigned int i;
|
||||
|
||||
if (list_empty_careful(&ctrl->list))
|
||||
goto free_ctrl;
|
||||
|
|
@ -2834,6 +2845,8 @@ static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl)
|
|||
nvmf_free_options(nctrl->opts);
|
||||
free_ctrl:
|
||||
put_net(ctrl->net);
|
||||
for (i = 0; i < nr_queues; i++)
|
||||
mutex_destroy(&ctrl->queues[i].queue_lock);
|
||||
kfree(ctrl->queues);
|
||||
kfree(ctrl);
|
||||
}
|
||||
|
|
@ -3086,17 +3099,18 @@ static int nvme_tcp_get_address(struct nvme_ctrl *ctrl, char *buf, int size)
|
|||
|
||||
len = nvmf_get_address(ctrl, buf, size);
|
||||
|
||||
if (!test_bit(NVME_TCP_Q_LIVE, &queue->flags))
|
||||
return len;
|
||||
|
||||
mutex_lock(&queue->queue_lock);
|
||||
|
||||
ret = kernel_getsockname(queue->sock, (struct sockaddr *)&src_addr);
|
||||
if (ret > 0) {
|
||||
if (len > 0)
|
||||
len--; /* strip trailing newline */
|
||||
len += scnprintf(buf + len, size - len, "%ssrc_addr=%pISc\n",
|
||||
(len) ? "," : "", &src_addr);
|
||||
if (test_bit(NVME_TCP_Q_LIVE, &queue->flags)) {
|
||||
ret = kernel_getsockname(queue->sock,
|
||||
(struct sockaddr *)&src_addr);
|
||||
if (ret > 0) {
|
||||
if (len > 0)
|
||||
len--; /* strip trailing newline */
|
||||
len += scnprintf(buf + len, size - len,
|
||||
"%ssrc_addr=%pISc\n",
|
||||
(len) ? "," : "", &src_addr);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&queue->queue_lock);
|
||||
|
|
@ -3173,8 +3187,7 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev,
|
|||
*/
|
||||
context_unsafe(INIT_LIST_HEAD(&ctrl->list));
|
||||
ctrl->ctrl.opts = opts;
|
||||
ctrl->ctrl.queue_count = opts->nr_io_queues + opts->nr_write_queues +
|
||||
opts->nr_poll_queues + 1;
|
||||
ctrl->ctrl.queue_count = NVME_TCP_OPT_NR_QUEUES(opts);
|
||||
ctrl->ctrl.sqsize = opts->queue_size - 1;
|
||||
ctrl->ctrl.kato = opts->kato;
|
||||
|
||||
|
|
@ -3237,14 +3250,18 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev,
|
|||
* its requests already allocates the PDUs from the page frag caches of
|
||||
* the queues, sized by their header digest setting, so set up the
|
||||
* locks and hdr_digest here.
|
||||
*
|
||||
* The queue locks are owned by the controller, not the queues:
|
||||
* they outlive the per-queue connect/disconnect cycles so that
|
||||
* sysfs readers can never race their destruction.
|
||||
*/
|
||||
for (i = 0; i < ctrl->ctrl.queue_count; i++) {
|
||||
mutex_init(&ctrl->queues[i].pf_cache_lock);
|
||||
ctrl->queues[i].hdr_digest = opts->hdr_digest;
|
||||
mutex_init(&ctrl->queues[i].queue_lock);
|
||||
}
|
||||
|
||||
ctrl->net = get_net(current->nsproxy->net_ns);
|
||||
|
||||
ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_tcp_ctrl_ops, 0);
|
||||
if (ret)
|
||||
goto out_kfree_queues;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user