From: Ibrahim Hashimov <security@auditcode.ai>
To: Hannes Reinecke <hare@suse.de>, Christoph Hellwig <hch@lst.de>,
Sagi Grimberg <sagi@grimberg.me>,
Chaitanya Kulkarni <kch@nvidia.com>
Cc: linux-nvme@lists.infradead.org, linux-kernel@vger.kernel.org,
stable@vger.kernel.org
Subject: [PATCH] nvmet-auth: fix uninitialized-memory leak in AUTH_RECEIVE
Date: Thu, 9 Jul 2026 15:25:20 +0200 [thread overview]
Message-ID: <20260709132520.44160-1-security@auditcode.ai> (raw)
nvmet_execute_auth_receive() allocates its response buffer with the
attacker-controlled AUTH_RECEIVE `al` (data length) field:
al = nvmet_auth_receive_data_len(req);
...
d = kmalloc(al, GFP_KERNEL);
...
switch (req->sq->dhchap_step) {
case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:
nvmet_auth_challenge(req, d, al);
...
status = nvmet_copy_to_sgl(req, 0, d, al);
nvmet_check_transfer_len() only enforces al == req->transfer_len, a
pure equality check with no upper bound tied to the message actually
being produced. nvmet_auth_challenge() computes its own data_size
(sizeof(challenge header) + hash_len, 48 bytes for SHA-256 with no DH
group) and only checks "al < data_size" as a floor -- it memset()s and
fills exactly data_size bytes of the buffer. The same pattern applies
to nvmet_auth_success1()/nvmet_auth_failure1(), which only memset()
sizeof(*data) bytes of `d`.
Whichever handler runs, nvmet_execute_auth_receive() then streams the
entire al-byte buffer back to the remote host with
nvmet_copy_to_sgl(req, 0, d, al). Since a remote NVMe-oF/TCP host
chooses `al` and can set it far larger than data_size (e.g. 4096 vs.
48), the tail [data_size, al) of the kmalloc()'d buffer is returned to
the peer without ever having been written by the kernel -- a remote,
pre-authentication disclosure of uninitialized kernel heap contents,
confirmed with KASAN by planting a marker byte in
a freed kmalloc-4096 object and observing it echoed back in the tail
of the CHALLENGE response. This is reachable as soon as
CONFIG_NVME_TARGET_AUTH is enabled (the default on Fedora/RHEL/Ubuntu
distro kernels) and a subsystem uses/allows the DH-CHAP auth path; no
secret needs to be proven first, since the leak occurs on the
CHALLENGE step of the handshake.
Fix it the same way nvmet_execute_disc_get_log_page() already handles
an analogous host-controlled-length response buffer
(drivers/nvme/target/discovery.c: "buffer = kzalloc(alloc_len,
GFP_KERNEL);"): zero the allocation up front instead of only zeroing
the sub-range each per-step helper happens to fill. This keeps the fix
to a single line, covers all three dhchap_step handlers that share
this buffer and the common copy-back site (CHALLENGE, SUCCESS1,
FAILURE1), and does not change the wire format, the transfer-length
checks, or any success/error paths.
Testing: the uninitialized-tail disclosure itself was confirmed live
under KASAN before this fix, by planting a marker byte in a freed
kmalloc-4096 object and observing it (or other live heap bytes)
echoed back in the CHALLENGE response tail; a CONTROL request with
al == data_size returned no such tail on every run, and an EXPLOIT
request with al = 4096 leaked up to 4048 bytes of stale heap on two
separate runs. The fix itself is verified by code inspection rather
than a fresh KASAN run: kzalloc() zeroes the whole al-byte allocation
before any dhchap_step handler executes, so the tail past whatever
bytes a handler's own memset()/fill writes is guaranteed zero instead
of leftover slab content, for all three handlers and the single
shared nvmet_copy_to_sgl() copy-back site. Re-running the KASAN
harness against a kernel built with this patch to reconfirm a
zero tail is currently blocked by an nvme-tcp loopback livelock in
the test stand; a controlled A/B check showed the same livelock on
the unpatched baseline as well, so it is a pre-existing harness
limitation, not something introduced by this one-line kzalloc()
change.
Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication")
Cc: stable@vger.kernel.org
Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
Assisted-by: AuditCode-AI:2026.07
---
drivers/nvme/target/fabrics-cmd-auth.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c
index 45820a12750d..2b617d3b8bba 100644
--- a/drivers/nvme/target/fabrics-cmd-auth.c
+++ b/drivers/nvme/target/fabrics-cmd-auth.c
@@ -557,7 +557,7 @@ void nvmet_execute_auth_receive(struct nvmet_req *req)
return;
}
- d = kmalloc(al, GFP_KERNEL);
+ d = kzalloc(al, GFP_KERNEL);
if (!d) {
status = NVME_SC_INTERNAL;
goto done;
--
2.50.1 (Apple Git-155)
next reply other threads:[~2026-07-09 13:25 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-09 13:25 Ibrahim Hashimov [this message]
2026-07-11 7:53 ` [PATCH] nvmet-auth: fix uninitialized-memory leak in AUTH_RECEIVE Ibrahim Hashimov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260709132520.44160-1-security@auditcode.ai \
--to=security@auditcode.ai \
--cc=hare@suse.de \
--cc=hch@lst.de \
--cc=kch@nvidia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=sagi@grimberg.me \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.