[PATCH] nvmet-auth: validate NEGOTIATE message length against the transfer length

Chuyf26 <[email protected]>
Newsgroups org.infradead.lists.linux-nvme
Message-ID <[email protected]>
nvmet_auth_negotiate() reads the protocol descriptor and then iterates
over the hash and DH group id lists using the halen and dhlen counts
from the received NEGOTIATE message, without checking that the lists
fit into the buffer that was actually received.  The buffer is
allocated with the transfer length from the AUTH_SEND command, which
the host controls.  An unauthenticated host can therefore send a
NEGOTIATE message with a small transfer length and large halen/dhlen
values and read out of bounds from the heap buffer before
authentication completes.

The path is: the host issues an AUTH_SEND command with the DH-HMAC-CHAP
protocol identifier, nvmet_execute_auth_send() allocates a buffer of
the transfer length tl from the command, copies the message into it and
dispatches the NEGOTIATE message to nvmet_auth_negotiate().  halen and
dhlen are single bytes, so the id list loops can read up to about 300
bytes past the end of the kmalloc(tl) buffer.  The buffer is a heap
object, so KASAN reports the over-read.

The REPLY and other received messages are already validated against
the transfer length; do the same for NEGOTIATE by checking that the
protocol descriptor and both id lists fit into the transfer length
before reading them.

Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication")
Reported-by: Abaci <[email protected]>
Assisted-by: abaci:qwen3.8-max
Signed-off-by: Chuyf26 <[email protected]>
---
 drivers/nvme/target/fabrics-cmd-auth.c | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c
index ccd5dd3dac85..61a36724bae8 100644
--- a/drivers/nvme/target/fabrics-cmd-auth.c
+++ b/drivers/nvme/target/fabrics-cmd-auth.c
@@ -31,11 +31,19 @@ void nvmet_auth_sq_init(struct nvmet_sq *sq)
 	sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
 }
 
-static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
+static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d, u32 tl)
 {
 	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;
+	size_t proto_off = offsetof(struct nvmf_auth_dhchap_negotiate_data,
+				    auth_protocol);
+	size_t idlist_off = proto_off +
+		offsetof(struct nvmf_auth_dhchap_protocol_descriptor, idlist);
+
+	/* Validate that the protocol descriptor fits the transfer length */
+	if (tl < proto_off + sizeof(union nvmf_auth_protocol))
+		return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
 
 	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,
@@ -53,6 +61,10 @@ static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
 	    NVME_AUTH_DHCHAP_AUTH_ID)
 		return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
 
+	/* Validate that the hash id list fits the transfer length */
+	if (tl < idlist_off + data->auth_protocol[0].dhchap.halen)
+		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];
 
@@ -78,6 +90,11 @@ static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
 
 	dhgid = -1;
 	fallback_dhgid = -1;
+
+	/* The DH group id list starts at idlist offset 30 */
+	if (tl < idlist_off + 30 + data->auth_protocol[0].dhchap.dhlen)
+		return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
+
 	for (i = 0; i < data->auth_protocol[0].dhchap.dhlen; i++) {
 		int tmp_dhgid = data->auth_protocol[0].dhchap.idlist[i + 30];
 
@@ -265,7 +282,7 @@ void nvmet_execute_auth_send(struct nvmet_req *req)
 		} else if (data->auth_id != req->sq->dhchap_step)
 			goto done_failure1;
 		/* Validate negotiation parameters */
-		dhchap_status = nvmet_auth_negotiate(req, d);
+		dhchap_status = nvmet_auth_negotiate(req, d, tl);
 		if (dhchap_status == 0)
 			req->sq->dhchap_step =
 				NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
-- 
2.43.5
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.