[PATCH v2] nvmet: auth: validate dhchap id list lengths

YunJe Shin yjshin0438 at gmail.com
Thu Mar 12 22:24:09 PDT 2026


From: Yunje Shin <ioerts at kookmin.ac.kr>

The function nvmet_auth_negotiate() parses the idlist array in the
struct nvmf_auth_dhchap_protocol_descriptor payload. This array is 60
bytes and is logically divided into two 30-byte halves: the first half
for HMAC IDs and the second half for DH group IDs. The current code
uses a hardcoded +30 offset for the DH list, but does not validate
halen and dhlen against the per-half bounds. As a result, if a
malicious host sends halen or dhlen larger than 30, the loop can
read past the 60-byte array into adjacent slab memory, triggering a
KASAN slab-out-of-bounds read.

KASAN splat:
[    4.241646] BUG: KASAN: slab-out-of-bounds in nvmet_execute_auth_send+0x19b8/0x2090
[    4.242874] Read of size 1 at addr ffff8881045754e8 by task kworker/1:1H/41
[    4.265342] The buggy address belongs to the cache kmalloc-96 of size 96
[    4.266291]  allocated 72-byte region [ffff8881045754a0, ffff8881045754e8)
[    4.270337] page dumped because: kasan: bad access detected

This patch fixes the issue by introducing NVME_AUTH_DHCHAP_MAX_HASH_IDS
and NVME_AUTH_DHCHAP_MAX_DH_IDS defined as 30, which explicitly indicates
the maximum boundaries allowed per NVMe specification. The lengths halen
and dhlen are validated against these boundaries before processing,
preventing the out-of-bounds reads.

Fixes: db1312dd95488 ("nvmet: implement basic In-Band Authentication")
Cc: stable at kernel.org
Signed-off-by: Yunje Shin <ioerts at kookmin.ac.kr>
Reviewed-by: Hannes Reinecke <hare at suse.de>
---
v2:
    - Replaced the runtime 'sizeof' calculation (idlist_half) with explicit 
      NVME_AUTH_DHCHAP_MAX_HASH_IDS and NVME_AUTH_DHCHAP_MAX_DH_IDS macros
      to clearly reflect the 30:30 split limit per Chris Leech's feedback.

 drivers/nvme/target/fabrics-cmd-auth.c | 11 ++++++++++-
 include/linux/nvme.h                   |  2 ++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c
index 5946681cb0e3..acba4878a873 100644
--- a/drivers/nvme/target/fabrics-cmd-auth.c
+++ b/drivers/nvme/target/fabrics-cmd-auth.c
@@ -72,6 +72,14 @@ static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
 	    NVME_AUTH_DHCHAP_AUTH_ID)
 		return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
 
+	/*
+	 * idlist[0..29]: hash IDs
+	 * idlist[30..59]: DH group IDs
+	 */
+	if (data->auth_protocol[0].dhchap.halen > NVME_AUTH_DHCHAP_MAX_HASH_IDS ||
+	    data->auth_protocol[0].dhchap.dhlen > NVME_AUTH_DHCHAP_MAX_DH_IDS)
+		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];
 
@@ -97,7 +105,8 @@ static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
 	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];
+		int tmp_dhgid =
+			data->auth_protocol[0].dhchap.idlist[i + NVME_AUTH_DHCHAP_MAX_HASH_IDS];
 
 		if (tmp_dhgid != ctrl->dh_gid) {
 			dhgid = tmp_dhgid;
diff --git a/include/linux/nvme.h b/include/linux/nvme.h
index b09dcaf5bcbc..ea0393ab16fc 100644
--- a/include/linux/nvme.h
+++ b/include/linux/nvme.h
@@ -1824,6 +1824,8 @@ struct nvmf_auth_dhchap_protocol_descriptor {
 	__u8		dhlen;
 	__u8		idlist[60];
 };
+#define NVME_AUTH_DHCHAP_MAX_HASH_IDS 30
+#define NVME_AUTH_DHCHAP_MAX_DH_IDS 30
 
 enum {
 	NVME_AUTH_DHCHAP_AUTH_ID	= 0x01,
-- 
2.43.0



More information about the Linux-nvme mailing list