From nobody Tue Dec 16 21:28:57 2025 Received: from mail-pl1-f172.google.com (mail-pl1-f172.google.com [209.85.214.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0CFB32153D3 for ; Tue, 2 Dec 2025 01:35:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764639338; cv=none; b=Phbs5tV+WCvwl83E/uJLvQPzhWyj/Q9OCll29+o9pQ8yvqWEpvoK2wIWQma1xj7CxOCiIxvDfc4wG42yGVG8+HQ+QHFMK1Mz2b/2vKy5k6GvCKz9eGWyAnCYWgV/nBF6VKUGABpcP4KjNE6QbBEZy1zyq313HIKqrA7G4N6MboQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764639338; c=relaxed/simple; bh=aX4b/dtOJbU7+H1rQfMiZQtLMAdUZ1mamd1Fipz5Mhc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OIFZ1qhaZz1ACYQSxm0SurnFk8vpxgmRH0Nrw8mtH4hDiJBVYBU9o5byvj91OXDsv+1IQP1Y8zZcMS8gyKkjujCDn1Id9ESGafcoJbOj2XAwBKG8q7CT0b3QryNaYn+DRndXxc0Q8ttkoL+eoVKHkT6PhxPZOjPgk3uh5y7eVGI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=QVQJ0xCV; arc=none smtp.client-ip=209.85.214.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="QVQJ0xCV" Received: by mail-pl1-f172.google.com with SMTP id d9443c01a7336-2955623e6faso53872485ad.1 for ; Mon, 01 Dec 2025 17:35:35 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1764639335; x=1765244135; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=P/I0auzUVkVcxbH70BIVYr6yUr92WLMw+xrjecYcacA=; b=QVQJ0xCVxeIp4RsWB3gy57tU3EvfGIUOpHBKVZE9Uujn4wc2Lx34NMCm166iFJV04R hGNTWmYqpuQG0KfJ4sTfRw22jPJyONeMvbmldMn2LUqI5RVSHKs0HU50Gq4lMbHejlp0 zMIf8znfHfFwRtjLxZFM0Oxm7ajZUpA9DOAFEiT7fRDHtB9m9+bJw+YhitK2mbN5pMsb x8bd449toAq7D13nCbPirUHwmRiY7RKgjVC9u2eYWzgNafN13gvu9/e/UIap5ecFn2NG grPF3Ob973ZB1S0bPPIe5Z+GzAlVzPqr8/9+jmAKJ0dow/KdYGWMKC7EtyBk/dOtH8qv 0GkA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1764639335; x=1765244135; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=P/I0auzUVkVcxbH70BIVYr6yUr92WLMw+xrjecYcacA=; b=n3Ok2796k5o0pZveYrMhiccJQSM9H/ujMRp14C5Y7pNQRuXqp9dokuJkjt0Q4edTkh TT28yHTwKEEdH5Zjrf9Im9QFAbKZ0rPer9+lU9ubUbQjFm9JqhHgrbuHk/zSMeN+hjOC mcFRhUZarMTWEMc+AOyw+Dmj0kHRu1F40h+W3Ih+C6aDUEcIQa3N/E96CytEQSO4+Q24 +MmZCs10LQp/dkSMaS3MRzy121/QuJHakv//PQGJi+jr4lMFXr/MlDB+MLJ95maTpUfB lK432cMTzjLoAPcTS9VKYrGWtrFh2vnhiezBKMln74rOLGsx4pQCjuSmF2Bs0ChuIKQd ye7Q== X-Forwarded-Encrypted: i=1; AJvYcCVxokyUCEMmRkB/ihzNvcRx/CyJXeaTjf6tchVNmoWvX+6fjQMmh36yNLBdMhsCBle+XK9RHkdQ1usC0LE=@vger.kernel.org X-Gm-Message-State: AOJu0YwAIXiGS54vBYzX6/YBv9Mv+/txb142rY2bBLBO4vYpy2sCVM5F 1H3PzJMELMDFKUYe7Woalj9lKH0ewKMNFGBxBvALz6FIxbAd5NqcA0C8 X-Gm-Gg: ASbGncsTAErQKiDE7sq6Z6MDScBgTYM5DKz0aMsm6kawDLy1jUDDk8JMy53Yp6U5ttu VnRUkswRnUdPtEFZaSy8rIn3a8epmaPBsNA7NaDFSDc8zSJxL43cQhyCwIP8rY0DZ+tScl9sHNJ A59mLTHfMsr7KpKPETOagfGZqv7gqLeYRwEAPlVWqRmmKyDpN5Xwxa/NvNzqorwOLRhy0nt+m7I R3cEZi0KPzD8jHzDK4cceyLfUqxE0qvrD+7ATZJT8DalfDqtNSo22lPTf/L6CmobswFzzhLlmNb YEsrYowiLh1mfhOVma/UxMItj+3Pu1Q+g4bG9Bhfp0r1+m+Ipl4+2zr8totRpcAoaJvX6zIk67n irimgkO88pgNA7BO8mqBVmSSjl2/ayHmdyR69a+KJOcg4TpEWFh9s6H5559ZYZjc9CROcxiOmjP pi7nmsZOcRT2PNlL+kRBC0qCRBnzCaFvpQoAhwOJE+TLjzCoLyR2VhOy4dz82B/BPYckywzv+Qu fEmeA0zmev915gAkL8= X-Google-Smtp-Source: AGHT+IGQK/nb9FASk2chWpg9Ni/OJR5AgPXn3/ZK8ZXqfQ8nOYrkMVsl+JxFYgH88N7Fd3k6akiHDQ== X-Received: by 2002:a17:903:1670:b0:295:3ad7:948a with SMTP id d9443c01a7336-29baaf9a961mr298440265ad.16.1764639335372; Mon, 01 Dec 2025 17:35:35 -0800 (PST) Received: from toolbx.alistair23.me (2403-580b-97e8-0-82ce-f179-8a79-69f4.ip6.aussiebb.net. [2403:580b:97e8:0:82ce:f179:8a79:69f4]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-29bceb54563sm132378575ad.89.2025.12.01.17.35.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 01 Dec 2025 17:35:34 -0800 (PST) From: alistair23@gmail.com X-Google-Original-From: alistair.francis@wdc.com To: chuck.lever@oracle.com, hare@kernel.org, kernel-tls-handshake@lists.linux.dev, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-nvme@lists.infradead.org, linux-nfs@vger.kernel.org Cc: kbusch@kernel.org, axboe@kernel.dk, hch@lst.de, sagi@grimberg.me, kch@nvidia.com, hare@suse.de, alistair23@gmail.com, Alistair Francis Subject: [PATCH v6 5/5] nvmet-tcp: Support KeyUpdate Date: Tue, 2 Dec 2025 11:34:29 +1000 Message-ID: <20251202013429.1199659-6-alistair.francis@wdc.com> X-Mailer: git-send-email 2.51.1 In-Reply-To: <20251202013429.1199659-1-alistair.francis@wdc.com> References: <20251202013429.1199659-1-alistair.francis@wdc.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alistair Francis If the nvmet_tcp_try_recv() function return EKEYEXPIRED or if we receive a KeyUpdate handshake type then the underlying TLS keys need to be updated. If the NVMe Host (TLS client) initiates a KeyUpdate this patch will allow the NVMe layer to process the KeyUpdate request and forward the request to userspace. Userspace must then update the key to keep the connection alive. This patch allows us to handle the NVMe host sending a KeyUpdate request without aborting the connection. At this time we don't support initiating a KeyUpdate. Link: https://datatracker.ietf.org/doc/html/rfc8446#section-4.6.3 Signed-off-by: Alistair Francis Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig --- v6: - Simplify the nvmet_tls_key_expired() check v5: - No change v4: - Restructure code to avoid #ifdefs and forward declarations - Use a helper function for checking -EKEYEXPIRED - Remove all support for initiating KeyUpdate - Use helper function for restoring callbacks v3: - Use a write lock for sk_user_data - Fix build with CONFIG_NVME_TARGET_TCP_TLS disabled - Remove unused variable v2: - Use a helper function for KeyUpdates - Ensure keep alive timer is stopped - Wait for TLS KeyUpdate to complete drivers/nvme/target/tcp.c | 200 ++++++++++++++++++++++++++------------ 1 file changed, 139 insertions(+), 61 deletions(-) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 818efdeccef1..0458a9691cbc 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -175,6 +175,7 @@ struct nvmet_tcp_queue { =20 /* TLS state */ key_serial_t tls_pskid; + key_serial_t handshake_session_id; struct delayed_work tls_handshake_tmo_work; =20 unsigned long poll_end; @@ -186,6 +187,8 @@ struct nvmet_tcp_queue { struct sockaddr_storage sockaddr_peer; struct work_struct release_work; =20 + struct completion tls_complete; + int idx; struct list_head queue_list; =20 @@ -214,6 +217,10 @@ static struct workqueue_struct *nvmet_tcp_wq; static const struct nvmet_fabrics_ops nvmet_tcp_ops; static void nvmet_tcp_free_cmd(struct nvmet_tcp_cmd *c); static void nvmet_tcp_free_cmd_buffers(struct nvmet_tcp_cmd *cmd); +#ifdef CONFIG_NVME_TARGET_TCP_TLS +static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue, + enum handshake_key_update_type keyupdate); +#endif =20 static inline u16 nvmet_tcp_cmd_tag(struct nvmet_tcp_queue *queue, struct nvmet_tcp_cmd *cmd) @@ -832,6 +839,20 @@ static int nvmet_tcp_try_send_one(struct nvmet_tcp_que= ue *queue, return 1; } =20 +#ifdef CONFIG_NVME_TARGET_TCP_TLS +static bool nvmet_tls_key_expired(struct nvmet_tcp_queue *queue, int ret) +{ + return ret =3D=3D -EKEYEXPIRED && + queue->state !=3D NVMET_TCP_Q_DISCONNECTING && + queue->state !=3D NVMET_TCP_Q_TLS_HANDSHAKE; +} +#else +static bool nvmet_tls_key_expired(struct nvmet_tcp_queue *queue, int ret) +{ + return false; +} +#endif + static int nvmet_tcp_try_send(struct nvmet_tcp_queue *queue, int budget, int *sends) { @@ -1106,6 +1127,103 @@ static inline bool nvmet_tcp_pdu_valid(u8 type) return false; } =20 +static void nvmet_tcp_release_queue(struct kref *kref) +{ + struct nvmet_tcp_queue *queue =3D + container_of(kref, struct nvmet_tcp_queue, kref); + + WARN_ON(queue->state !=3D NVMET_TCP_Q_DISCONNECTING); + queue_work(nvmet_wq, &queue->release_work); +} + +static void nvmet_tcp_schedule_release_queue(struct nvmet_tcp_queue *queue) +{ + spin_lock_bh(&queue->state_lock); + if (queue->state =3D=3D NVMET_TCP_Q_TLS_HANDSHAKE) { + /* Socket closed during handshake */ + tls_handshake_cancel(queue->sock->sk); + } + if (queue->state !=3D NVMET_TCP_Q_DISCONNECTING) { + queue->state =3D NVMET_TCP_Q_DISCONNECTING; + kref_put(&queue->kref, nvmet_tcp_release_queue); + } + spin_unlock_bh(&queue->state_lock); +} + +static void nvmet_tcp_restore_socket_callbacks(struct nvmet_tcp_queue *que= ue) +{ + struct socket *sock =3D queue->sock; + + if (!queue->state_change) + return; + + write_lock_bh(&sock->sk->sk_callback_lock); + sock->sk->sk_data_ready =3D queue->data_ready; + sock->sk->sk_state_change =3D queue->state_change; + sock->sk->sk_write_space =3D queue->write_space; + sock->sk->sk_user_data =3D NULL; + write_unlock_bh(&sock->sk->sk_callback_lock); +} + +#ifdef CONFIG_NVME_TARGET_TCP_TLS +static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w) +{ + struct nvmet_tcp_queue *queue =3D container_of(to_delayed_work(w), + struct nvmet_tcp_queue, tls_handshake_tmo_work); + + pr_warn("queue %d: TLS handshake timeout\n", queue->idx); + /* + * If tls_handshake_cancel() fails we've lost the race with + * nvmet_tcp_tls_handshake_done() */ + if (!tls_handshake_cancel(queue->sock->sk)) + return; + spin_lock_bh(&queue->state_lock); + if (WARN_ON(queue->state !=3D NVMET_TCP_Q_TLS_HANDSHAKE)) { + spin_unlock_bh(&queue->state_lock); + return; + } + queue->state =3D NVMET_TCP_Q_FAILED; + spin_unlock_bh(&queue->state_lock); + nvmet_tcp_schedule_release_queue(queue); + kref_put(&queue->kref, nvmet_tcp_release_queue); +} + +static int update_tls_keys(struct nvmet_tcp_queue *queue) +{ + int ret; + + cancel_work(&queue->io_work); + queue->state =3D NVMET_TCP_Q_TLS_HANDSHAKE; + + nvmet_tcp_restore_socket_callbacks(queue); + + INIT_DELAYED_WORK(&queue->tls_handshake_tmo_work, + nvmet_tcp_tls_handshake_timeout); + + ret =3D nvmet_tcp_tls_handshake(queue, HANDSHAKE_KEY_UPDATE_TYPE_RECEIVED= ); + + if (ret < 0) + return ret; + + ret =3D wait_for_completion_interruptible_timeout(&queue->tls_complete, + 10 * HZ); + + if (ret <=3D 0) { + tls_handshake_cancel(queue->sock->sk); + return ret; + } + + queue->state =3D NVMET_TCP_Q_LIVE; + + return 0; +} +#else +static int update_tls_keys(struct nvmet_tcp_queue *queue) +{ + return -EPFNOSUPPORT; +} +#endif + static int nvmet_tcp_tls_record_ok(struct nvmet_tcp_queue *queue, struct msghdr *msg, char *cbuf) { @@ -1131,6 +1249,9 @@ static int nvmet_tcp_tls_record_ok(struct nvmet_tcp_q= ueue *queue, ret =3D -EAGAIN; } break; + case TLS_RECORD_TYPE_HANDSHAKE: + ret =3D -EAGAIN; + break; default: /* discard this record type */ pr_err("queue %d: TLS record %d unhandled\n", @@ -1340,6 +1461,8 @@ static int nvmet_tcp_try_recv(struct nvmet_tcp_queue = *queue, for (i =3D 0; i < budget; i++) { ret =3D nvmet_tcp_try_recv_one(queue); if (unlikely(ret < 0)) { + if (nvmet_tls_key_expired(queue, ret)) + goto done; nvmet_tcp_socket_error(queue, ret); goto done; } else if (ret =3D=3D 0) { @@ -1351,29 +1474,6 @@ static int nvmet_tcp_try_recv(struct nvmet_tcp_queue= *queue, return ret; } =20 -static void nvmet_tcp_release_queue(struct kref *kref) -{ - struct nvmet_tcp_queue *queue =3D - container_of(kref, struct nvmet_tcp_queue, kref); - - WARN_ON(queue->state !=3D NVMET_TCP_Q_DISCONNECTING); - queue_work(nvmet_wq, &queue->release_work); -} - -static void nvmet_tcp_schedule_release_queue(struct nvmet_tcp_queue *queue) -{ - spin_lock_bh(&queue->state_lock); - if (queue->state =3D=3D NVMET_TCP_Q_TLS_HANDSHAKE) { - /* Socket closed during handshake */ - tls_handshake_cancel(queue->sock->sk); - } - if (queue->state !=3D NVMET_TCP_Q_DISCONNECTING) { - queue->state =3D NVMET_TCP_Q_DISCONNECTING; - kref_put(&queue->kref, nvmet_tcp_release_queue); - } - spin_unlock_bh(&queue->state_lock); -} - static inline void nvmet_tcp_arm_queue_deadline(struct nvmet_tcp_queue *qu= eue) { queue->poll_end =3D jiffies + usecs_to_jiffies(idle_poll_period_usecs); @@ -1404,8 +1504,12 @@ static void nvmet_tcp_io_work(struct work_struct *w) ret =3D nvmet_tcp_try_recv(queue, NVMET_TCP_RECV_BUDGET, &ops); if (ret > 0) pending =3D true; - else if (ret < 0) + else if (ret < 0) { + if (ret =3D=3D -EKEYEXPIRED) + break; + return; + } =20 ret =3D nvmet_tcp_try_send(queue, NVMET_TCP_SEND_BUDGET, &ops); if (ret > 0) @@ -1415,6 +1519,11 @@ static void nvmet_tcp_io_work(struct work_struct *w) =20 } while (pending && ops < NVMET_TCP_IO_WORK_BUDGET); =20 + if (ret =3D=3D -EKEYEXPIRED) { + update_tls_keys(queue); + pending =3D true; + } + /* * Requeue the worker if idle deadline period is in progress or any * ops activity was recorded during the do-while loop above. @@ -1517,21 +1626,6 @@ static void nvmet_tcp_free_cmds(struct nvmet_tcp_que= ue *queue) kfree(cmds); } =20 -static void nvmet_tcp_restore_socket_callbacks(struct nvmet_tcp_queue *que= ue) -{ - struct socket *sock =3D queue->sock; - - if (!queue->state_change) - return; - - write_lock_bh(&sock->sk->sk_callback_lock); - sock->sk->sk_data_ready =3D queue->data_ready; - sock->sk->sk_state_change =3D queue->state_change; - sock->sk->sk_write_space =3D queue->write_space; - sock->sk->sk_user_data =3D NULL; - write_unlock_bh(&sock->sk->sk_callback_lock); -} - static void nvmet_tcp_uninit_data_in_cmds(struct nvmet_tcp_queue *queue) { struct nvmet_tcp_cmd *cmd =3D queue->cmds; @@ -1794,6 +1888,7 @@ static void nvmet_tcp_tls_handshake_done(void *data, = int status, } if (!status) { queue->tls_pskid =3D peerid; + queue->handshake_session_id =3D handshake_session_id; queue->state =3D NVMET_TCP_Q_CONNECTING; } else queue->state =3D NVMET_TCP_Q_FAILED; @@ -1809,28 +1904,7 @@ static void nvmet_tcp_tls_handshake_done(void *data,= int status, else nvmet_tcp_set_queue_sock(queue); kref_put(&queue->kref, nvmet_tcp_release_queue); -} - -static void nvmet_tcp_tls_handshake_timeout(struct work_struct *w) -{ - struct nvmet_tcp_queue *queue =3D container_of(to_delayed_work(w), - struct nvmet_tcp_queue, tls_handshake_tmo_work); - - pr_warn("queue %d: TLS handshake timeout\n", queue->idx); - /* - * If tls_handshake_cancel() fails we've lost the race with - * nvmet_tcp_tls_handshake_done() */ - if (!tls_handshake_cancel(queue->sock->sk)) - return; - spin_lock_bh(&queue->state_lock); - if (WARN_ON(queue->state !=3D NVMET_TCP_Q_TLS_HANDSHAKE)) { - spin_unlock_bh(&queue->state_lock); - return; - } - queue->state =3D NVMET_TCP_Q_FAILED; - spin_unlock_bh(&queue->state_lock); - nvmet_tcp_schedule_release_queue(queue); - kref_put(&queue->kref, nvmet_tcp_release_queue); + complete(&queue->tls_complete); } =20 static int nvmet_tcp_tls_handshake(struct nvmet_tcp_queue *queue, @@ -1852,11 +1926,15 @@ static int nvmet_tcp_tls_handshake(struct nvmet_tcp= _queue *queue, args.ta_data =3D queue; args.ta_keyring =3D key_serial(queue->port->nport->keyring); args.ta_timeout_ms =3D tls_handshake_timeout * 1000; + args.ta_handshake_session_id =3D queue->handshake_session_id; + + init_completion(&queue->tls_complete); =20 if (keyupdate =3D=3D HANDSHAKE_KEY_UPDATE_TYPE_UNSPEC) ret =3D tls_server_hello_psk(&args, GFP_KERNEL); else ret =3D tls_server_keyupdate_psk(&args, GFP_KERNEL, keyupdate); + if (ret) { kref_put(&queue->kref, nvmet_tcp_release_queue); pr_err("failed to start TLS, err=3D%d\n", ret); --=20 2.51.1