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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox