Linux network filesystem support library
 help / color / mirror / Atom feed
From: Christopher Lusk <clusk@northecho.dev>
To: sfrench@samba.org, pc@manguebit.org, ronniesahlberg@gmail.com,
	sprasad@microsoft.com, tom@talpey.com, bharathsm@microsoft.com,
	pshilov@microsoft.com, longli@microsoft.com, dhowells@redhat.com
Cc: linux-cifs@vger.kernel.org, samba-technical@lists.samba.org,
	netfs@lists.linux.dev, linux-kernel@vger.kernel.org
Subject: [PATCH] smb: client: fix request buffer leak in smb2_new_read_req()
Date: Wed, 29 Jul 2026 18:00:17 -0400	[thread overview]
Message-ID: <20260729220017.944651-1-clusk@northecho.dev> (raw)

smb2_new_read_req() allocates the request buffer with
smb2_plain_req_init() but only publishes it to the caller with
*buf = req at the very end of the function. Two error returns sit in
between:

	rc = smb2_plain_req_init(SMB2_READ, io_parms->tcon, server,
				 (void **) &req, total_len);
	if (rc)
		return rc;

	if (server == NULL)
		return -ECONNABORTED;
	[...]
		rdata->mr = smbd_register_mr(server->smbd_conn,
					     &rdata->subreq.io_iter,
					     true, need_invalidate);
		if (!rdata->mr)
			return -EAGAIN;

On either of them the buffer is neither released nor handed back, so
it is leaked. The caller cannot clean up after it: smb2_async_readv()
does 'goto out' on a non-zero return, which skips the
cifs_small_buf_release(buf) at async_readv_out, and buf has not been
assigned at that point in any case.

The write path has never had this problem. smb2_async_writev()
registers the memory region inline and jumps to its release label
instead of returning:

	wdata->mr = smbd_register_mr(...);
	if (!wdata->mr) {
		rc = -EAGAIN;
		goto async_writev_out;
	}

Commit b7972092199f ("cifs: smbd: Retry on memory registration
failure") changed both sides from -ENOBUFS to -EAGAIN in a single
patch, which puts the two shapes next to each other.

Only the -EAGAIN return is reachable in practice, because
smb2_plain_req_init() calls smb2_reconnect() first and that already
fails with -EIO when server is NULL, before anything is allocated.
Both returns are given the same treatment here rather than leaving
one of them correct only by accident.

Because -EAGAIN is a replayable error, the failure also reaches the
retry block at the end of smb2_async_readv(), which marks the
subrequest NETFS_SREQ_NEED_RETRY, so a failing registration can be
retried rather than ending the I/O, and every attempt that reaches it
leaks another buffer. smb2_should_replay() short-circuits on
tcon->retry, so on a hard mount the attempt count is not bounded by
the retrans setting.

Only the asynchronous read path is affected. The synchronous
SMB2_read() caller passes rdata == NULL and the memory registration
block is guarded on rdata.

The memory registration failure path was pointed out by the Sashiko
AI reviewer while it was reviewing an unrelated patch to
smb2_async_readv().

Fixes: bd3dcc6a22a9 ("CIFS: SMBD: Upper layer performs SMB read via RDMA write through memory registration")
Link: https://sashiko.dev/#/patchset/20260729192002.876156-1-clusk%40northecho.dev
Link: https://lore.kernel.org/all/20260729192002.876156-1-clusk@northecho.dev/
Assisted-by: Claude:claude-opus-5
Signed-off-by: Christopher Lusk <clusk@northecho.dev>
---
Tested by fault injection, not on real hardware. I have no SMB Direct
setup, so a throwaway debug patch forced the memory registration
failure branch on both the read and the write side, with a countdown
module parameter, and the resulting error paths were measured. The
mount was ordinary SMB2 over TCP; nothing was simulated beyond making
the registration return NULL.

Detector: small_buf_alloc_count, the counter behind "SMB Small
Req/Resp Buffer" in /proc/fs/cifs/Stats, incremented in
cifs_small_buf_get() and decremented in cifs_small_buf_release().
Second detector: kmemleak.

Same kernel, same test, without and with the patch:

                                        unpatched   patched
  clean read, no injection                    +0        +0
  5 reads, one forced read MR failure each    +5         0
  5 writes, one forced write MR failure each  +0        +0
  buffers still allocated after umount         5         0
  kmemleak objects under smb2_new_read_req     1         0

Both runs consumed all five injected failures on each side and
returned the same errors to userspace, so the only difference is the
leak. The write column is the control: the same forced failure on the
path that already has the release does not leak.

kmemleak on the unpatched run, the 448 byte object's contents
starting \xfeSMB:

  unreferenced object 0xffffa241013cca80 (size 448):
    comm "dd"
    backtrace:
      cifs_small_buf_get+0x15/0x30
      __smb2_plain_req_init+0x33/0x230
      smb2_new_read_req.constprop.0+0x93/0x2e0
      smb2_async_readv+0xfd/0x3c0
      cifs_issue_read+0x87/0x170

kmemleak found one of the five, which is the usual false negative
when the address is still lying around in stale stack memory. The
counter found all five.

Two limits worth stating. The five failures came from five separate
reads rather than five retries of one read: in this configuration the
read was unbuffered and netfs returned EAGAIN to userspace instead of
reissuing, while the write side did retry and completed successfully.
And forcing the branch says nothing about how often a real memory
registration fails, so the reachability argument in the changelog is
from reading the code, not from measurement.

Build: x86_64, gcc 15.2.1, W=1, clean in both
CONFIG_CIFS_SMB_DIRECT=y, which is what compiles the changed hunk,
and CONFIG_CIFS_SMB_DIRECT=n, which checks that the new label is
still reached. checkpatch --strict reports 0 errors, 0 warnings, 0
checks. Applies to cifs-2.6/for-next at fa724e235cfd and on top of
the earlier patch in this thread.

Not marked for stable. The leak is real but I cannot say how often
the path is taken in the field. If you think an unbounded leak under
persistent memory registration failure warrants a backport, please
add the tag.

Per Documentation/process/generated-content.rst: this patch, the
analysis in its changelog and the test harness were produced with the
assistance of Claude (claude-opus-5). The memory registration failure
path was surfaced by the Sashiko AI reviewer on an unrelated patch.
The claims were checked against the tree rather than taken from the
tools, and the numbers above are from runs I can reproduce.
 fs/smb/client/smb2pdu.c | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 4ce165e40657..b885060be200 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -4564,8 +4564,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
 	if (rc)
 		return rc;
 
-	if (server == NULL)
-		return -ECONNABORTED;
+	if (!server) {
+		rc = -ECONNABORTED;
+		goto free_req;
+	}
 
 	shdr = &req->hdr;
 	shdr->Id.SyncId.ProcessId = cpu_to_le32(io_parms->pid);
@@ -4596,8 +4598,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
 
 		rdata->mr = smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter,
 					     true, need_invalidate);
-		if (!rdata->mr)
-			return -EAGAIN;
+		if (!rdata->mr) {
+			rc = -EAGAIN;
+			goto free_req;
+		}
 
 		req->Channel = SMB2_CHANNEL_RDMA_V1_INVALIDATE;
 		if (need_invalidate)
@@ -4638,6 +4642,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
 
 	*buf = req;
 	return rc;
+
+free_req:
+	cifs_small_buf_release(req);
+	return rc;
 }
 
 static void
-- 
2.54.0


                 reply	other threads:[~2026-07-29 22:00 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20260729220017.944651-1-clusk@northecho.dev \
    --to=clusk@northecho.dev \
    --cc=bharathsm@microsoft.com \
    --cc=dhowells@redhat.com \
    --cc=linux-cifs@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=longli@microsoft.com \
    --cc=netfs@lists.linux.dev \
    --cc=pc@manguebit.org \
    --cc=pshilov@microsoft.com \
    --cc=ronniesahlberg@gmail.com \
    --cc=samba-technical@lists.samba.org \
    --cc=sfrench@samba.org \
    --cc=sprasad@microsoft.com \
    --cc=tom@talpey.com \
    /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