diff mbox series

[8/9] nvmet-tcp: support secure channel concatenation

Message ID 20241018063343.39798-9-hare@kernel.org
State New
Headers show
Series nvme: implement secure concatenaion | expand

Commit Message

Hannes Reinecke Oct. 18, 2024, 6:33 a.m. UTC
Evaluate the SC_C flag during DH-CHAP-HMAC negotiation and insert
the generated PSK once negotiation has finished.

Signed-off-by: Hannes Reinecke <hare@kernel.org>
---
 drivers/nvme/target/auth.c             | 72 +++++++++++++++++++++++++-
 drivers/nvme/target/fabrics-cmd-auth.c | 49 +++++++++++++++---
 drivers/nvme/target/fabrics-cmd.c      | 33 +++++++++---
 drivers/nvme/target/nvmet.h            | 38 +++++++++++---
 drivers/nvme/target/tcp.c              | 23 +++++++-
 5 files changed, 192 insertions(+), 23 deletions(-)

Comments

Sagi Grimberg Oct. 20, 2024, 9:13 p.m. UTC | #1
On 18/10/2024 9:33, Hannes Reinecke wrote:
> Evaluate the SC_C flag during DH-CHAP-HMAC negotiation and insert
> the generated PSK once negotiation has finished.
>
> Signed-off-by: Hannes Reinecke <hare@kernel.org>
> ---
>   drivers/nvme/target/auth.c             | 72 +++++++++++++++++++++++++-
>   drivers/nvme/target/fabrics-cmd-auth.c | 49 +++++++++++++++---
>   drivers/nvme/target/fabrics-cmd.c      | 33 +++++++++---
>   drivers/nvme/target/nvmet.h            | 38 +++++++++++---
>   drivers/nvme/target/tcp.c              | 23 +++++++-
>   5 files changed, 192 insertions(+), 23 deletions(-)
>
> diff --git a/drivers/nvme/target/auth.c b/drivers/nvme/target/auth.c
> index 7897d02c681d..7470ac020db6 100644
> --- a/drivers/nvme/target/auth.c
> +++ b/drivers/nvme/target/auth.c
> @@ -15,6 +15,7 @@
>   #include <linux/ctype.h>
>   #include <linux/random.h>
>   #include <linux/nvme-auth.h>
> +#include <linux/nvme-keyring.h>
>   #include <asm/unaligned.h>
>   
>   #include "nvmet.h"
> @@ -138,7 +139,7 @@ int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, u8 dhgroup_id)
>   	return ret;
>   }
>   
> -u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
> +u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
>   {
>   	int ret = 0;
>   	struct nvmet_host_link *p;
> @@ -164,6 +165,11 @@ u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
>   		goto out_unlock;
>   	}
>   
> +	if (nvmet_queue_tls_keyid(req->sq)) {
> +		pr_debug("host %s tls enabled\n", ctrl->hostnqn);
> +		goto out_unlock;
> +	}
> +
>   	ret = nvmet_setup_dhgroup(ctrl, host->dhchap_dhgroup_id);
>   	if (ret < 0) {
>   		pr_warn("Failed to setup DH group");
> @@ -232,6 +238,9 @@ u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
>   void nvmet_auth_sq_free(struct nvmet_sq *sq)
>   {
>   	cancel_delayed_work(&sq->auth_expired_work);
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +	sq->tls_key = 0;
> +#endif
>   	kfree(sq->dhchap_c1);
>   	sq->dhchap_c1 = NULL;
>   	kfree(sq->dhchap_c2);
> @@ -260,6 +269,12 @@ void nvmet_destroy_auth(struct nvmet_ctrl *ctrl)
>   		nvme_auth_free_key(ctrl->ctrl_key);
>   		ctrl->ctrl_key = NULL;
>   	}
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +	if (ctrl->tls_key) {
> +		key_put(ctrl->tls_key);
> +		ctrl->tls_key = NULL;
> +	}
> +#endif
>   }
>   
>   bool nvmet_check_auth_status(struct nvmet_req *req)
> @@ -541,3 +556,58 @@ int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
>   
>   	return ret;
>   }
> +
> +void nvmet_auth_insert_psk(struct nvmet_sq *sq)
> +{
> +	int hash_len = nvme_auth_hmac_hash_len(sq->ctrl->shash_id);
> +	u8 *psk, *digest, *tls_psk;
> +	size_t psk_len;
> +	int ret;
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +	struct key *tls_key = NULL;
> +#endif
> +
> +	ret = nvme_auth_generate_psk(sq->ctrl->shash_id,
> +				     sq->dhchap_skey,
> +				     sq->dhchap_skey_len,
> +				     sq->dhchap_c1, sq->dhchap_c2,
> +				     hash_len, &psk, &psk_len);
> +	if (ret) {
> +		pr_warn("%s: ctrl %d qid %d failed to generate PSK, error %d\n",
> +			__func__, sq->ctrl->cntlid, sq->qid, ret);
> +		return;
> +	}
> +	ret = nvme_auth_generate_digest(sq->ctrl->shash_id, psk, psk_len,
> +					sq->ctrl->subsysnqn,
> +					sq->ctrl->hostnqn, &digest);
> +	if (ret) {
> +		pr_warn("%s: ctrl %d qid %d failed to generate digest, error %d\n",
> +			__func__, sq->ctrl->cntlid, sq->qid, ret);
> +		goto out_free_psk;
> +	}
> +	ret = nvme_auth_derive_tls_psk(sq->ctrl->shash_id, psk, psk_len,
> +				       digest, &tls_psk);
> +	if (ret) {
> +		pr_warn("%s: ctrl %d qid %d failed to derive TLS PSK, error %d\n",
> +			__func__, sq->ctrl->cntlid, sq->qid, ret);
> +		goto out_free_digest;
> +	}
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +	tls_key = nvme_tls_psk_refresh(NULL, sq->ctrl->hostnqn, sq->ctrl->subsysnqn,
> +				       sq->ctrl->shash_id, tls_psk, psk_len, digest);
> +	if (IS_ERR(tls_key)) {
> +		pr_warn("%s: ctrl %d qid %d failed to refresh key, error %ld\n",
> +			__func__, sq->ctrl->cntlid, sq->qid, PTR_ERR(tls_key));
> +		tls_key = NULL;
> +		kfree_sensitive(tls_psk);
> +	}
> +	if (sq->ctrl->tls_key)
> +		key_put(sq->ctrl->tls_key);
> +	sq->ctrl->tls_key = tls_key;
> +#endif
> +
> +out_free_digest:
> +	kfree_sensitive(digest);
> +out_free_psk:
> +	kfree_sensitive(psk);
> +}
> diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c
> index 3f2857c17d95..cf4b38c0e7bd 100644
> --- a/drivers/nvme/target/fabrics-cmd-auth.c
> +++ b/drivers/nvme/target/fabrics-cmd-auth.c
> @@ -43,8 +43,26 @@ static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
>   		 data->auth_protocol[0].dhchap.halen,
>   		 data->auth_protocol[0].dhchap.dhlen);
>   	req->sq->dhchap_tid = le16_to_cpu(data->t_id);
> -	if (data->sc_c)
> -		return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
> +	if (data->sc_c != NVME_AUTH_SECP_NOSC) {
> +		if (!IS_ENABLED(CONFIG_NVME_TARGET_TCP_TLS))
> +			return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
> +		/* Secure concatenation can only be enabled on the admin queue */
> +		if (req->sq->qid)
> +			return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
> +		switch (data->sc_c) {
> +		case NVME_AUTH_SECP_NEWTLSPSK:
> +			if (nvmet_queue_tls_keyid(req->sq))
> +				return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
> +			break;

fallthru instead?

> +		case NVME_AUTH_SECP_REPLACETLSPSK:
> +			if (!nvmet_queue_tls_keyid(req->sq))
> +				return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
> +			break;
> +		default:
> +			return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
> +		}
> +		ctrl->concat = true;
> +	}
>   
>   	if (data->napd != 1)
>   		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
> @@ -103,6 +121,13 @@ static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
>   			 nvme_auth_dhgroup_name(fallback_dhgid));
>   		ctrl->dh_gid = fallback_dhgid;
>   	}
> +	if (ctrl->dh_gid == NVME_AUTH_DHGROUP_NULL &&
> +	    ctrl->concat) {
> +		pr_debug("%s: ctrl %d qid %d: NULL DH group invalid "
> +			 "for secure channel concatenation\n", __func__,
> +			 ctrl->cntlid, req->sq->qid);
> +		return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
> +	}
>   	pr_debug("%s: ctrl %d qid %d: selected DH group %s (%d)\n",
>   		 __func__, ctrl->cntlid, req->sq->qid,
>   		 nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);
> @@ -154,6 +179,12 @@ static u8 nvmet_auth_reply(struct nvmet_req *req, void *d)
>   	kfree(response);
>   	pr_debug("%s: ctrl %d qid %d host authenticated\n",
>   		 __func__, ctrl->cntlid, req->sq->qid);
> +	if (!data->cvalid && ctrl->concat) {
> +		pr_debug("%s: ctrl %d qid %d invalid challenge\n",
> +			 __func__, ctrl->cntlid, req->sq->qid);
> +		return NVME_AUTH_DHCHAP_FAILURE_FAILED;
> +	}
> +	req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);
>   	if (data->cvalid) {
>   		req->sq->dhchap_c2 = kmemdup(data->rval + data->hl, data->hl,
>   					     GFP_KERNEL);
> @@ -163,11 +194,15 @@ static u8 nvmet_auth_reply(struct nvmet_req *req, void *d)
>   		pr_debug("%s: ctrl %d qid %d challenge %*ph\n",
>   			 __func__, ctrl->cntlid, req->sq->qid, data->hl,
>   			 req->sq->dhchap_c2);
> -	} else {
> +	}
> +	if (req->sq->dhchap_s2 == 0) {
> +		if (ctrl->concat)
> +			nvmet_auth_insert_psk(req->sq);
>   		req->sq->authenticated = true;
> +		kfree(req->sq->dhchap_c2);
>   		req->sq->dhchap_c2 = NULL;
> -	}
> -	req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);
> +	} else if (!data->cvalid)
> +		req->sq->authenticated = true;
>   
>   	return 0;
>   }
> @@ -241,7 +276,7 @@ void nvmet_execute_auth_send(struct nvmet_req *req)
>   			pr_debug("%s: ctrl %d qid %d reset negotiation\n",
>   				 __func__, ctrl->cntlid, req->sq->qid);
>   			if (!req->sq->qid) {
> -				dhchap_status = nvmet_setup_auth(ctrl);
> +				dhchap_status = nvmet_setup_auth(ctrl, req);
>   				if (dhchap_status) {
>   					pr_err("ctrl %d qid 0 failed to setup re-authentication\n",
>   					       ctrl->cntlid);
> @@ -298,6 +333,8 @@ void nvmet_execute_auth_send(struct nvmet_req *req)
>   		}
>   		goto done_kfree;
>   	case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:
> +		if (ctrl->concat)
> +			nvmet_auth_insert_psk(req->sq);
>   		req->sq->authenticated = true;
>   		pr_debug("%s: ctrl %d qid %d ctrl authenticated\n",
>   			 __func__, ctrl->cntlid, req->sq->qid);
> diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/fabrics-cmd.c
> index c4b2eddd5666..9a1256deee51 100644
> --- a/drivers/nvme/target/fabrics-cmd.c
> +++ b/drivers/nvme/target/fabrics-cmd.c
> @@ -199,10 +199,26 @@ static u16 nvmet_install_queue(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
>   	return ret;
>   }
>   
> -static u32 nvmet_connect_result(struct nvmet_ctrl *ctrl)
> +static u32 nvmet_connect_result(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
>   {
> +	bool needs_auth = nvmet_has_auth(ctrl, req);
> +	key_serial_t keyid = nvmet_queue_tls_keyid(req->sq);
> +
> +	/* Do not authenticate I/O queues for secure concatenation */
> +	if (ctrl->concat && req->sq->qid)
> +		needs_auth = false;
> +
> +	if (keyid)
> +		pr_debug("%s: ctrl %d qid %d should %sauthenticate, tls psk %08x\n",
> +			 __func__, ctrl->cntlid, req->sq->qid,
> +			 needs_auth ? "" : "not ", keyid);
> +	else
> +		pr_debug("%s: ctrl %d qid %d should %sauthenticate%s\n",
> +			 __func__, ctrl->cntlid, req->sq->qid,
> +			 needs_auth ? "" : "not ",
> +			 ctrl->concat ? ", secure concatenation" : "");
>   	return (u32)ctrl->cntlid |
> -		(nvmet_has_auth(ctrl) ? NVME_CONNECT_AUTHREQ_ATR : 0);
> +		(needs_auth ? NVME_CONNECT_AUTHREQ_ATR : 0);
>   }
>   
>   static void nvmet_execute_admin_connect(struct nvmet_req *req)
> @@ -251,7 +267,7 @@ static void nvmet_execute_admin_connect(struct nvmet_req *req)
>   
>   	uuid_copy(&ctrl->hostid, &d->hostid);
>   
> -	dhchap_status = nvmet_setup_auth(ctrl);
> +	dhchap_status = nvmet_setup_auth(ctrl, req);
>   	if (dhchap_status) {
>   		pr_err("Failed to setup authentication, dhchap status %u\n",
>   		       dhchap_status);
> @@ -269,12 +285,13 @@ static void nvmet_execute_admin_connect(struct nvmet_req *req)
>   		goto out;
>   	}
>   
> -	pr_info("creating %s controller %d for subsystem %s for NQN %s%s%s.\n",
> +	pr_info("creating %s controller %d for subsystem %s for NQN %s%s%s%s.\n",
>   		nvmet_is_disc_subsys(ctrl->subsys) ? "discovery" : "nvm",
>   		ctrl->cntlid, ctrl->subsys->subsysnqn, ctrl->hostnqn,
> -		ctrl->pi_support ? " T10-PI is enabled" : "",
> -		nvmet_has_auth(ctrl) ? " with DH-HMAC-CHAP" : "");
> -	req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl));
> +		ctrl->pi_support ? ", T10-PI" : "",
> +		nvmet_has_auth(ctrl, req) ? ", DH-HMAC-CHAP" : "",
> +		nvmet_queue_tls_keyid(req->sq) ? ", TLS" : "");
> +	req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl, req));
>   out:
>   	kfree(d);
>   complete:
> @@ -330,7 +347,7 @@ static void nvmet_execute_io_connect(struct nvmet_req *req)
>   		goto out_ctrl_put;
>   
>   	pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);
> -	req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl));
> +	req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl, req));
>   out:
>   	kfree(d);
>   complete:
> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
> index 190f55e6d753..c2e17201c757 100644
> --- a/drivers/nvme/target/nvmet.h
> +++ b/drivers/nvme/target/nvmet.h
> @@ -121,6 +121,9 @@ struct nvmet_sq {
>   	u32			dhchap_s2;
>   	u8			*dhchap_skey;
>   	int			dhchap_skey_len;
> +#endif
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +	struct key		*tls_key;
>   #endif
>   	struct completion	free_done;
>   	struct completion	confirm_done;
> @@ -237,6 +240,7 @@ struct nvmet_ctrl {
>   	u64			err_counter;
>   	struct nvme_error_slot	slots[NVMET_ERROR_LOG_SLOTS];
>   	bool			pi_support;
> +	bool			concat;
>   #ifdef CONFIG_NVME_TARGET_AUTH
>   	struct nvme_dhchap_key	*host_key;
>   	struct nvme_dhchap_key	*ctrl_key;
> @@ -246,6 +250,9 @@ struct nvmet_ctrl {
>   	u8			*dh_key;
>   	size_t			dh_keysize;
>   #endif
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +	struct key		*tls_key;
> +#endif
>   };
>   
>   struct nvmet_subsys {
> @@ -716,13 +723,29 @@ static inline void nvmet_req_bio_put(struct nvmet_req *req, struct bio *bio)
>   		bio_put(bio);
>   }
>   
> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
> +static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq)
> +{
> +	return sq->tls_key ? key_serial(sq->tls_key) : 0;
> +}
> +static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq)
> +{
> +	if (sq->tls_key) {
> +		key_put(sq->tls_key);
> +		sq->tls_key = NULL;
> +	}
> +}
> +#else
> +static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq) { return 0; }
> +static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq) {}
> +#endif
>   #ifdef CONFIG_NVME_TARGET_AUTH
>   void nvmet_execute_auth_send(struct nvmet_req *req);
>   void nvmet_execute_auth_receive(struct nvmet_req *req);
>   int nvmet_auth_set_key(struct nvmet_host *host, const char *secret,
>   		       bool set_ctrl);
>   int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);
> -u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl);
> +u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);
>   void nvmet_auth_sq_init(struct nvmet_sq *sq);
>   void nvmet_destroy_auth(struct nvmet_ctrl *ctrl);
>   void nvmet_auth_sq_free(struct nvmet_sq *sq);
> @@ -732,16 +755,18 @@ int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,
>   			 unsigned int hash_len);
>   int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,
>   			 unsigned int hash_len);
> -static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)
> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
>   {
> -	return ctrl->host_key != NULL;
> +	return ctrl->host_key != NULL && !nvmet_queue_tls_keyid(req->sq);
>   }
>   int nvmet_auth_ctrl_exponential(struct nvmet_req *req,
>   				u8 *buf, int buf_size);
>   int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
>   			    u8 *buf, int buf_size);
> +void nvmet_auth_insert_psk(struct nvmet_sq *sq);
>   #else
> -static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
> +static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl,
> +				  struct nvmet_req *req)
>   {
>   	return 0;
>   }
> @@ -754,11 +779,12 @@ static inline bool nvmet_check_auth_status(struct nvmet_req *req)
>   {
>   	return true;
>   }
> -static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)
> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl,
> +				  struct nvmet_req *req)
>   {
>   	return false;
>   }
>   static inline const char *nvmet_dhchap_dhgroup_name(u8 dhgid) { return NULL; }
> +static inline void nvmet_auth_insert_psk(struct nvmet_sq *sq) {};
>   #endif
> -
>   #endif /* _NVMET_H */
> diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
> index 7c51c2a8c109..671600b5c64b 100644
> --- a/drivers/nvme/target/tcp.c
> +++ b/drivers/nvme/target/tcp.c
> @@ -1073,10 +1073,11 @@ static int nvmet_tcp_done_recv_pdu(struct nvmet_tcp_queue *queue)
>   
>   	if (unlikely(!nvmet_req_init(req, &queue->nvme_cq,
>   			&queue->nvme_sq, &nvmet_tcp_ops))) {
> -		pr_err("failed cmd %p id %d opcode %d, data_len: %d\n",
> +		pr_err("failed cmd %p id %d opcode %d, data_len: %d, status: %04x\n",
>   			req->cmd, req->cmd->common.command_id,
>   			req->cmd->common.opcode,
> -			le32_to_cpu(req->cmd->common.dptr.sgl.length));
> +		       le32_to_cpu(req->cmd->common.dptr.sgl.length),
> +		       le16_to_cpu(req->cqe->status));
>   
>   		nvmet_tcp_handle_req_failure(queue, queue->cmd, req);
>   		return 0;
> @@ -1602,6 +1603,7 @@ static void nvmet_tcp_release_queue_work(struct work_struct *w)
>   	/* stop accepting incoming data */
>   	queue->rcv_state = NVMET_TCP_RECV_ERR;
>   
> +	nvmet_sq_put_tls_key(&queue->nvme_sq);
>   	nvmet_tcp_uninit_data_in_cmds(queue);
>   	nvmet_sq_destroy(&queue->nvme_sq);
>   	cancel_work_sync(&queue->io_work);
> @@ -1807,6 +1809,23 @@ static void nvmet_tcp_tls_handshake_done(void *data, int status,
>   	spin_unlock_bh(&queue->state_lock);
>   
>   	cancel_delayed_work_sync(&queue->tls_handshake_tmo_work);
> +
> +	if (!status) {
> +		struct key *tls_key = nvme_tls_key_lookup(peerid);
> +
> +		if (IS_ERR(tls_key)) {

It is not clear to me how this can happen. Can you explain?

Other than that, patch looks good.
Hannes Reinecke Oct. 21, 2024, 7:29 a.m. UTC | #2
On 10/20/24 23:13, Sagi Grimberg wrote:
> 
> 
> 
> On 18/10/2024 9:33, Hannes Reinecke wrote:
>> Evaluate the SC_C flag during DH-CHAP-HMAC negotiation and insert
>> the generated PSK once negotiation has finished.
>>
>> Signed-off-by: Hannes Reinecke <hare@kernel.org>
>> ---
>>   drivers/nvme/target/auth.c             | 72 +++++++++++++++++++++++++-
>>   drivers/nvme/target/fabrics-cmd-auth.c | 49 +++++++++++++++---
>>   drivers/nvme/target/fabrics-cmd.c      | 33 +++++++++---
>>   drivers/nvme/target/nvmet.h            | 38 +++++++++++---
>>   drivers/nvme/target/tcp.c              | 23 +++++++-
>>   5 files changed, 192 insertions(+), 23 deletions(-)
>>
>> diff --git a/drivers/nvme/target/auth.c b/drivers/nvme/target/auth.c
>> index 7897d02c681d..7470ac020db6 100644
>> --- a/drivers/nvme/target/auth.c
>> +++ b/drivers/nvme/target/auth.c
>> @@ -15,6 +15,7 @@
>>   #include <linux/ctype.h>
>>   #include <linux/random.h>
>>   #include <linux/nvme-auth.h>
>> +#include <linux/nvme-keyring.h>
>>   #include <asm/unaligned.h>
>>   #include "nvmet.h"
>> @@ -138,7 +139,7 @@ int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, 
>> u8 dhgroup_id)
>>       return ret;
>>   }
>> -u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
>> +u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
>>   {
>>       int ret = 0;
>>       struct nvmet_host_link *p;
>> @@ -164,6 +165,11 @@ u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
>>           goto out_unlock;
>>       }
>> +    if (nvmet_queue_tls_keyid(req->sq)) {
>> +        pr_debug("host %s tls enabled\n", ctrl->hostnqn);
>> +        goto out_unlock;
>> +    }
>> +
>>       ret = nvmet_setup_dhgroup(ctrl, host->dhchap_dhgroup_id);
>>       if (ret < 0) {
>>           pr_warn("Failed to setup DH group");
>> @@ -232,6 +238,9 @@ u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
>>   void nvmet_auth_sq_free(struct nvmet_sq *sq)
>>   {
>>       cancel_delayed_work(&sq->auth_expired_work);
>> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
>> +    sq->tls_key = 0;
>> +#endif
>>       kfree(sq->dhchap_c1);
>>       sq->dhchap_c1 = NULL;
>>       kfree(sq->dhchap_c2);
>> @@ -260,6 +269,12 @@ void nvmet_destroy_auth(struct nvmet_ctrl *ctrl)
>>           nvme_auth_free_key(ctrl->ctrl_key);
>>           ctrl->ctrl_key = NULL;
>>       }
>> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
>> +    if (ctrl->tls_key) {
>> +        key_put(ctrl->tls_key);
>> +        ctrl->tls_key = NULL;
>> +    }
>> +#endif
>>   }
>>   bool nvmet_check_auth_status(struct nvmet_req *req)
>> @@ -541,3 +556,58 @@ int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
>>       return ret;
>>   }
>> +
>> +void nvmet_auth_insert_psk(struct nvmet_sq *sq)
>> +{
>> +    int hash_len = nvme_auth_hmac_hash_len(sq->ctrl->shash_id);
>> +    u8 *psk, *digest, *tls_psk;
>> +    size_t psk_len;
>> +    int ret;
>> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
>> +    struct key *tls_key = NULL;
>> +#endif
>> +
>> +    ret = nvme_auth_generate_psk(sq->ctrl->shash_id,
>> +                     sq->dhchap_skey,
>> +                     sq->dhchap_skey_len,
>> +                     sq->dhchap_c1, sq->dhchap_c2,
>> +                     hash_len, &psk, &psk_len);
>> +    if (ret) {
>> +        pr_warn("%s: ctrl %d qid %d failed to generate PSK, error %d\n",
>> +            __func__, sq->ctrl->cntlid, sq->qid, ret);
>> +        return;
>> +    }
>> +    ret = nvme_auth_generate_digest(sq->ctrl->shash_id, psk, psk_len,
>> +                    sq->ctrl->subsysnqn,
>> +                    sq->ctrl->hostnqn, &digest);
>> +    if (ret) {
>> +        pr_warn("%s: ctrl %d qid %d failed to generate digest, error 
>> %d\n",
>> +            __func__, sq->ctrl->cntlid, sq->qid, ret);
>> +        goto out_free_psk;
>> +    }
>> +    ret = nvme_auth_derive_tls_psk(sq->ctrl->shash_id, psk, psk_len,
>> +                       digest, &tls_psk);
>> +    if (ret) {
>> +        pr_warn("%s: ctrl %d qid %d failed to derive TLS PSK, error 
>> %d\n",
>> +            __func__, sq->ctrl->cntlid, sq->qid, ret);
>> +        goto out_free_digest;
>> +    }
>> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
>> +    tls_key = nvme_tls_psk_refresh(NULL, sq->ctrl->hostnqn, sq->ctrl- 
>> >subsysnqn,
>> +                       sq->ctrl->shash_id, tls_psk, psk_len, digest);
>> +    if (IS_ERR(tls_key)) {
>> +        pr_warn("%s: ctrl %d qid %d failed to refresh key, error %ld\n",
>> +            __func__, sq->ctrl->cntlid, sq->qid, PTR_ERR(tls_key));
>> +        tls_key = NULL;
>> +        kfree_sensitive(tls_psk);
>> +    }
>> +    if (sq->ctrl->tls_key)
>> +        key_put(sq->ctrl->tls_key);
>> +    sq->ctrl->tls_key = tls_key;
>> +#endif
>> +
>> +out_free_digest:
>> +    kfree_sensitive(digest);
>> +out_free_psk:
>> +    kfree_sensitive(psk);
>> +}
>> diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/ 
>> target/fabrics-cmd-auth.c
>> index 3f2857c17d95..cf4b38c0e7bd 100644
>> --- a/drivers/nvme/target/fabrics-cmd-auth.c
>> +++ b/drivers/nvme/target/fabrics-cmd-auth.c
>> @@ -43,8 +43,26 @@ static u8 nvmet_auth_negotiate(struct nvmet_req 
>> *req, void *d)
>>            data->auth_protocol[0].dhchap.halen,
>>            data->auth_protocol[0].dhchap.dhlen);
>>       req->sq->dhchap_tid = le16_to_cpu(data->t_id);
>> -    if (data->sc_c)
>> -        return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
>> +    if (data->sc_c != NVME_AUTH_SECP_NOSC) {
>> +        if (!IS_ENABLED(CONFIG_NVME_TARGET_TCP_TLS))
>> +            return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
>> +        /* Secure concatenation can only be enabled on the admin 
>> queue */
>> +        if (req->sq->qid)
>> +            return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
>> +        switch (data->sc_c) {
>> +        case NVME_AUTH_SECP_NEWTLSPSK:
>> +            if (nvmet_queue_tls_keyid(req->sq))
>> +                return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
>> +            break;
> 
> fallthru instead?
> 
Yeah, will do.

>> +        case NVME_AUTH_SECP_REPLACETLSPSK:
>> +            if (!nvmet_queue_tls_keyid(req->sq))
>> +                return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
>> +            break;
>> +        default:
>> +            return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
>> +        }
>> +        ctrl->concat = true;
>> +    }
>>       if (data->napd != 1)
>>           return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
>> @@ -103,6 +121,13 @@ static u8 nvmet_auth_negotiate(struct nvmet_req 
>> *req, void *d)
>>                nvme_auth_dhgroup_name(fallback_dhgid));
>>           ctrl->dh_gid = fallback_dhgid;
>>       }
>> +    if (ctrl->dh_gid == NVME_AUTH_DHGROUP_NULL &&
>> +        ctrl->concat) {
>> +        pr_debug("%s: ctrl %d qid %d: NULL DH group invalid "
>> +             "for secure channel concatenation\n", __func__,
>> +             ctrl->cntlid, req->sq->qid);
>> +        return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
>> +    }
>>       pr_debug("%s: ctrl %d qid %d: selected DH group %s (%d)\n",
>>            __func__, ctrl->cntlid, req->sq->qid,
>>            nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);
>> @@ -154,6 +179,12 @@ static u8 nvmet_auth_reply(struct nvmet_req *req, 
>> void *d)
>>       kfree(response);
>>       pr_debug("%s: ctrl %d qid %d host authenticated\n",
>>            __func__, ctrl->cntlid, req->sq->qid);
>> +    if (!data->cvalid && ctrl->concat) {
>> +        pr_debug("%s: ctrl %d qid %d invalid challenge\n",
>> +             __func__, ctrl->cntlid, req->sq->qid);
>> +        return NVME_AUTH_DHCHAP_FAILURE_FAILED;
>> +    }
>> +    req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);
>>       if (data->cvalid) {
>>           req->sq->dhchap_c2 = kmemdup(data->rval + data->hl, data->hl,
>>                            GFP_KERNEL);
>> @@ -163,11 +194,15 @@ static u8 nvmet_auth_reply(struct nvmet_req 
>> *req, void *d)
>>           pr_debug("%s: ctrl %d qid %d challenge %*ph\n",
>>                __func__, ctrl->cntlid, req->sq->qid, data->hl,
>>                req->sq->dhchap_c2);
>> -    } else {
>> +    }
>> +    if (req->sq->dhchap_s2 == 0) {
>> +        if (ctrl->concat)
>> +            nvmet_auth_insert_psk(req->sq);
>>           req->sq->authenticated = true;
>> +        kfree(req->sq->dhchap_c2);
>>           req->sq->dhchap_c2 = NULL;
>> -    }
>> -    req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);
>> +    } else if (!data->cvalid)
>> +        req->sq->authenticated = true;
>>       return 0;
>>   }
>> @@ -241,7 +276,7 @@ void nvmet_execute_auth_send(struct nvmet_req *req)
>>               pr_debug("%s: ctrl %d qid %d reset negotiation\n",
>>                    __func__, ctrl->cntlid, req->sq->qid);
>>               if (!req->sq->qid) {
>> -                dhchap_status = nvmet_setup_auth(ctrl);
>> +                dhchap_status = nvmet_setup_auth(ctrl, req);
>>                   if (dhchap_status) {
>>                       pr_err("ctrl %d qid 0 failed to setup re- 
>> authentication\n",
>>                              ctrl->cntlid);
>> @@ -298,6 +333,8 @@ void nvmet_execute_auth_send(struct nvmet_req *req)
>>           }
>>           goto done_kfree;
>>       case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:
>> +        if (ctrl->concat)
>> +            nvmet_auth_insert_psk(req->sq);
>>           req->sq->authenticated = true;
>>           pr_debug("%s: ctrl %d qid %d ctrl authenticated\n",
>>                __func__, ctrl->cntlid, req->sq->qid);
>> diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/ 
>> fabrics-cmd.c
>> index c4b2eddd5666..9a1256deee51 100644
>> --- a/drivers/nvme/target/fabrics-cmd.c
>> +++ b/drivers/nvme/target/fabrics-cmd.c
>> @@ -199,10 +199,26 @@ static u16 nvmet_install_queue(struct nvmet_ctrl 
>> *ctrl, struct nvmet_req *req)
>>       return ret;
>>   }
>> -static u32 nvmet_connect_result(struct nvmet_ctrl *ctrl)
>> +static u32 nvmet_connect_result(struct nvmet_ctrl *ctrl, struct 
>> nvmet_req *req)
>>   {
>> +    bool needs_auth = nvmet_has_auth(ctrl, req);
>> +    key_serial_t keyid = nvmet_queue_tls_keyid(req->sq);
>> +
>> +    /* Do not authenticate I/O queues for secure concatenation */
>> +    if (ctrl->concat && req->sq->qid)
>> +        needs_auth = false;
>> +
>> +    if (keyid)
>> +        pr_debug("%s: ctrl %d qid %d should %sauthenticate, tls psk 
>> %08x\n",
>> +             __func__, ctrl->cntlid, req->sq->qid,
>> +             needs_auth ? "" : "not ", keyid);
>> +    else
>> +        pr_debug("%s: ctrl %d qid %d should %sauthenticate%s\n",
>> +             __func__, ctrl->cntlid, req->sq->qid,
>> +             needs_auth ? "" : "not ",
>> +             ctrl->concat ? ", secure concatenation" : "");
>>       return (u32)ctrl->cntlid |
>> -        (nvmet_has_auth(ctrl) ? NVME_CONNECT_AUTHREQ_ATR : 0);
>> +        (needs_auth ? NVME_CONNECT_AUTHREQ_ATR : 0);
>>   }
>>   static void nvmet_execute_admin_connect(struct nvmet_req *req)
>> @@ -251,7 +267,7 @@ static void nvmet_execute_admin_connect(struct 
>> nvmet_req *req)
>>       uuid_copy(&ctrl->hostid, &d->hostid);
>> -    dhchap_status = nvmet_setup_auth(ctrl);
>> +    dhchap_status = nvmet_setup_auth(ctrl, req);
>>       if (dhchap_status) {
>>           pr_err("Failed to setup authentication, dhchap status %u\n",
>>                  dhchap_status);
>> @@ -269,12 +285,13 @@ static void nvmet_execute_admin_connect(struct 
>> nvmet_req *req)
>>           goto out;
>>       }
>> -    pr_info("creating %s controller %d for subsystem %s for NQN 
>> %s%s%s.\n",
>> +    pr_info("creating %s controller %d for subsystem %s for NQN 
>> %s%s%s%s.\n",
>>           nvmet_is_disc_subsys(ctrl->subsys) ? "discovery" : "nvm",
>>           ctrl->cntlid, ctrl->subsys->subsysnqn, ctrl->hostnqn,
>> -        ctrl->pi_support ? " T10-PI is enabled" : "",
>> -        nvmet_has_auth(ctrl) ? " with DH-HMAC-CHAP" : "");
>> -    req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl));
>> +        ctrl->pi_support ? ", T10-PI" : "",
>> +        nvmet_has_auth(ctrl, req) ? ", DH-HMAC-CHAP" : "",
>> +        nvmet_queue_tls_keyid(req->sq) ? ", TLS" : "");
>> +    req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl, req));
>>   out:
>>       kfree(d);
>>   complete:
>> @@ -330,7 +347,7 @@ static void nvmet_execute_io_connect(struct 
>> nvmet_req *req)
>>           goto out_ctrl_put;
>>       pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);
>> -    req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl));
>> +    req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl, req));
>>   out:
>>       kfree(d);
>>   complete:
>> diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
>> index 190f55e6d753..c2e17201c757 100644
>> --- a/drivers/nvme/target/nvmet.h
>> +++ b/drivers/nvme/target/nvmet.h
>> @@ -121,6 +121,9 @@ struct nvmet_sq {
>>       u32            dhchap_s2;
>>       u8            *dhchap_skey;
>>       int            dhchap_skey_len;
>> +#endif
>> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
>> +    struct key        *tls_key;
>>   #endif
>>       struct completion    free_done;
>>       struct completion    confirm_done;
>> @@ -237,6 +240,7 @@ struct nvmet_ctrl {
>>       u64            err_counter;
>>       struct nvme_error_slot    slots[NVMET_ERROR_LOG_SLOTS];
>>       bool            pi_support;
>> +    bool            concat;
>>   #ifdef CONFIG_NVME_TARGET_AUTH
>>       struct nvme_dhchap_key    *host_key;
>>       struct nvme_dhchap_key    *ctrl_key;
>> @@ -246,6 +250,9 @@ struct nvmet_ctrl {
>>       u8            *dh_key;
>>       size_t            dh_keysize;
>>   #endif
>> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
>> +    struct key        *tls_key;
>> +#endif
>>   };
>>   struct nvmet_subsys {
>> @@ -716,13 +723,29 @@ static inline void nvmet_req_bio_put(struct 
>> nvmet_req *req, struct bio *bio)
>>           bio_put(bio);
>>   }
>> +#ifdef CONFIG_NVME_TARGET_TCP_TLS
>> +static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq)
>> +{
>> +    return sq->tls_key ? key_serial(sq->tls_key) : 0;
>> +}
>> +static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq)
>> +{
>> +    if (sq->tls_key) {
>> +        key_put(sq->tls_key);
>> +        sq->tls_key = NULL;
>> +    }
>> +}
>> +#else
>> +static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq) 
>> { return 0; }
>> +static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq) {}
>> +#endif
>>   #ifdef CONFIG_NVME_TARGET_AUTH
>>   void nvmet_execute_auth_send(struct nvmet_req *req);
>>   void nvmet_execute_auth_receive(struct nvmet_req *req);
>>   int nvmet_auth_set_key(struct nvmet_host *host, const char *secret,
>>                  bool set_ctrl);
>>   int nvmet_auth_set_host_hash(struct nvmet_host *host, const char 
>> *hash);
>> -u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl);
>> +u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);
>>   void nvmet_auth_sq_init(struct nvmet_sq *sq);
>>   void nvmet_destroy_auth(struct nvmet_ctrl *ctrl);
>>   void nvmet_auth_sq_free(struct nvmet_sq *sq);
>> @@ -732,16 +755,18 @@ int nvmet_auth_host_hash(struct nvmet_req *req, 
>> u8 *response,
>>                unsigned int hash_len);
>>   int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,
>>                unsigned int hash_len);
>> -static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)
>> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl, struct 
>> nvmet_req *req)
>>   {
>> -    return ctrl->host_key != NULL;
>> +    return ctrl->host_key != NULL && !nvmet_queue_tls_keyid(req->sq);
>>   }
>>   int nvmet_auth_ctrl_exponential(struct nvmet_req *req,
>>                   u8 *buf, int buf_size);
>>   int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
>>                   u8 *buf, int buf_size);
>> +void nvmet_auth_insert_psk(struct nvmet_sq *sq);
>>   #else
>> -static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
>> +static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl,
>> +                  struct nvmet_req *req)
>>   {
>>       return 0;
>>   }
>> @@ -754,11 +779,12 @@ static inline bool 
>> nvmet_check_auth_status(struct nvmet_req *req)
>>   {
>>       return true;
>>   }
>> -static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)
>> +static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl,
>> +                  struct nvmet_req *req)
>>   {
>>       return false;
>>   }
>>   static inline const char *nvmet_dhchap_dhgroup_name(u8 dhgid) 
>> { return NULL; }
>> +static inline void nvmet_auth_insert_psk(struct nvmet_sq *sq) {};
>>   #endif
>> -
>>   #endif /* _NVMET_H */
>> diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
>> index 7c51c2a8c109..671600b5c64b 100644
>> --- a/drivers/nvme/target/tcp.c
>> +++ b/drivers/nvme/target/tcp.c
>> @@ -1073,10 +1073,11 @@ static int nvmet_tcp_done_recv_pdu(struct 
>> nvmet_tcp_queue *queue)
>>       if (unlikely(!nvmet_req_init(req, &queue->nvme_cq,
>>               &queue->nvme_sq, &nvmet_tcp_ops))) {
>> -        pr_err("failed cmd %p id %d opcode %d, data_len: %d\n",
>> +        pr_err("failed cmd %p id %d opcode %d, data_len: %d, status: 
>> %04x\n",
>>               req->cmd, req->cmd->common.command_id,
>>               req->cmd->common.opcode,
>> -            le32_to_cpu(req->cmd->common.dptr.sgl.length));
>> +               le32_to_cpu(req->cmd->common.dptr.sgl.length),
>> +               le16_to_cpu(req->cqe->status));
>>           nvmet_tcp_handle_req_failure(queue, queue->cmd, req);
>>           return 0;
>> @@ -1602,6 +1603,7 @@ static void nvmet_tcp_release_queue_work(struct 
>> work_struct *w)
>>       /* stop accepting incoming data */
>>       queue->rcv_state = NVMET_TCP_RECV_ERR;
>> +    nvmet_sq_put_tls_key(&queue->nvme_sq);
>>       nvmet_tcp_uninit_data_in_cmds(queue);
>>       nvmet_sq_destroy(&queue->nvme_sq);
>>       cancel_work_sync(&queue->io_work);
>> @@ -1807,6 +1809,23 @@ static void nvmet_tcp_tls_handshake_done(void 
>> *data, int status,
>>       spin_unlock_bh(&queue->state_lock);
>>       cancel_delayed_work_sync(&queue->tls_handshake_tmo_work);
>> +
>> +    if (!status) {
>> +        struct key *tls_key = nvme_tls_key_lookup(peerid);
>> +
>> +        if (IS_ERR(tls_key)) {
> 
> It is not clear to me how this can happen. Can you explain?
> 
Passing key information between kernel and handshake daemon is
done purely by key IDs (not the keys itself).
So we will be getting a key ID back from the handshaked daemon,
and we need to validate that; the user (or admin software) could
have invalidated the key while the handshake was running, or before
we had a chance to process the reply from the handshake daemon.

> Other than that, patch looks good.

Cheers,

Hannes
Sagi Grimberg Oct. 21, 2024, 7:36 a.m. UTC | #3
>>>
>>> diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
>>> index 7c51c2a8c109..671600b5c64b 100644
>>> --- a/drivers/nvme/target/tcp.c
>>> +++ b/drivers/nvme/target/tcp.c
>>> @@ -1073,10 +1073,11 @@ static int nvmet_tcp_done_recv_pdu(struct 
>>> nvmet_tcp_queue *queue)
>>>       if (unlikely(!nvmet_req_init(req, &queue->nvme_cq,
>>>               &queue->nvme_sq, &nvmet_tcp_ops))) {
>>> -        pr_err("failed cmd %p id %d opcode %d, data_len: %d\n",
>>> +        pr_err("failed cmd %p id %d opcode %d, data_len: %d, 
>>> status: %04x\n",
>>>               req->cmd, req->cmd->common.command_id,
>>>               req->cmd->common.opcode,
>>> - le32_to_cpu(req->cmd->common.dptr.sgl.length));
>>> + le32_to_cpu(req->cmd->common.dptr.sgl.length),
>>> +               le16_to_cpu(req->cqe->status));
>>>           nvmet_tcp_handle_req_failure(queue, queue->cmd, req);
>>>           return 0;
>>> @@ -1602,6 +1603,7 @@ static void 
>>> nvmet_tcp_release_queue_work(struct work_struct *w)
>>>       /* stop accepting incoming data */
>>>       queue->rcv_state = NVMET_TCP_RECV_ERR;
>>> +    nvmet_sq_put_tls_key(&queue->nvme_sq);
>>>       nvmet_tcp_uninit_data_in_cmds(queue);
>>>       nvmet_sq_destroy(&queue->nvme_sq);
>>>       cancel_work_sync(&queue->io_work);
>>> @@ -1807,6 +1809,23 @@ static void nvmet_tcp_tls_handshake_done(void 
>>> *data, int status,
>>>       spin_unlock_bh(&queue->state_lock);
>>> cancel_delayed_work_sync(&queue->tls_handshake_tmo_work);
>>> +
>>> +    if (!status) {
>>> +        struct key *tls_key = nvme_tls_key_lookup(peerid);
>>> +
>>> +        if (IS_ERR(tls_key)) {
>>
>> It is not clear to me how this can happen. Can you explain?
>>
> Passing key information between kernel and handshake daemon is
> done purely by key IDs (not the keys itself).
> So we will be getting a key ID back from the handshaked daemon,
> and we need to validate that; the user (or admin software) could
> have invalidated the key while the handshake was running, or before
> we had a chance to process the reply from the handshake daemon.

Got it. thanks.
diff mbox series

Patch

diff --git a/drivers/nvme/target/auth.c b/drivers/nvme/target/auth.c
index 7897d02c681d..7470ac020db6 100644
--- a/drivers/nvme/target/auth.c
+++ b/drivers/nvme/target/auth.c
@@ -15,6 +15,7 @@ 
 #include <linux/ctype.h>
 #include <linux/random.h>
 #include <linux/nvme-auth.h>
+#include <linux/nvme-keyring.h>
 #include <asm/unaligned.h>
 
 #include "nvmet.h"
@@ -138,7 +139,7 @@  int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, u8 dhgroup_id)
 	return ret;
 }
 
-u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
+u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
 {
 	int ret = 0;
 	struct nvmet_host_link *p;
@@ -164,6 +165,11 @@  u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
 		goto out_unlock;
 	}
 
+	if (nvmet_queue_tls_keyid(req->sq)) {
+		pr_debug("host %s tls enabled\n", ctrl->hostnqn);
+		goto out_unlock;
+	}
+
 	ret = nvmet_setup_dhgroup(ctrl, host->dhchap_dhgroup_id);
 	if (ret < 0) {
 		pr_warn("Failed to setup DH group");
@@ -232,6 +238,9 @@  u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
 void nvmet_auth_sq_free(struct nvmet_sq *sq)
 {
 	cancel_delayed_work(&sq->auth_expired_work);
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+	sq->tls_key = 0;
+#endif
 	kfree(sq->dhchap_c1);
 	sq->dhchap_c1 = NULL;
 	kfree(sq->dhchap_c2);
@@ -260,6 +269,12 @@  void nvmet_destroy_auth(struct nvmet_ctrl *ctrl)
 		nvme_auth_free_key(ctrl->ctrl_key);
 		ctrl->ctrl_key = NULL;
 	}
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+	if (ctrl->tls_key) {
+		key_put(ctrl->tls_key);
+		ctrl->tls_key = NULL;
+	}
+#endif
 }
 
 bool nvmet_check_auth_status(struct nvmet_req *req)
@@ -541,3 +556,58 @@  int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
 
 	return ret;
 }
+
+void nvmet_auth_insert_psk(struct nvmet_sq *sq)
+{
+	int hash_len = nvme_auth_hmac_hash_len(sq->ctrl->shash_id);
+	u8 *psk, *digest, *tls_psk;
+	size_t psk_len;
+	int ret;
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+	struct key *tls_key = NULL;
+#endif
+
+	ret = nvme_auth_generate_psk(sq->ctrl->shash_id,
+				     sq->dhchap_skey,
+				     sq->dhchap_skey_len,
+				     sq->dhchap_c1, sq->dhchap_c2,
+				     hash_len, &psk, &psk_len);
+	if (ret) {
+		pr_warn("%s: ctrl %d qid %d failed to generate PSK, error %d\n",
+			__func__, sq->ctrl->cntlid, sq->qid, ret);
+		return;
+	}
+	ret = nvme_auth_generate_digest(sq->ctrl->shash_id, psk, psk_len,
+					sq->ctrl->subsysnqn,
+					sq->ctrl->hostnqn, &digest);
+	if (ret) {
+		pr_warn("%s: ctrl %d qid %d failed to generate digest, error %d\n",
+			__func__, sq->ctrl->cntlid, sq->qid, ret);
+		goto out_free_psk;
+	}
+	ret = nvme_auth_derive_tls_psk(sq->ctrl->shash_id, psk, psk_len,
+				       digest, &tls_psk);
+	if (ret) {
+		pr_warn("%s: ctrl %d qid %d failed to derive TLS PSK, error %d\n",
+			__func__, sq->ctrl->cntlid, sq->qid, ret);
+		goto out_free_digest;
+	}
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+	tls_key = nvme_tls_psk_refresh(NULL, sq->ctrl->hostnqn, sq->ctrl->subsysnqn,
+				       sq->ctrl->shash_id, tls_psk, psk_len, digest);
+	if (IS_ERR(tls_key)) {
+		pr_warn("%s: ctrl %d qid %d failed to refresh key, error %ld\n",
+			__func__, sq->ctrl->cntlid, sq->qid, PTR_ERR(tls_key));
+		tls_key = NULL;
+		kfree_sensitive(tls_psk);
+	}
+	if (sq->ctrl->tls_key)
+		key_put(sq->ctrl->tls_key);
+	sq->ctrl->tls_key = tls_key;
+#endif
+
+out_free_digest:
+	kfree_sensitive(digest);
+out_free_psk:
+	kfree_sensitive(psk);
+}
diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c
index 3f2857c17d95..cf4b38c0e7bd 100644
--- a/drivers/nvme/target/fabrics-cmd-auth.c
+++ b/drivers/nvme/target/fabrics-cmd-auth.c
@@ -43,8 +43,26 @@  static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
 		 data->auth_protocol[0].dhchap.halen,
 		 data->auth_protocol[0].dhchap.dhlen);
 	req->sq->dhchap_tid = le16_to_cpu(data->t_id);
-	if (data->sc_c)
-		return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
+	if (data->sc_c != NVME_AUTH_SECP_NOSC) {
+		if (!IS_ENABLED(CONFIG_NVME_TARGET_TCP_TLS))
+			return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
+		/* Secure concatenation can only be enabled on the admin queue */
+		if (req->sq->qid)
+			return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
+		switch (data->sc_c) {
+		case NVME_AUTH_SECP_NEWTLSPSK:
+			if (nvmet_queue_tls_keyid(req->sq))
+				return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
+			break;
+		case NVME_AUTH_SECP_REPLACETLSPSK:
+			if (!nvmet_queue_tls_keyid(req->sq))
+				return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
+			break;
+		default:
+			return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
+		}
+		ctrl->concat = true;
+	}
 
 	if (data->napd != 1)
 		return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
@@ -103,6 +121,13 @@  static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
 			 nvme_auth_dhgroup_name(fallback_dhgid));
 		ctrl->dh_gid = fallback_dhgid;
 	}
+	if (ctrl->dh_gid == NVME_AUTH_DHGROUP_NULL &&
+	    ctrl->concat) {
+		pr_debug("%s: ctrl %d qid %d: NULL DH group invalid "
+			 "for secure channel concatenation\n", __func__,
+			 ctrl->cntlid, req->sq->qid);
+		return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
+	}
 	pr_debug("%s: ctrl %d qid %d: selected DH group %s (%d)\n",
 		 __func__, ctrl->cntlid, req->sq->qid,
 		 nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);
@@ -154,6 +179,12 @@  static u8 nvmet_auth_reply(struct nvmet_req *req, void *d)
 	kfree(response);
 	pr_debug("%s: ctrl %d qid %d host authenticated\n",
 		 __func__, ctrl->cntlid, req->sq->qid);
+	if (!data->cvalid && ctrl->concat) {
+		pr_debug("%s: ctrl %d qid %d invalid challenge\n",
+			 __func__, ctrl->cntlid, req->sq->qid);
+		return NVME_AUTH_DHCHAP_FAILURE_FAILED;
+	}
+	req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);
 	if (data->cvalid) {
 		req->sq->dhchap_c2 = kmemdup(data->rval + data->hl, data->hl,
 					     GFP_KERNEL);
@@ -163,11 +194,15 @@  static u8 nvmet_auth_reply(struct nvmet_req *req, void *d)
 		pr_debug("%s: ctrl %d qid %d challenge %*ph\n",
 			 __func__, ctrl->cntlid, req->sq->qid, data->hl,
 			 req->sq->dhchap_c2);
-	} else {
+	}
+	if (req->sq->dhchap_s2 == 0) {
+		if (ctrl->concat)
+			nvmet_auth_insert_psk(req->sq);
 		req->sq->authenticated = true;
+		kfree(req->sq->dhchap_c2);
 		req->sq->dhchap_c2 = NULL;
-	}
-	req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);
+	} else if (!data->cvalid)
+		req->sq->authenticated = true;
 
 	return 0;
 }
@@ -241,7 +276,7 @@  void nvmet_execute_auth_send(struct nvmet_req *req)
 			pr_debug("%s: ctrl %d qid %d reset negotiation\n",
 				 __func__, ctrl->cntlid, req->sq->qid);
 			if (!req->sq->qid) {
-				dhchap_status = nvmet_setup_auth(ctrl);
+				dhchap_status = nvmet_setup_auth(ctrl, req);
 				if (dhchap_status) {
 					pr_err("ctrl %d qid 0 failed to setup re-authentication\n",
 					       ctrl->cntlid);
@@ -298,6 +333,8 @@  void nvmet_execute_auth_send(struct nvmet_req *req)
 		}
 		goto done_kfree;
 	case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:
+		if (ctrl->concat)
+			nvmet_auth_insert_psk(req->sq);
 		req->sq->authenticated = true;
 		pr_debug("%s: ctrl %d qid %d ctrl authenticated\n",
 			 __func__, ctrl->cntlid, req->sq->qid);
diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/fabrics-cmd.c
index c4b2eddd5666..9a1256deee51 100644
--- a/drivers/nvme/target/fabrics-cmd.c
+++ b/drivers/nvme/target/fabrics-cmd.c
@@ -199,10 +199,26 @@  static u16 nvmet_install_queue(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
 	return ret;
 }
 
-static u32 nvmet_connect_result(struct nvmet_ctrl *ctrl)
+static u32 nvmet_connect_result(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
 {
+	bool needs_auth = nvmet_has_auth(ctrl, req);
+	key_serial_t keyid = nvmet_queue_tls_keyid(req->sq);
+
+	/* Do not authenticate I/O queues for secure concatenation */
+	if (ctrl->concat && req->sq->qid)
+		needs_auth = false;
+
+	if (keyid)
+		pr_debug("%s: ctrl %d qid %d should %sauthenticate, tls psk %08x\n",
+			 __func__, ctrl->cntlid, req->sq->qid,
+			 needs_auth ? "" : "not ", keyid);
+	else
+		pr_debug("%s: ctrl %d qid %d should %sauthenticate%s\n",
+			 __func__, ctrl->cntlid, req->sq->qid,
+			 needs_auth ? "" : "not ",
+			 ctrl->concat ? ", secure concatenation" : "");
 	return (u32)ctrl->cntlid |
-		(nvmet_has_auth(ctrl) ? NVME_CONNECT_AUTHREQ_ATR : 0);
+		(needs_auth ? NVME_CONNECT_AUTHREQ_ATR : 0);
 }
 
 static void nvmet_execute_admin_connect(struct nvmet_req *req)
@@ -251,7 +267,7 @@  static void nvmet_execute_admin_connect(struct nvmet_req *req)
 
 	uuid_copy(&ctrl->hostid, &d->hostid);
 
-	dhchap_status = nvmet_setup_auth(ctrl);
+	dhchap_status = nvmet_setup_auth(ctrl, req);
 	if (dhchap_status) {
 		pr_err("Failed to setup authentication, dhchap status %u\n",
 		       dhchap_status);
@@ -269,12 +285,13 @@  static void nvmet_execute_admin_connect(struct nvmet_req *req)
 		goto out;
 	}
 
-	pr_info("creating %s controller %d for subsystem %s for NQN %s%s%s.\n",
+	pr_info("creating %s controller %d for subsystem %s for NQN %s%s%s%s.\n",
 		nvmet_is_disc_subsys(ctrl->subsys) ? "discovery" : "nvm",
 		ctrl->cntlid, ctrl->subsys->subsysnqn, ctrl->hostnqn,
-		ctrl->pi_support ? " T10-PI is enabled" : "",
-		nvmet_has_auth(ctrl) ? " with DH-HMAC-CHAP" : "");
-	req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl));
+		ctrl->pi_support ? ", T10-PI" : "",
+		nvmet_has_auth(ctrl, req) ? ", DH-HMAC-CHAP" : "",
+		nvmet_queue_tls_keyid(req->sq) ? ", TLS" : "");
+	req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl, req));
 out:
 	kfree(d);
 complete:
@@ -330,7 +347,7 @@  static void nvmet_execute_io_connect(struct nvmet_req *req)
 		goto out_ctrl_put;
 
 	pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);
-	req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl));
+	req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl, req));
 out:
 	kfree(d);
 complete:
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index 190f55e6d753..c2e17201c757 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -121,6 +121,9 @@  struct nvmet_sq {
 	u32			dhchap_s2;
 	u8			*dhchap_skey;
 	int			dhchap_skey_len;
+#endif
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+	struct key		*tls_key;
 #endif
 	struct completion	free_done;
 	struct completion	confirm_done;
@@ -237,6 +240,7 @@  struct nvmet_ctrl {
 	u64			err_counter;
 	struct nvme_error_slot	slots[NVMET_ERROR_LOG_SLOTS];
 	bool			pi_support;
+	bool			concat;
 #ifdef CONFIG_NVME_TARGET_AUTH
 	struct nvme_dhchap_key	*host_key;
 	struct nvme_dhchap_key	*ctrl_key;
@@ -246,6 +250,9 @@  struct nvmet_ctrl {
 	u8			*dh_key;
 	size_t			dh_keysize;
 #endif
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+	struct key		*tls_key;
+#endif
 };
 
 struct nvmet_subsys {
@@ -716,13 +723,29 @@  static inline void nvmet_req_bio_put(struct nvmet_req *req, struct bio *bio)
 		bio_put(bio);
 }
 
+#ifdef CONFIG_NVME_TARGET_TCP_TLS
+static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq)
+{
+	return sq->tls_key ? key_serial(sq->tls_key) : 0;
+}
+static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq)
+{
+	if (sq->tls_key) {
+		key_put(sq->tls_key);
+		sq->tls_key = NULL;
+	}
+}
+#else
+static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq) { return 0; }
+static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq) {}
+#endif
 #ifdef CONFIG_NVME_TARGET_AUTH
 void nvmet_execute_auth_send(struct nvmet_req *req);
 void nvmet_execute_auth_receive(struct nvmet_req *req);
 int nvmet_auth_set_key(struct nvmet_host *host, const char *secret,
 		       bool set_ctrl);
 int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);
-u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl);
+u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req);
 void nvmet_auth_sq_init(struct nvmet_sq *sq);
 void nvmet_destroy_auth(struct nvmet_ctrl *ctrl);
 void nvmet_auth_sq_free(struct nvmet_sq *sq);
@@ -732,16 +755,18 @@  int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,
 			 unsigned int hash_len);
 int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,
 			 unsigned int hash_len);
-static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)
+static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
 {
-	return ctrl->host_key != NULL;
+	return ctrl->host_key != NULL && !nvmet_queue_tls_keyid(req->sq);
 }
 int nvmet_auth_ctrl_exponential(struct nvmet_req *req,
 				u8 *buf, int buf_size);
 int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
 			    u8 *buf, int buf_size);
+void nvmet_auth_insert_psk(struct nvmet_sq *sq);
 #else
-static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl)
+static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl,
+				  struct nvmet_req *req)
 {
 	return 0;
 }
@@ -754,11 +779,12 @@  static inline bool nvmet_check_auth_status(struct nvmet_req *req)
 {
 	return true;
 }
-static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl)
+static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl,
+				  struct nvmet_req *req)
 {
 	return false;
 }
 static inline const char *nvmet_dhchap_dhgroup_name(u8 dhgid) { return NULL; }
+static inline void nvmet_auth_insert_psk(struct nvmet_sq *sq) {};
 #endif
-
 #endif /* _NVMET_H */
diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
index 7c51c2a8c109..671600b5c64b 100644
--- a/drivers/nvme/target/tcp.c
+++ b/drivers/nvme/target/tcp.c
@@ -1073,10 +1073,11 @@  static int nvmet_tcp_done_recv_pdu(struct nvmet_tcp_queue *queue)
 
 	if (unlikely(!nvmet_req_init(req, &queue->nvme_cq,
 			&queue->nvme_sq, &nvmet_tcp_ops))) {
-		pr_err("failed cmd %p id %d opcode %d, data_len: %d\n",
+		pr_err("failed cmd %p id %d opcode %d, data_len: %d, status: %04x\n",
 			req->cmd, req->cmd->common.command_id,
 			req->cmd->common.opcode,
-			le32_to_cpu(req->cmd->common.dptr.sgl.length));
+		       le32_to_cpu(req->cmd->common.dptr.sgl.length),
+		       le16_to_cpu(req->cqe->status));
 
 		nvmet_tcp_handle_req_failure(queue, queue->cmd, req);
 		return 0;
@@ -1602,6 +1603,7 @@  static void nvmet_tcp_release_queue_work(struct work_struct *w)
 	/* stop accepting incoming data */
 	queue->rcv_state = NVMET_TCP_RECV_ERR;
 
+	nvmet_sq_put_tls_key(&queue->nvme_sq);
 	nvmet_tcp_uninit_data_in_cmds(queue);
 	nvmet_sq_destroy(&queue->nvme_sq);
 	cancel_work_sync(&queue->io_work);
@@ -1807,6 +1809,23 @@  static void nvmet_tcp_tls_handshake_done(void *data, int status,
 	spin_unlock_bh(&queue->state_lock);
 
 	cancel_delayed_work_sync(&queue->tls_handshake_tmo_work);
+
+	if (!status) {
+		struct key *tls_key = nvme_tls_key_lookup(peerid);
+
+		if (IS_ERR(tls_key)) {
+			pr_warn("%s: queue %d failed to lookup key %x\n",
+				__func__, queue->idx, peerid);
+			spin_lock_bh(&queue->state_lock);
+			queue->state = NVMET_TCP_Q_FAILED;
+			spin_unlock_bh(&queue->state_lock);
+			status = PTR_ERR(tls_key);
+		} else {
+			pr_debug("%s: queue %d using TLS PSK %x\n",
+				 __func__, queue->idx, peerid);
+			queue->nvme_sq.tls_key = tls_key;
+		}
+	}
 	if (status)
 		nvmet_tcp_schedule_release_queue(queue);
 	else