root/drivers/nvme/target/fabrics-cmd-auth.c
// SPDX-License-Identifier: GPL-2.0
/*
 * NVMe over Fabrics DH-HMAC-CHAP authentication command handling.
 * Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.
 * All rights reserved.
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/blkdev.h>
#include <linux/random.h>
#include <linux/nvme-auth.h>
#include <crypto/hash.h>
#include <crypto/kpp.h>
#include "nvmet.h"

static void nvmet_auth_expired_work(struct work_struct *work)
{
        struct nvmet_sq *sq = container_of(to_delayed_work(work),
                        struct nvmet_sq, auth_expired_work);

        pr_debug("%s: ctrl %d qid %d transaction %u expired, resetting\n",
                 __func__, sq->ctrl->cntlid, sq->qid, sq->dhchap_tid);
        sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
        sq->dhchap_tid = -1;
}

void nvmet_auth_sq_init(struct nvmet_sq *sq)
{
        /* Initialize in-band authentication */
        INIT_DELAYED_WORK(&sq->auth_expired_work, nvmet_auth_expired_work);
        sq->authenticated = false;
        sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
}

static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
{
        struct nvmet_ctrl *ctrl = req->sq->ctrl;
        struct nvmf_auth_dhchap_negotiate_data *data = d;
        int i, hash_id = 0, fallback_hash_id = 0, dhgid, fallback_dhgid;

        pr_debug("%s: ctrl %d qid %d: data sc_d %d napd %d authid %d halen %d dhlen %d\n",
                 __func__, ctrl->cntlid, req->sq->qid,
                 data->sc_c, data->napd, data->auth_protocol[0].dhchap.authid,
                 data->auth_protocol[0].dhchap.halen,
                 data->auth_protocol[0].dhchap.dhlen);
        req->sq->dhchap_tid = le16_to_cpu(data->t_id);
        req->sq->sc_c = data->sc_c;
        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;

        if (data->auth_protocol[0].dhchap.authid !=
            NVME_AUTH_DHCHAP_AUTH_ID)
                return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;

        for (i = 0; i < data->auth_protocol[0].dhchap.halen; i++) {
                u8 host_hmac_id = data->auth_protocol[0].dhchap.idlist[i];

                if (!fallback_hash_id &&
                    crypto_has_shash(nvme_auth_hmac_name(host_hmac_id), 0, 0))
                        fallback_hash_id = host_hmac_id;
                if (ctrl->shash_id != host_hmac_id)
                        continue;
                hash_id = ctrl->shash_id;
                break;
        }
        if (hash_id == 0) {
                if (fallback_hash_id == 0) {
                        pr_debug("%s: ctrl %d qid %d: no usable hash found\n",
                                 __func__, ctrl->cntlid, req->sq->qid);
                        return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
                }
                pr_debug("%s: ctrl %d qid %d: no usable hash found, falling back to %s\n",
                         __func__, ctrl->cntlid, req->sq->qid,
                         nvme_auth_hmac_name(fallback_hash_id));
                ctrl->shash_id = fallback_hash_id;
        }

        dhgid = -1;
        fallback_dhgid = -1;
        for (i = 0; i < data->auth_protocol[0].dhchap.dhlen; i++) {
                int tmp_dhgid = data->auth_protocol[0].dhchap.idlist[i + 30];

                if (tmp_dhgid != ctrl->dh_gid) {
                        dhgid = tmp_dhgid;
                        break;
                }
                if (fallback_dhgid < 0) {
                        const char *kpp = nvme_auth_dhgroup_kpp(tmp_dhgid);

                        if (crypto_has_kpp(kpp, 0, 0))
                                fallback_dhgid = tmp_dhgid;
                }
        }
        if (dhgid < 0) {
                if (fallback_dhgid < 0) {
                        pr_debug("%s: ctrl %d qid %d: no usable DH group found\n",
                                 __func__, ctrl->cntlid, req->sq->qid);
                        return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;
                }
                pr_debug("%s: ctrl %d qid %d: configured DH group %s not found\n",
                         __func__, ctrl->cntlid, req->sq->qid,
                         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);
        return 0;
}

static u8 nvmet_auth_reply(struct nvmet_req *req, void *d)
{
        struct nvmet_ctrl *ctrl = req->sq->ctrl;
        struct nvmf_auth_dhchap_reply_data *data = d;
        u16 dhvlen = le16_to_cpu(data->dhvlen);
        u8 *response;

        pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %u\n",
                 __func__, ctrl->cntlid, req->sq->qid,
                 data->hl, data->cvalid, dhvlen);

        if (dhvlen) {
                if (!ctrl->dh_tfm)
                        return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
                if (nvmet_auth_ctrl_sesskey(req, data->rval + 2 * data->hl,
                                            dhvlen) < 0)
                        return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;
        }

        response = kmalloc(data->hl, GFP_KERNEL);
        if (!response)
                return NVME_AUTH_DHCHAP_FAILURE_FAILED;

        if (!ctrl->host_key) {
                pr_warn("ctrl %d qid %d no host key\n",
                        ctrl->cntlid, req->sq->qid);
                kfree(response);
                return NVME_AUTH_DHCHAP_FAILURE_FAILED;
        }
        if (nvmet_auth_host_hash(req, response, data->hl) < 0) {
                pr_debug("ctrl %d qid %d host hash failed\n",
                         ctrl->cntlid, req->sq->qid);
                kfree(response);
                return NVME_AUTH_DHCHAP_FAILURE_FAILED;
        }

        if (memcmp(data->rval, response, data->hl)) {
                pr_info("ctrl %d qid %d host response mismatch\n",
                        ctrl->cntlid, req->sq->qid);
                pr_debug("ctrl %d qid %d rval %*ph\n",
                         ctrl->cntlid, req->sq->qid, data->hl, data->rval);
                pr_debug("ctrl %d qid %d response %*ph\n",
                         ctrl->cntlid, req->sq->qid, data->hl, response);
                kfree(response);
                return NVME_AUTH_DHCHAP_FAILURE_FAILED;
        }
        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);
                if (!req->sq->dhchap_c2)
                        return NVME_AUTH_DHCHAP_FAILURE_FAILED;

                pr_debug("%s: ctrl %d qid %d challenge %*ph\n",
                         __func__, ctrl->cntlid, req->sq->qid, data->hl,
                         req->sq->dhchap_c2);
        }
        /*
         * NVMe Base Spec 2.2 section 8.3.4.5.4: DH-HMAC-CHAP_Reply message
         * Sequence Number (SEQNUM): [ .. ]
         * The value 0h is used to indicate that bidirectional authentication
         * is not performed, but a challenge value C2 is carried in order to
         * generate a pre-shared key (PSK) for subsequent establishment of a
         * secure channel.
         */
        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;
        } else if (!data->cvalid)
                req->sq->authenticated = true;

        return 0;
}

static u8 nvmet_auth_failure2(void *d)
{
        struct nvmf_auth_dhchap_failure_data *data = d;

        return data->rescode_exp;
}

u32 nvmet_auth_send_data_len(struct nvmet_req *req)
{
        return le32_to_cpu(req->cmd->auth_send.tl);
}

void nvmet_execute_auth_send(struct nvmet_req *req)
{
        struct nvmet_ctrl *ctrl = req->sq->ctrl;
        struct nvmf_auth_dhchap_success2_data *data;
        void *d;
        u32 tl;
        u16 status = 0;
        u8 dhchap_status;

        if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
                status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
                req->error_loc =
                        offsetof(struct nvmf_auth_send_command, secp);
                goto done;
        }
        if (req->cmd->auth_send.spsp0 != 0x01) {
                status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
                req->error_loc =
                        offsetof(struct nvmf_auth_send_command, spsp0);
                goto done;
        }
        if (req->cmd->auth_send.spsp1 != 0x01) {
                status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
                req->error_loc =
                        offsetof(struct nvmf_auth_send_command, spsp1);
                goto done;
        }
        tl = nvmet_auth_send_data_len(req);
        if (!tl) {
                status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
                req->error_loc =
                        offsetof(struct nvmf_auth_send_command, tl);
                goto done;
        }
        if (!nvmet_check_transfer_len(req, tl)) {
                pr_debug("%s: transfer length mismatch (%u)\n", __func__, tl);
                return;
        }

        d = kmalloc(tl, GFP_KERNEL);
        if (!d) {
                status = NVME_SC_INTERNAL;
                goto done;
        }

        status = nvmet_copy_from_sgl(req, 0, d, tl);
        if (status)
                goto done_kfree;

        data = d;
        pr_debug("%s: ctrl %d qid %d type %d id %d step %x\n", __func__,
                 ctrl->cntlid, req->sq->qid, data->auth_type, data->auth_id,
                 req->sq->dhchap_step);
        if (data->auth_type != NVME_AUTH_COMMON_MESSAGES &&
            data->auth_type != NVME_AUTH_DHCHAP_MESSAGES)
                goto done_failure1;
        if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) {
                if (data->auth_id == NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) {
                        /* Restart negotiation */
                        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, req->sq);
                                if (dhchap_status) {
                                        pr_err("ctrl %d qid 0 failed to setup re-authentication\n",
                                               ctrl->cntlid);
                                        req->sq->dhchap_status = dhchap_status;
                                        req->sq->dhchap_step =
                                                NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
                                        goto done_kfree;
                                }
                        }
                        req->sq->dhchap_step =
                                NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
                } else if (data->auth_id != req->sq->dhchap_step)
                        goto done_failure1;
                /* Validate negotiation parameters */
                dhchap_status = nvmet_auth_negotiate(req, d);
                if (dhchap_status == 0)
                        req->sq->dhchap_step =
                                NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
                else {
                        req->sq->dhchap_step =
                                NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
                        req->sq->dhchap_status = dhchap_status;
                }
                goto done_kfree;
        }
        if (data->auth_id != req->sq->dhchap_step) {
                pr_debug("%s: ctrl %d qid %d step mismatch (%d != %d)\n",
                         __func__, ctrl->cntlid, req->sq->qid,
                         data->auth_id, req->sq->dhchap_step);
                goto done_failure1;
        }
        if (le16_to_cpu(data->t_id) != req->sq->dhchap_tid) {
                pr_debug("%s: ctrl %d qid %d invalid transaction %d (expected %d)\n",
                         __func__, ctrl->cntlid, req->sq->qid,
                         le16_to_cpu(data->t_id),
                         req->sq->dhchap_tid);
                req->sq->dhchap_step =
                        NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
                req->sq->dhchap_status =
                        NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
                goto done_kfree;
        }

        switch (data->auth_id) {
        case NVME_AUTH_DHCHAP_MESSAGE_REPLY:
                dhchap_status = nvmet_auth_reply(req, d);
                if (dhchap_status == 0)
                        req->sq->dhchap_step =
                                NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
                else {
                        req->sq->dhchap_step =
                                NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
                        req->sq->dhchap_status = dhchap_status;
                }
                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);
                goto done_kfree;
        case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2:
                dhchap_status = nvmet_auth_failure2(d);
                if (dhchap_status) {
                        pr_warn("ctrl %d qid %d: authentication failed (%d)\n",
                                ctrl->cntlid, req->sq->qid, dhchap_status);
                        req->sq->dhchap_status = dhchap_status;
                        req->sq->authenticated = false;
                }
                goto done_kfree;
        default:
                req->sq->dhchap_status =
                        NVME_AUTH_DHCHAP_FAILURE_INCORRECT_MESSAGE;
                req->sq->dhchap_step =
                        NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;
                req->sq->authenticated = false;
                goto done_kfree;
        }
done_failure1:
        req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_MESSAGE;
        req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;

done_kfree:
        kfree(d);
done:
        pr_debug("%s: ctrl %d qid %d dhchap status %x step %x\n", __func__,
                 ctrl->cntlid, req->sq->qid,
                 req->sq->dhchap_status, req->sq->dhchap_step);
        if (status)
                pr_debug("%s: ctrl %d qid %d nvme status %x error loc %d\n",
                         __func__, ctrl->cntlid, req->sq->qid,
                         status, req->error_loc);
        if (req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2 &&
            req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_FAILURE2) {
                unsigned long auth_expire_secs = ctrl->kato ? ctrl->kato : 120;

                mod_delayed_work(system_wq, &req->sq->auth_expired_work,
                                 auth_expire_secs * HZ);
                goto complete;
        }
        /* Final states, clear up variables */
        nvmet_auth_sq_free(req->sq);
        if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)
                nvmet_ctrl_fatal_error(ctrl);

complete:
        nvmet_req_complete(req, status);
}

static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al)
{
        struct nvmf_auth_dhchap_challenge_data *data = d;
        struct nvmet_ctrl *ctrl = req->sq->ctrl;
        int ret = 0;
        int hash_len = nvme_auth_hmac_hash_len(ctrl->shash_id);
        int data_size = sizeof(*d) + hash_len;

        if (ctrl->dh_tfm)
                data_size += ctrl->dh_keysize;
        if (al < data_size) {
                pr_debug("%s: buffer too small (al %d need %d)\n", __func__,
                         al, data_size);
                return -EINVAL;
        }
        memset(data, 0, data_size);
        req->sq->dhchap_s1 = nvme_auth_get_seqnum();
        data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;
        data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
        data->t_id = cpu_to_le16(req->sq->dhchap_tid);
        data->hashid = ctrl->shash_id;
        data->hl = hash_len;
        data->seqnum = cpu_to_le32(req->sq->dhchap_s1);
        req->sq->dhchap_c1 = kmalloc(data->hl, GFP_KERNEL);
        if (!req->sq->dhchap_c1)
                return -ENOMEM;
        get_random_bytes(req->sq->dhchap_c1, data->hl);
        memcpy(data->cval, req->sq->dhchap_c1, data->hl);
        if (ctrl->dh_tfm) {
                data->dhgid = ctrl->dh_gid;
                data->dhvlen = cpu_to_le16(ctrl->dh_keysize);
                ret = nvmet_auth_ctrl_exponential(req, data->cval + data->hl,
                                                  ctrl->dh_keysize);
        }
        pr_debug("%s: ctrl %d qid %d seq %d transaction %d hl %d dhvlen %zu\n",
                 __func__, ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1,
                 req->sq->dhchap_tid, data->hl, ctrl->dh_keysize);
        return ret;
}

static int nvmet_auth_success1(struct nvmet_req *req, void *d, int al)
{
        struct nvmf_auth_dhchap_success1_data *data = d;
        struct nvmet_ctrl *ctrl = req->sq->ctrl;
        int hash_len = nvme_auth_hmac_hash_len(ctrl->shash_id);

        WARN_ON(al < sizeof(*data));
        memset(data, 0, sizeof(*data));
        data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;
        data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
        data->t_id = cpu_to_le16(req->sq->dhchap_tid);
        data->hl = hash_len;
        if (req->sq->dhchap_c2) {
                if (!ctrl->ctrl_key) {
                        pr_warn("ctrl %d qid %d no ctrl key\n",
                                ctrl->cntlid, req->sq->qid);
                        return NVME_AUTH_DHCHAP_FAILURE_FAILED;
                }
                if (nvmet_auth_ctrl_hash(req, data->rval, data->hl))
                        return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
                data->rvalid = 1;
                pr_debug("ctrl %d qid %d response %*ph\n",
                         ctrl->cntlid, req->sq->qid, data->hl, data->rval);
        }
        return 0;
}

static void nvmet_auth_failure1(struct nvmet_req *req, void *d, int al)
{
        struct nvmf_auth_dhchap_failure_data *data = d;

        WARN_ON(al < sizeof(*data));
        data->auth_type = NVME_AUTH_COMMON_MESSAGES;
        data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
        data->t_id = cpu_to_le16(req->sq->dhchap_tid);
        data->rescode = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED;
        data->rescode_exp = req->sq->dhchap_status;
}

u32 nvmet_auth_receive_data_len(struct nvmet_req *req)
{
        return le32_to_cpu(req->cmd->auth_receive.al);
}

void nvmet_execute_auth_receive(struct nvmet_req *req)
{
        struct nvmet_ctrl *ctrl = req->sq->ctrl;
        void *d;
        u32 al;
        u16 status = 0;

        if (req->cmd->auth_receive.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
                status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
                req->error_loc =
                        offsetof(struct nvmf_auth_receive_command, secp);
                goto done;
        }
        if (req->cmd->auth_receive.spsp0 != 0x01) {
                status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
                req->error_loc =
                        offsetof(struct nvmf_auth_receive_command, spsp0);
                goto done;
        }
        if (req->cmd->auth_receive.spsp1 != 0x01) {
                status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
                req->error_loc =
                        offsetof(struct nvmf_auth_receive_command, spsp1);
                goto done;
        }
        al = nvmet_auth_receive_data_len(req);
        if (!al) {
                status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
                req->error_loc =
                        offsetof(struct nvmf_auth_receive_command, al);
                goto done;
        }
        if (!nvmet_check_transfer_len(req, al)) {
                pr_debug("%s: transfer length mismatch (%u)\n", __func__, al);
                return;
        }

        d = kmalloc(al, GFP_KERNEL);
        if (!d) {
                status = NVME_SC_INTERNAL;
                goto done;
        }
        pr_debug("%s: ctrl %d qid %d step %x\n", __func__,
                 ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);
        switch (req->sq->dhchap_step) {
        case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:
                if (nvmet_auth_challenge(req, d, al) < 0) {
                        pr_warn("ctrl %d qid %d: challenge error (%d)\n",
                                ctrl->cntlid, req->sq->qid, status);
                        status = NVME_SC_INTERNAL;
                        break;
                }
                req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_REPLY;
                break;
        case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:
                status = nvmet_auth_success1(req, d, al);
                if (status) {
                        req->sq->dhchap_status = status;
                        req->sq->authenticated = false;
                        nvmet_auth_failure1(req, d, al);
                        pr_warn("ctrl %d qid %d: success1 status (%x)\n",
                                ctrl->cntlid, req->sq->qid,
                                req->sq->dhchap_status);
                        break;
                }
                req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2;
                break;
        case NVME_AUTH_DHCHAP_MESSAGE_FAILURE1:
                req->sq->authenticated = false;
                nvmet_auth_failure1(req, d, al);
                pr_warn("ctrl %d qid %d failure1 (%x)\n",
                        ctrl->cntlid, req->sq->qid, req->sq->dhchap_status);
                break;
        default:
                pr_warn("ctrl %d qid %d unhandled step (%d)\n",
                        ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);
                req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
                req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_FAILED;
                nvmet_auth_failure1(req, d, al);
                status = 0;
                break;
        }

        status = nvmet_copy_to_sgl(req, 0, d, al);
        kfree(d);
done:
        if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2)
                nvmet_auth_sq_free(req->sq);
        else if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) {
                nvmet_auth_sq_free(req->sq);
                nvmet_ctrl_fatal_error(ctrl);
        }
        nvmet_req_complete(req, status);
}