FAILED: patch "[PATCH] nvmet: fix pre-auth out-of-bounds heap read in Discovery Get" failed to apply to 5.15-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.15-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.15.y
git checkout FETCH_HEAD
git cherry-pick -x 53cd102a7a56079b11b897835bd9b94c14e6322c
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026071541-variably-lion-7934@gregkh' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 53cd102a7a56079b11b897835bd9b94c14e6322c Mon Sep 17 00:00:00 2001
From: Bryam Vargas <[email protected]>
Date: Wed, 27 May 2026 15:00:00 -0500
Subject: [PATCH] nvmet: fix pre-auth out-of-bounds heap read in Discovery Get
Log Page
nvmet_execute_disc_get_log_page() validates only the dword alignment of the host-supplied Log Page Offset (lpo). The 64-bit offset is then added to a small kzalloc'd buffer that holds the discovery log page and the result is passed straight to nvmet_copy_to_sgl(), which memcpy()s data_len bytes out to the host with no source-side bound check:
u64 offset = nvmet_get_log_page_offset(req->cmd); /* 64-bit host */
size_t data_len = nvmet_get_log_page_len(req->cmd); /* 32-bit host */
...
if (offset & 0x3) { ... } /* only check */
...
alloc_len = sizeof(*hdr) + entry_size * discovery_log_entries(req);
buffer = kzalloc(alloc_len, GFP_KERNEL);
...
status = nvmet_copy_to_sgl(req, 0, buffer + offset, data_len);The Discovery controller is unauthenticated -- nvmet_host_allowed() returns true unconditionally for the discovery subsystem -- so the call is reachable pre-authentication by any TCP/RDMA/FC peer that can reach the nvmet target. With a discovery log page of ~1 KiB, an attacker requesting up to 4 KiB starting at offset == alloc_len reads the next slab page out and gets its content returned over the fabric (an empirical run on a default nvmet-tcp loopback target leaked 81 canonical kernel pointers in one Get Log Page response). Pointing the offset at unmapped kernel memory faults the in-kernel memcpy and crashes (or panics, on panic_on_oops=1) the target host instead.
The attacker-controlled source-side offset pattern "nvmet_copy_to_sgl(req, 0, buffer + ATTACKER_OFFSET, ...)" is unique to nvmet_execute_disc_get_log_page in the entire nvmet codebase: every other Get Log Page handler in admin-cmd.c either ignores lpo (and silently starts every response at offset 0) or tracks a local destination offset with a fixed source pointer.
Validate the host-supplied offset against the log page size, cap the copy length to what is actually available, and zero-fill any remainder of the host transfer buffer. The zero-fill matches the existing short-response pattern in nvmet_execute_get_log_changed_ns() (admin-cmd.c) and prevents leaking transport SGL contents when the host asks for more bytes than the log page contains.
drivers/nvme/target/discovery.c +22 -1
@@ -166,6 +166,7 @@ static void nvmet_execute_disc_get_log_page(struct nvmet_req *req)u64 offset =nvmet_get_log_page_offset (req->cmd);size_t data_len =nvmet_get_log_page_len (req->cmd);size_t alloc_len; +size_t copy_len;struct nvmet_subsys_link *p;struct nvmet_port *r;u32 numrec =0 ;
@@ -242,7 +243,27 @@ static void nvmet_execute_disc_get_log_page(struct nvmet_req *req)up_read (&nvmet_config_sem); - status =nvmet_copy_to_sgl (req,0 , buffer + offset, data_len); +/* +* Validate the host-supplied log page offset before copying out. +* Without this check, the host controls a 64-bit byte offset into +* a small kzalloc'd buffer: a value past the log page lets the +* subsequent memcpy read adjacent kernel heap, and a value aimed +* at unmapped kernel memory faults the in-kernel copy and crashes +* the target host. The Discovery controller is unauthenticated, +* so the bug is reachable from any reachable fabric peer. +*/ +if (offset > alloc_len) { + req->error_loc = + offsetof(struct nvme_get_log_page_command , lpo); + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; + goto out_free_buffer; + } + + copy_len =min_t (size_t, data_len, alloc_len - offset); + status =nvmet_copy_to_sgl (req,0 , buffer + offset, copy_len); +if (!status && copy_len < data_len) + status =nvmet_zero_sgl (req, copy_len, data_len - copy_len); +out_free_buffer:kfree (buffer); out:nvmet_req_complete (req, status);
Sasha Levin
maintainerPATCH 1/3 nvme: fix status magic numbers
From: Weiwen Hu <[email protected]>
[ Upstream commit d89a5c6705998ddc42b104f8eabd3c4b9e8fde08 ]
Replaced some magic numbers about SC and SCT with enum and macro.
Stable-dep-of: 53cd102a7a56 ("nvmet: fix pre-auth out-of-bounds heap read in Discovery Get Log Page")
--- drivers/nvme/host/core.c | 4 ++-- drivers/nvme/host/multipath.c | 2 +- drivers/nvme/host/nvme.h | 4 ++-- include/linux/nvme.h | 14 ++++++++++++-- 4 files changed, 17 insertions(+), 7 deletions(-)
drivers/nvme/host/core.c +2 -2
@@ -262,7 +262,7 @@ static void nvme_delete_ctrl_sync(struct nvme_ctrl *ctrl)static blk_status_t nvme_error_status (u16 status) { -switch (status &0x7ff ) { +switch (status &NVME_SCT_SC_MASK ) {case NVME_SC_SUCCESS :return BLK_STS_OK ;case NVME_SC_CAP_EXCEEDED :
@@ -1248,7 +1248,7 @@ EXPORT_SYMBOL_NS_GPL(nvme_execute_passthru_rq, NVME_TARGET_PASSTHRU);/* * Recommended frequency for KATO commands per NVMe 1.4 section 7.12.1: -* +* * The host should send Keep Alive commands at half of the Keep Alive Timeout * accounting for transport roundtrip times [..]. */
drivers/nvme/host/multipath.c +1 -1
@@ -68,7 +68,7 @@ bool nvme_mpath_set_disk_name(struct nvme_ns *ns, char *disk_name, int *flags)void nvme_failover_req (struct request *req) {struct nvme_ns *ns = req->q->queuedata; -u16 status =nvme_req (req)->status &0x7ff ; +u16 status =nvme_req (req)->status &NVME_SCT_SC_MASK ;unsigned long flags;struct bio *bio;
drivers/nvme/host/nvme.h +2 -2
@@ -615,7 +615,7 @@ static inline u32 nvme_bytes_to_numd(size_t len)static inline bool nvme_is_ana_error (u16 status) { -switch (status &0x7ff ) { +switch (status &NVME_SCT_SC_MASK ) {case NVME_SC_ANA_TRANSITION :case NVME_SC_ANA_INACCESSIBLE :case NVME_SC_ANA_PERSISTENT_LOSS :
@@ -628,7 +628,7 @@ static inline bool nvme_is_ana_error(u16 status)static inline bool nvme_is_path_error (u16 status) {/* check for a status code type of 'path related status' */ -return (status &0x700 ) ==0x300 ; +return (status &NVME_SCT_MASK ) ==NVME_SCT_PATH ; }/*
include/linux/nvme.h +12 -2
@@ -1485,6 +1485,7 @@ enum {/* * Generic Command Status: */ +NVME_SCT_GENERIC =0x0 ,NVME_SC_SUCCESS =0x0 ,NVME_SC_INVALID_OPCODE =0x1 , NVME_SC_INVALID_FIELD = 0x2,
@@ -1531,6 +1532,7 @@ enum {/* * Command Specific Status: */ +NVME_SCT_COMMAND_SPECIFIC =0x100 ,NVME_SC_CQ_INVALID =0x100 ,NVME_SC_QID_INVALID =0x101 , NVME_SC_QUEUE_SIZE = 0x102,
@@ -1604,6 +1606,7 @@ enum {/* * Media and Data Integrity Errors: */ +NVME_SCT_MEDIA_ERROR =0x200 ,NVME_SC_WRITE_FAULT =0x280 ,NVME_SC_READ_ERROR =0x281 , NVME_SC_GUARD_CHECK = 0x282,
@@ -1616,16 +1619,23 @@ enum {/* * Path-related Errors: */ +NVME_SCT_PATH =0x300 ,NVME_SC_ANA_PERSISTENT_LOSS =0x301 ,NVME_SC_ANA_INACCESSIBLE =0x302 ,NVME_SC_ANA_TRANSITION =0x303 ,NVME_SC_HOST_PATH_ERROR =0x370 ,NVME_SC_HOST_ABORTED_CMD =0x371 , -NVME_SC_CRD =0x1800 , - NVME_SC_DNR = 0x4000, +NVME_SC_MASK =0x00ff ,/* Status Code */ +NVME_SCT_MASK =0x0700 ,/* Status Code Type */ +NVME_SCT_SC_MASK =NVME_SCT_MASK |NVME_SC_MASK , + +NVME_SC_CRD =0x1800 ,/* Command Retry Delayed */ + NVME_SC_DNR = 0x4000, /* Do Not Retry */ }; +#define NVME_SCT (status) ((status) >> 8 & 7) + struct nvme_completion {/* * Used by Admin and Fabrics commands to return data:
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/3 nvme: rename CDR/MORE/DNR to NVME_STATUS_*
From: Weiwen Hu <[email protected]>
[ Upstream commit dd0b0a4a2c5d7209457dc172997d1243ad269cfa ]
CDR/MORE/DNR fields are not belonging to SC in the NVMe spec, rename them to NVME_STATUS_* to avoid confusion.
Stable-dep-of: 53cd102a7a56 ("nvmet: fix pre-auth out-of-bounds heap read in Discovery Get Log Page")
--- drivers/nvme/host/core.c | 12 ++++----- drivers/nvme/host/fabrics.c | 8 +++--- drivers/nvme/host/fault_inject.c | 2 +- drivers/nvme/host/fc.c | 8 +++--- drivers/nvme/target/admin-cmd.c | 20 +++++++-------- drivers/nvme/target/core.c | 42 +++++++++++++++---------------- drivers/nvme/target/discovery.c | 14 +++++------ drivers/nvme/target/fabrics-cmd.c | 32 +++++++++++------------ drivers/nvme/target/io-cmd-bdev.c | 12 ++++----- drivers/nvme/target/nvmet.h | 2 +- drivers/nvme/target/passthru.c | 10 ++++---- drivers/nvme/target/rdma.c | 10 ++++---- drivers/nvme/target/tcp.c | 4 +-- drivers/nvme/target/zns.c | 30 +++++++++++----------- include/linux/nvme.h | 4 +-- 15 files changed, 105 insertions(+), 105 deletions(-)
drivers/nvme/host/core.c +6 -6
@@ -308,7 +308,7 @@ static void nvme_retry_req(struct request *req)u16 crd;/* The mask and shift result must be <= 3 */ - crd = (nvme_req (req)->status &NVME_SC_CRD ) >>11 ; + crd = (nvme_req (req)->status &NVME_STATUS_CRD ) >>11 ;if (crd) delay =nvme_req (req)->ctrl->crdt[crd -1 ] *100 ;
@@ -329,7 +329,7 @@ static inline enum nvme_disposition nvme_decide_disposition(struct request *req)return COMPLETE ;if (blk_noretry_request (req) || - (nvme_req (req)->status &NVME_SC_DNR ) || + (nvme_req (req)->status &NVME_STATUS_DNR ) ||nvme_req (req)->retries >= nvme_max_retries)return COMPLETE ;
@@ -1519,7 +1519,7 @@ static int nvme_identify_ns(struct nvme_ctrl *ctrl, unsigned nsid, goto out_free_id; } - error =NVME_SC_INVALID_NS |NVME_SC_DNR ; + error =NVME_SC_INVALID_NS |NVME_STATUS_DNR ;if ((*id)->ncap ==0 )/* namespace not allocated or attached */ goto out_free_id;
@@ -4015,7 +4015,7 @@ static void nvme_ns_remove_by_nsid(struct nvme_ctrl *ctrl, u32 nsid)static void nvme_validate_ns (struct nvme_ns *ns,struct nvme_ns_ids *ids) {struct nvme_id_ns *id; -int ret =NVME_SC_INVALID_NS |NVME_SC_DNR ; +int ret =NVME_SC_INVALID_NS |NVME_STATUS_DNR ;if (test_bit (NVME_NS_DEAD , &ns->flags)) goto out;
@@ -4024,7 +4024,7 @@ static void nvme_validate_ns(struct nvme_ns *ns, struct nvme_ns_ids *ids)if (ret) goto out; - ret =NVME_SC_INVALID_NS |NVME_SC_DNR ; + ret =NVME_SC_INVALID_NS |NVME_STATUS_DNR ;if (!nvme_ns_ids_equal (&ns->head->ids, ids)) {dev_err (ns->ctrl->device,"identifiers changed for nsid %d\n" , ns->head->ns_id);
@@ -4042,7 +4042,7 @@ static void nvme_validate_ns(struct nvme_ns *ns, struct nvme_ns_ids *ids)* * TODO: we should probably schedule a delayed retry here. */ -if (ret >0 && (ret &NVME_SC_DNR )) +if (ret >0 && (ret &NVME_STATUS_DNR ))nvme_ns_remove (ns); }
drivers/nvme/host/fabrics.c +4 -4
@@ -161,7 +161,7 @@ int nvmf_reg_read32(struct nvme_ctrl *ctrl, u32 off, u32 *val)if (unlikely (ret !=0 ))dev_err (ctrl->device,"Property Get error: %d, offset %#x\n" , - ret >0 ? ret & ~NVME_SC_DNR : ret, off); + ret >0 ? ret & ~NVME_STATUS_DNR : ret, off);return ret; }
@@ -207,7 +207,7 @@ int nvmf_reg_read64(struct nvme_ctrl *ctrl, u32 off, u64 *val)if (unlikely (ret !=0 ))dev_err (ctrl->device,"Property Get error: %d, offset %#x\n" , - ret >0 ? ret & ~NVME_SC_DNR : ret, off); + ret >0 ? ret & ~NVME_STATUS_DNR : ret, off);return ret; }EXPORT_SYMBOL_GPL (nvmf_reg_read64);
@@ -249,7 +249,7 @@ int nvmf_reg_write32(struct nvme_ctrl *ctrl, u32 off, u32 val)if (unlikely (ret))dev_err (ctrl->device,"Property Set error: %d, offset %#x\n" , - ret >0 ? ret & ~NVME_SC_DNR : ret, off); + ret >0 ? ret & ~NVME_STATUS_DNR : ret, off);return ret; }EXPORT_SYMBOL_GPL (nvmf_reg_write32);
@@ -284,7 +284,7 @@ static void nvmf_log_connect_error(struct nvme_ctrl *ctrl, int errval, int offset, struct nvme_command *cmd, struct nvmf_connect_data *data) { - int err_sctype = errval & ~NVME_SC_DNR; + int err_sctype = errval & ~NVME_STATUS_DNR;switch (err_sctype) {case (NVME_SC_CONNECT_INVALID_PARAM ):
drivers/nvme/host/fault_inject.c +1 -1
@@ -75,7 +75,7 @@ void nvme_should_fail(struct request *req)/* inject status code and DNR bit */ status = fault_inject->status;if (fault_inject->dont_retry) - status |=NVME_SC_DNR ; + status |=NVME_STATUS_DNR ;nvme_req (req)->status = status; } }
drivers/nvme/host/fc.c +4 -4
@@ -3108,7 +3108,7 @@ nvme_fc_create_association(struct nvme_fc_ctrl *ctrl)if (ctrl->ctrl.icdoff) {dev_err (ctrl->ctrl.device,"icdoff %d is not supported!\n" , ctrl->ctrl.icdoff); - ret =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + ret =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; goto out_disconnect_admin_queue; }
@@ -3116,7 +3116,7 @@ nvme_fc_create_association(struct nvme_fc_ctrl *ctrl)if (!nvme_ctrl_sgl_supported (&ctrl->ctrl)) {dev_err (ctrl->ctrl.device,"Mandatory sgls are not supported!\n" ); - ret =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + ret =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; goto out_disconnect_admin_queue; }
@@ -3287,7 +3287,7 @@ nvme_fc_reconnect_or_delete(struct nvme_fc_ctrl *ctrl, int status)dev_info (ctrl->ctrl.device,"NVME-FC{%d}: reset: Reconnect attempt failed (%d)\n" , ctrl->cnum, status); -if (status >0 && (status &NVME_SC_DNR )) +if (status >0 && (status &NVME_STATUS_DNR )) recon = false; }else if (time_after_eq (jiffies, rport->dev_loss_end)) recon = false;
@@ -3304,7 +3304,7 @@ nvme_fc_reconnect_or_delete(struct nvme_fc_ctrl *ctrl, int status)queue_delayed_work (nvme_wq, &ctrl->connect_work, recon_delay); }else {if (portptr->port_state ==FC_OBJSTATE_ONLINE ) { -if (status >0 && (status &NVME_SC_DNR )) +if (status >0 && (status &NVME_STATUS_DNR ))dev_warn (ctrl->ctrl.device,"NVME-FC{%d}: reconnect failure\n" , ctrl->cnum);
drivers/nvme/target/admin-cmd.c +10 -10
@@ -342,7 +342,7 @@ static void nvmet_execute_get_log_page(struct nvmet_req *req)pr_debug ("unhandled lid %d on qid %d\n" , req->cmd->get_log_page.lid, req->sq->qid); req->error_loc = offsetof(struct nvme_get_log_page_command , lid); -nvmet_req_complete (req,NVME_SC_INVALID_FIELD |NVME_SC_DNR ); +nvmet_req_complete (req,NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ); }static void nvmet_execute_identify_ctrl (struct nvmet_req *req)
@@ -491,7 +491,7 @@ static void nvmet_execute_identify_ns(struct nvmet_req *req)if (le32_to_cpu (req->cmd->identify.nsid) ==NVME_NSID_ALL ) { req->error_loc = offsetof(struct nvme_identify , nsid); - status =NVME_SC_INVALID_NS |NVME_SC_DNR ; + status =NVME_SC_INVALID_NS |NVME_STATUS_DNR ; goto out; }
@@ -657,7 +657,7 @@ static void nvmet_execute_identify_desclist(struct nvmet_req *req)if (sg_zero_buffer (req->sg, req->sg_cnt,NVME_IDENTIFY_DATA_SIZE - off, off) !=NVME_IDENTIFY_DATA_SIZE - off) - status =NVME_SC_INTERNAL |NVME_SC_DNR ; + status =NVME_SC_INTERNAL |NVME_STATUS_DNR ; out:nvmet_req_complete (req, status);
@@ -814,7 +814,7 @@ u16 nvmet_set_feat_async_event(struct nvmet_req *req, u32 mask)if (val32 & ~mask) { req->error_loc = offsetof(struct nvme_common_command , cdw11); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }WRITE_ONCE (req->sq->ctrl->aen_enabled, val32);
@@ -840,7 +840,7 @@ void nvmet_execute_set_features(struct nvmet_req *req) ncqr = (cdw11 >>16 ) &0xffff ; nsqr = cdw11 &0xffff ;if (ncqr ==0xffff || nsqr ==0xffff ) { - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;break ; } nvmet_set_result(req,
@@ -853,14 +853,14 @@ void nvmet_execute_set_features(struct nvmet_req *req) status =nvmet_set_feat_async_event (req,NVMET_AEN_CFG_ALL );break ;case NVME_FEAT_HOST_ID : - status =NVME_SC_CMD_SEQ_ERROR |NVME_SC_DNR ; + status =NVME_SC_CMD_SEQ_ERROR |NVME_STATUS_DNR ;break ;case NVME_FEAT_WRITE_PROTECT : status =nvmet_set_feat_write_protect (req);break ;default : req->error_loc = offsetof(struct nvme_common_command , cdw10); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;break ; }
@@ -946,7 +946,7 @@ void nvmet_execute_get_features(struct nvmet_req *req)if (!(req->cmd->common.cdw11 &cpu_to_le32 (1 <<0 ))) { req->error_loc = offsetof(struct nvme_common_command , cdw11); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;break ; }
@@ -959,7 +959,7 @@ void nvmet_execute_get_features(struct nvmet_req *req)default : req->error_loc = offsetof(struct nvme_common_command , cdw10); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;break ; }
@@ -976,7 +976,7 @@ void nvmet_execute_async_event(struct nvmet_req *req)mutex_lock (&ctrl->lock);if (ctrl->nr_async_event_cmds >=NVMET_ASYNC_EVENTS ) {mutex_unlock (&ctrl->lock); -nvmet_req_complete (req,NVME_SC_ASYNC_LIMIT |NVME_SC_DNR ); +nvmet_req_complete (req,NVME_SC_ASYNC_LIMIT |NVME_STATUS_DNR );return ; } ctrl->async_event_cmds[ctrl->nr_async_event_cmds++] = req;
drivers/nvme/target/core.c +21 -21
157 lines not on this page · raw message
drivers/nvme/target/discovery.c +7 -7
@@ -178,7 +178,7 @@ static void nvmet_execute_disc_get_log_page(struct nvmet_req *req)if (req->cmd->get_log_page.lid !=NVME_LOG_DISC ) { req->error_loc = offsetof(struct nvme_get_log_page_command , lid); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; goto out; }
@@ -186,7 +186,7 @@ static void nvmet_execute_disc_get_log_page(struct nvmet_req *req)if (offset &0x3 ) { req->error_loc = offsetof(struct nvme_get_log_page_command , lpo); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; goto out; }
@@ -251,7 +251,7 @@ static void nvmet_execute_disc_identify(struct nvmet_req *req)if (req->cmd->identify.cns !=NVME_ID_CNS_CTRL ) { req->error_loc = offsetof(struct nvme_identify , cns); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; goto out; }
@@ -313,7 +313,7 @@ static void nvmet_execute_disc_set_features(struct nvmet_req *req)default : req->error_loc = offsetof(struct nvme_common_command , cdw10); - stat =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + stat =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;break ; }
@@ -338,7 +338,7 @@ static void nvmet_execute_disc_get_features(struct nvmet_req *req)default : req->error_loc = offsetof(struct nvme_common_command , cdw10); - stat =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + stat =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;break ; }
@@ -354,7 +354,7 @@ u16 nvmet_parse_discovery_cmd(struct nvmet_req *req) cmd->common.opcode); req->error_loc = offsetof(struct nvme_common_command , opcode); -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ; }switch (cmd->common.opcode) {
@@ -379,7 +379,7 @@ u16 nvmet_parse_discovery_cmd(struct nvmet_req *req)default :pr_debug ("unhandled cmd %d\n" , cmd->common.opcode); req->error_loc = offsetof(struct nvme_common_command , opcode); -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ; } }
drivers/nvme/target/fabrics-cmd.c +16 -16
@@ -18,7 +18,7 @@ static void nvmet_execute_prop_set(struct nvmet_req *req)if (req->cmd->prop_set.attrib &1 ) { req->error_loc = offsetof(struct nvmf_property_set_command , attrib); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; goto out; }
@@ -29,7 +29,7 @@ static void nvmet_execute_prop_set(struct nvmet_req *req)default : req->error_loc = offsetof(struct nvmf_property_set_command , offset); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; } out:nvmet_req_complete (req, status);
@@ -50,7 +50,7 @@ static void nvmet_execute_prop_get(struct nvmet_req *req) val = ctrl->cap;break ;default : - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;break ; } }else {
@@ -65,7 +65,7 @@ static void nvmet_execute_prop_get(struct nvmet_req *req) val = ctrl->csts;break ;default : - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;break ; } }
@@ -97,7 +97,7 @@ u16 nvmet_parse_fabrics_cmd(struct nvmet_req *req)pr_debug ("received unknown capsule type 0x%x\n" , cmd->fabrics.fctype); req->error_loc = offsetof(struct nvmf_common_command , fctype); -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ; }return 0 ;
@@ -116,14 +116,14 @@ static u16 nvmet_install_queue(struct nvmet_ctrl *ctrl, struct nvmet_req *req)pr_warn ("queue size zero!\n" ); req->error_loc = offsetof(struct nvmf_connect_command , sqsize); req->cqe->result.u32 =IPO_IATTR_CONNECT_SQE (sqsize); - ret =NVME_SC_CONNECT_INVALID_PARAM |NVME_SC_DNR ; + ret =NVME_SC_CONNECT_INVALID_PARAM |NVME_STATUS_DNR ; goto err; }if (ctrl->sqs[qid] !=NULL ) {pr_warn ("qid %u has already been created\n" , qid); req->error_loc = offsetof(struct nvmf_connect_command , qid); -return NVME_SC_CMD_SEQ_ERROR |NVME_SC_DNR ; +return NVME_SC_CMD_SEQ_ERROR |NVME_STATUS_DNR ; }if (sqsize > mqes) {
@@ -131,14 +131,14 @@ static u16 nvmet_install_queue(struct nvmet_ctrl *ctrl, struct nvmet_req *req) sqsize, mqes, ctrl->cntlid); req->error_loc = offsetof(struct nvmf_connect_command , sqsize); req->cqe->result.u32 =IPO_IATTR_CONNECT_SQE (sqsize); -return NVME_SC_CONNECT_INVALID_PARAM |NVME_SC_DNR ; +return NVME_SC_CONNECT_INVALID_PARAM |NVME_STATUS_DNR ; } old =cmpxchg (&req->sq->ctrl,NULL , ctrl);if (old) {pr_warn ("queue already connected!\n" ); req->error_loc = offsetof(struct nvmf_connect_command , opcode); -return NVME_SC_CONNECT_CTRL_BUSY |NVME_SC_DNR ; +return NVME_SC_CONNECT_CTRL_BUSY |NVME_STATUS_DNR ; }/* note: convert queue size from 0's-based value to 1's-based value */
@@ -191,14 +191,14 @@ static void nvmet_execute_admin_connect(struct nvmet_req *req)pr_warn ("invalid connect version (%d).\n" ,le16_to_cpu (c->recfmt)); req->error_loc = offsetof(struct nvmf_connect_command , recfmt); - status =NVME_SC_CONNECT_FORMAT |NVME_SC_DNR ; + status =NVME_SC_CONNECT_FORMAT |NVME_STATUS_DNR ; goto out; }if (unlikely (d->cntlid !=cpu_to_le16 (0xffff ))) {pr_warn ("connect attempt for invalid controller ID %#x\n" , d->cntlid); - status =NVME_SC_CONNECT_INVALID_PARAM |NVME_SC_DNR ; + status =NVME_SC_CONNECT_INVALID_PARAM |NVME_STATUS_DNR ; req->cqe->result.u32 =IPO_IATTR_CONNECT_DATA (cntlid); goto out; }
@@ -255,7 +255,7 @@ static void nvmet_execute_io_connect(struct nvmet_req *req)if (c->recfmt !=0 ) {pr_warn ("invalid connect version (%d).\n" ,le16_to_cpu (c->recfmt)); - status =NVME_SC_CONNECT_FORMAT |NVME_SC_DNR ; + status =NVME_SC_CONNECT_FORMAT |NVME_STATUS_DNR ; goto out; }
@@ -264,13 +264,13 @@ static void nvmet_execute_io_connect(struct nvmet_req *req) ctrl =nvmet_ctrl_find_get (d->subsysnqn, d->hostnqn,le16_to_cpu (d->cntlid), req);if (!ctrl) { - status =NVME_SC_CONNECT_INVALID_PARAM |NVME_SC_DNR ; + status =NVME_SC_CONNECT_INVALID_PARAM |NVME_STATUS_DNR ; goto out; }if (unlikely (qid > ctrl->subsys->max_qid)) {pr_warn ("invalid queue id (%d)\n" , qid); - status =NVME_SC_CONNECT_INVALID_PARAM |NVME_SC_DNR ; + status =NVME_SC_CONNECT_INVALID_PARAM |NVME_STATUS_DNR ; req->cqe->result.u32 =IPO_IATTR_CONNECT_SQE (qid); goto out_ctrl_put; }
@@ -303,13 +303,13 @@ u16 nvmet_parse_connect_cmd(struct nvmet_req *req)pr_debug ("invalid command 0x%x on unconnected queue.\n" , cmd->fabrics.opcode); req->error_loc = offsetof(struct nvme_common_command , opcode); -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ; }if (cmd->fabrics.fctype != nvme_fabrics_type_connect) {pr_debug ("invalid capsule type 0x%x on unconnected queue.\n" , cmd->fabrics.fctype); req->error_loc = offsetof(struct nvmf_common_command , fctype); -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ; }if (cmd->connect.qid ==0 )
drivers/nvme/target/io-cmd-bdev.c +6 -6
@@ -124,11 +124,11 @@ u16 blk_to_nvme_status(struct nvmet_req *req, blk_status_t blk_sts)*/ switch (blk_sts) {case BLK_STS_NOSPC : - status =NVME_SC_CAP_EXCEEDED |NVME_SC_DNR ; + status =NVME_SC_CAP_EXCEEDED |NVME_STATUS_DNR ; req->error_loc = offsetof(struct nvme_rw_command , length);break ;case BLK_STS_TARGET : - status =NVME_SC_LBA_RANGE |NVME_SC_DNR ; + status =NVME_SC_LBA_RANGE |NVME_STATUS_DNR ; req->error_loc = offsetof(struct nvme_rw_command , slba);break ;case BLK_STS_NOTSUPP :
@@ -136,10 +136,10 @@ u16 blk_to_nvme_status(struct nvmet_req *req, blk_status_t blk_sts)switch (req->cmd->common.opcode) {case nvme_cmd_dsm:case nvme_cmd_write_zeroes: - status =NVME_SC_ONCS_NOT_SUPPORTED |NVME_SC_DNR ; + status =NVME_SC_ONCS_NOT_SUPPORTED |NVME_STATUS_DNR ;break ;default : - status =NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; + status =NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ; }break ;case BLK_STS_MEDIUM :
@@ -148,7 +148,7 @@ u16 blk_to_nvme_status(struct nvmet_req *req, blk_status_t blk_sts)break ;case BLK_STS_IOERR :default : - status =NVME_SC_INTERNAL |NVME_SC_DNR ; + status =NVME_SC_INTERNAL |NVME_STATUS_DNR ; req->error_loc = offsetof(struct nvme_common_command , opcode); }
@@ -339,7 +339,7 @@ static void nvmet_bdev_execute_flush(struct nvmet_req *req)u16 nvmet_bdev_flush (struct nvmet_req *req) {if (blkdev_issue_flush (req->ns->bdev)) -return NVME_SC_INTERNAL |NVME_SC_DNR ; +return NVME_SC_INTERNAL |NVME_STATUS_DNR ;return 0 ; }
drivers/nvme/target/nvmet.h +1 -1
@@ -653,7 +653,7 @@ static inline void nvmet_req_cns_error_complete(struct nvmet_req *req)pr_debug ("unhandled identify cns %d on qid %d\n" , req->cmd->identify.cns, req->sq->qid); req->error_loc = offsetof(struct nvme_identify , cns); -nvmet_req_complete (req,NVME_SC_INVALID_FIELD |NVME_SC_DNR ); +nvmet_req_complete (req,NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ); }static inline void nvmet_req_bio_put (struct nvmet_req *req,struct bio *bio)
drivers/nvme/target/passthru.c +5 -5
@@ -243,7 +243,7 @@ static void nvmet_passthru_execute_cmd(struct nvmet_req *req) ns =nvme_find_get_ns (ctrl, nsid);if (unlikely (!ns)) {pr_err ("failed to get passthru ns nsid:%u\n" , nsid); - status =NVME_SC_INVALID_NS |NVME_SC_DNR ; + status =NVME_SC_INVALID_NS |NVME_STATUS_DNR ; goto out; }
@@ -362,7 +362,7 @@ u16 nvmet_parse_passthru_io_cmd(struct nvmet_req *req)* emulated in the future if regular targets grow support for * this feature. */ -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ; }return nvmet_setup_passthru_command (req);
@@ -414,7 +414,7 @@ static u16 nvmet_passthru_get_set_features(struct nvmet_req *req)case NVME_FEAT_RESV_PERSIST :/* No reservations, see nvmet_parse_passthru_io_cmd() */ default : -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ; } }
@@ -482,7 +482,7 @@ u16 nvmet_parse_passthru_admin_cmd(struct nvmet_req *req) req->p.use_workqueue = true;return NVME_SC_SUCCESS ; } -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ;case NVME_ID_CNS_NS : req->execute = nvmet_passthru_execute_cmd; req->p.use_workqueue = true;
@@ -494,7 +494,7 @@ u16 nvmet_parse_passthru_admin_cmd(struct nvmet_req *req) req->p.use_workqueue = true;return NVME_SC_SUCCESS ; } -return NVME_SC_INVALID_OPCODE |NVME_SC_DNR ; +return NVME_SC_INVALID_OPCODE |NVME_STATUS_DNR ;default :return nvmet_setup_passthru_command (req); }
drivers/nvme/target/rdma.c +5 -5
@@ -850,12 +850,12 @@ static u16 nvmet_rdma_map_sgl_inline(struct nvmet_rdma_rsp *rsp)if (!nvme_is_write (rsp->req.cmd)) { rsp->req.error_loc = offsetof(struct nvme_common_command , opcode); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }if (off + len > rsp->queue->dev->inline_data_size) {pr_err ("invalid inline data offset!\n" ); -return NVME_SC_SGL_INVALID_OFFSET |NVME_SC_DNR ; +return NVME_SC_SGL_INVALID_OFFSET |NVME_STATUS_DNR ; }/* no data command? */
@@ -919,7 +919,7 @@ static u16 nvmet_rdma_map_sgl(struct nvmet_rdma_rsp *rsp)pr_err ("invalid SGL subtype: %#x\n" , sgl->type); rsp->req.error_loc = offsetof(struct nvme_common_command , dptr); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }case NVME_KEY_SGL_FMT_DATA_DESC :switch (sgl->type &0xf ) {
@@ -931,12 +931,12 @@ static u16 nvmet_rdma_map_sgl(struct nvmet_rdma_rsp *rsp)pr_err ("invalid SGL subtype: %#x\n" , sgl->type); rsp->req.error_loc = offsetof(struct nvme_common_command , dptr); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }default :pr_err ("invalid SGL type: %#x\n" , sgl->type); rsp->req.error_loc = offsetof(struct nvme_common_command , dptr); -return NVME_SC_SGL_INVALID_TYPE |NVME_SC_DNR ; +return NVME_SC_SGL_INVALID_TYPE |NVME_STATUS_DNR ; } }
drivers/nvme/target/tcp.c +2 -2
@@ -384,10 +384,10 @@ static int nvmet_tcp_map_data(struct nvmet_tcp_cmd *cmd)if (sgl->type == ((NVME_SGL_FMT_DATA_DESC <<4 ) |NVME_SGL_FMT_OFFSET )) {if (!nvme_is_write (cmd->req.cmd)) -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ;if (len > cmd->req.port->inline_data_size) -return NVME_SC_SGL_INVALID_OFFSET |NVME_SC_DNR ; +return NVME_SC_SGL_INVALID_OFFSET |NVME_STATUS_DNR ; cmd->pdu_len = len; } cmd->req.transfer_len += len;
drivers/nvme/target/zns.c +15 -15
@@ -104,7 +104,7 @@ void nvmet_execute_identify_cns_cs_ns(struct nvmet_req *req)if (le32_to_cpu (req->cmd->identify.nsid) ==NVME_NSID_ALL ) { req->error_loc = offsetof(struct nvme_identify , nsid); - status =NVME_SC_INVALID_NS |NVME_SC_DNR ; + status =NVME_SC_INVALID_NS |NVME_STATUS_DNR ; goto out; }
@@ -125,7 +125,7 @@ void nvmet_execute_identify_cns_cs_ns(struct nvmet_req *req) }if (!bdev_is_zoned (req->ns->bdev)) { - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; req->error_loc = offsetof(struct nvme_identify , nsid); goto out; }
@@ -162,17 +162,17 @@ static u16 nvmet_bdev_validate_zone_mgmt_recv(struct nvmet_req *req)if (sect >=get_capacity (req->ns->bdev->bd_disk)) { req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd , slba); -return NVME_SC_LBA_RANGE |NVME_SC_DNR ; +return NVME_SC_LBA_RANGE |NVME_STATUS_DNR ; }if (out_bufsize <sizeof (struct nvme_zone_report )) { req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd , numd); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }if (req->cmd->zmr.zra !=NVME_ZRA_ZONE_REPORT ) { req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd , zra); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }switch (req->cmd->zmr.pr) {
@@ -181,7 +181,7 @@ static u16 nvmet_bdev_validate_zone_mgmt_recv(struct nvmet_req *req)break ;default : req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd , pr); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }switch (req->cmd->zmr.zrasf) {
@@ -197,7 +197,7 @@ static u16 nvmet_bdev_validate_zone_mgmt_recv(struct nvmet_req *req)default : req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd , zrasf); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }return NVME_SC_SUCCESS ;
@@ -346,7 +346,7 @@ static u16 blkdev_zone_mgmt_errno_to_nvme_status(int ret)return NVME_SC_SUCCESS ;case -EINVAL :case -EIO : -return NVME_SC_ZONE_INVALID_TRANSITION |NVME_SC_DNR ; +return NVME_SC_ZONE_INVALID_TRANSITION |NVME_STATUS_DNR ;default :return NVME_SC_INTERNAL ; }
@@ -470,7 +470,7 @@ static u16 nvmet_bdev_execute_zmgmt_send_all(struct nvmet_req *req)default :/* this is needed to quiet compiler warning */ req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd , zsa); -return NVME_SC_INVALID_FIELD |NVME_SC_DNR ; +return NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; }return NVME_SC_SUCCESS ;
@@ -488,7 +488,7 @@ static void nvmet_bdev_zmgmt_send_work(struct work_struct *w)if (op ==REQ_OP_LAST ) { req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd , zsa); - status =NVME_SC_ZONE_INVALID_TRANSITION |NVME_SC_DNR ; + status =NVME_SC_ZONE_INVALID_TRANSITION |NVME_STATUS_DNR ; goto out; }
@@ -500,13 +500,13 @@ static void nvmet_bdev_zmgmt_send_work(struct work_struct *w)if (sect >=get_capacity (bdev->bd_disk)) { req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd , slba); - status =NVME_SC_LBA_RANGE |NVME_SC_DNR ; + status =NVME_SC_LBA_RANGE |NVME_STATUS_DNR ; goto out; }if (sect & (zone_sectors -1 )) { req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd , slba); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; goto out; }
@@ -557,13 +557,13 @@ void nvmet_bdev_execute_zone_append(struct nvmet_req *req)if (sect >=get_capacity (req->ns->bdev->bd_disk)) { req->error_loc = offsetof(struct nvme_rw_command , slba); - status =NVME_SC_LBA_RANGE |NVME_SC_DNR ; + status =NVME_SC_LBA_RANGE |NVME_STATUS_DNR ; goto out; }if (sect & (bdev_zone_sectors (req->ns->bdev) -1 )) { req->error_loc = offsetof(struct nvme_rw_command , slba); - status =NVME_SC_INVALID_FIELD |NVME_SC_DNR ; + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; goto out; }
@@ -597,7 +597,7 @@ void nvmet_bdev_execute_zone_append(struct nvmet_req *req) }if (total_len !=nvmet_rw_data_len (req)) { - status =NVME_SC_INTERNAL |NVME_SC_DNR ; + status =NVME_SC_INTERNAL |NVME_STATUS_DNR ; goto out_put_bio; }
include/linux/nvme.h +2 -2
@@ -1630,8 +1630,8 @@ enum {NVME_SCT_MASK =0x0700 ,/* Status Code Type */ NVME_SCT_SC_MASK =NVME_SCT_MASK |NVME_SC_MASK , -NVME_SC_CRD =0x1800 ,/* Command Retry Delayed */ - NVME_SC_DNR = 0x4000, /* Do Not Retry */ +NVME_STATUS_CRD =0x1800 ,/* Command Retry Delayed */ + NVME_STATUS_DNR = 0x4000, /* Do Not Retry */ };#define NVME_SCT (status) ((status) >> 8 & 7)
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/3 nvmet: fix pre-auth out-of-bounds heap read in Discovery Get Log Page
In reply to Sasha LevinFrom: Bryam Vargas <[email protected]>
[ Upstream commit 53cd102a7a56079b11b897835bd9b94c14e6322c ]
nvmet_execute_disc_get_log_page() validates only the dword alignment of the host-supplied Log Page Offset (lpo). The 64-bit offset is then added to a small kzalloc'd buffer that holds the discovery log page and the result is passed straight to nvmet_copy_to_sgl(), which memcpy()s data_len bytes out to the host with no source-side bound check:
u64 offset = nvmet_get_log_page_offset(req->cmd); /* 64-bit host */
size_t data_len = nvmet_get_log_page_len(req->cmd); /* 32-bit host */
...
if (offset & 0x3) { ... } /* only check */
...
alloc_len = sizeof(*hdr) + entry_size * discovery_log_entries(req);
buffer = kzalloc(alloc_len, GFP_KERNEL);
...
status = nvmet_copy_to_sgl(req, 0, buffer + offset, data_len);The Discovery controller is unauthenticated -- nvmet_host_allowed() returns true unconditionally for the discovery subsystem -- so the call is reachable pre-authentication by any TCP/RDMA/FC peer that can reach the nvmet target. With a discovery log page of ~1 KiB, an attacker requesting up to 4 KiB starting at offset == alloc_len reads the next slab page out and gets its content returned over the fabric (an empirical run on a default nvmet-tcp loopback target leaked 81 canonical kernel pointers in one Get Log Page response). Pointing the offset at unmapped kernel memory faults the in-kernel memcpy and crashes (or panics, on panic_on_oops=1) the target host instead.
The attacker-controlled source-side offset pattern "nvmet_copy_to_sgl(req, 0, buffer + ATTACKER_OFFSET, ...)" is unique to nvmet_execute_disc_get_log_page in the entire nvmet codebase: every other Get Log Page handler in admin-cmd.c either ignores lpo (and silently starts every response at offset 0) or tracks a local destination offset with a fixed source pointer.
Validate the host-supplied offset against the log page size, cap the copy length to what is actually available, and zero-fill any remainder of the host transfer buffer. The zero-fill matches the existing short-response pattern in nvmet_execute_get_log_changed_ns() (admin-cmd.c) and prevents leaking transport SGL contents when the host asks for more bytes than the log page contains.
--- drivers/nvme/target/discovery.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-)
drivers/nvme/target/discovery.c +22 -1
@@ -166,6 +166,7 @@ static void nvmet_execute_disc_get_log_page(struct nvmet_req *req)u64 offset =nvmet_get_log_page_offset (req->cmd);size_t data_len =nvmet_get_log_page_len (req->cmd);size_t alloc_len; +size_t copy_len;struct nvmet_subsys_link *p;struct nvmet_port *r;u32 numrec =0 ;
@@ -234,7 +235,27 @@ static void nvmet_execute_disc_get_log_page(struct nvmet_req *req)up_read (&nvmet_config_sem); - status =nvmet_copy_to_sgl (req,0 , buffer + offset, data_len); +/* +* Validate the host-supplied log page offset before copying out. +* Without this check, the host controls a 64-bit byte offset into +* a small kzalloc'd buffer: a value past the log page lets the +* subsequent memcpy read adjacent kernel heap, and a value aimed +* at unmapped kernel memory faults the in-kernel copy and crashes +* the target host. The Discovery controller is unauthenticated, +* so the bug is reachable from any reachable fabric peer. +*/ +if (offset > alloc_len) { + req->error_loc = + offsetof(struct nvme_get_log_page_command , lpo); + status =NVME_SC_INVALID_FIELD |NVME_STATUS_DNR ; + goto out_free_buffer; + } + + copy_len =min_t (size_t, data_len, alloc_len - offset); + status =nvmet_copy_to_sgl (req,0 , buffer + offset, copy_len); +if (!status && copy_len < data_len) + status =nvmet_zero_sgl (req, copy_len, data_len - copy_len); +out_free_buffer:kfree (buffer); out:nvmet_req_complete (req, status);
Signature · 1 line
2.53.0