[PATCH v3 2/2] ksmbd: fix named-pipe reply loss with small output buffers

From: Wang Zhaolong

Date: Mon Oct 26 2026 - 01:37:04 EST


FSCTL_PIPE_TRANSCEIVE copies at most MaxOutputResponse bytes from an RPC
reply and reports STATUS_BUFFER_OVERFLOW when it does not fit. It then
frees the complete IPC reply, including the bytes that were not returned.
The client cannot recover those bytes with a subsequent READ, so an RPC
call can fail even though userspace produced a complete reply. Pipe READ
also ignores the requested Length when copying an IPC reply.

In the reproduced Windows NetShareEnum case, a 2632-byte reply is limited
to 1024 bytes of SMB output. The remaining 1608 bytes are discarded
instead of being available to the follow-up READ. Increasing the
application-level enumeration budget does not change that SMB output
limit.

Keep unread reply data on the pipe and respect each operation's output
limit. Preserve it until successfully included in a response: response
preparation failures and later input must not discard output that the
client has yet to receive. Treat a failed IPC exchange as an I/O error,
rather than reporting an empty read or acknowledging a write without
an IPC reply. Additional RPC reply queueing remains userspace's
responsibility.

Keep cached bytes on failed WRITE or TRANSCEIVE requests, and consume them
only after the response is pinned successfully. Release the original reply
when drained or when its pipe is closed, logged off or disconnected.

Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3")
Link: https://bugzilla.kernel.org/show_bug.cgi?id=222196
Reported-by: kernel test robot <lkp@xxxxxxxxx>
Closes: https://lore.kernel.org/oe-kbuild-all/202610260610.5prVfJss-lkp@xxxxxxxxx/
Signed-off-by: Wang Zhaolong <wangzhaolong3@xxxxxxxxxxx>
---
fs/smb/server/mgmt/user_session.c | 1 +
fs/smb/server/mgmt/user_session.h | 3 +
fs/smb/server/smb2pdu.c | 159 +++++++++++++++++-------------
3 files changed, 95 insertions(+), 68 deletions(-)

diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index 4db69809b9fd..3f75049be878 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -301,6 +301,7 @@ static void ksmbd_session_rpc_free(struct ksmbd_session_rpc *entry)
{
if (refcount_dec_and_test(&entry->refcount)) {
ksmbd_rpc_id_free(entry->id);
+ kvfree(entry->pending);
kfree(entry);
}
}
diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h
index 38073ea9089d..5f0f6276e316 100644
--- a/fs/smb/server/mgmt/user_session.h
+++ b/fs/smb/server/mgmt/user_session.h
@@ -19,6 +19,7 @@
#define PREAUTH_HASHVALUE_SIZE 64

struct ksmbd_file_table;
+struct ksmbd_rpc_command;
struct ksmbd_work;

struct ksmbd_session_rpc {
@@ -27,6 +28,8 @@ struct ksmbd_session_rpc {
struct mutex lock;
refcount_t refcount;
bool closed;
+ struct ksmbd_rpc_command *pending;
+ unsigned int pending_offset;
};

struct channel {
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 66123b7afd5d..627b4ff58da2 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -9262,6 +9262,17 @@ static __le32 smb2_pipe_error_status(int err, __le32 fallback)
}
}

+static void smb2_pipe_response_done(struct ksmbd_session_rpc *rpc,
+ unsigned int nbytes)
+{
+ rpc->pending_offset += nbytes;
+ if (rpc->pending_offset == rpc->pending->payload_sz) {
+ kvfree(rpc->pending);
+ rpc->pending = NULL;
+ rpc->pending_offset = 0;
+ }
+}
+
/**
* smb2_read_pipe() - handler for smb2 read from IPC pipe
* @work: smb work containing read IPC pipe command buffer
@@ -9270,12 +9281,14 @@ static __le32 smb2_pipe_error_status(int err, __le32 fallback)
*/
static noinline int smb2_read_pipe(struct ksmbd_work *work)
{
- int nbytes = 0, err;
+ unsigned int nbytes = 0, length;
u64 id;
struct ksmbd_rpc_command *rpc_resp;
struct ksmbd_session_rpc *rpc;
struct smb2_read_req *req;
struct smb2_read_rsp *rsp;
+ void *aux_payload_buf;
+ int err;

WORK_BUFFERS(work, req, rsp);

@@ -9294,46 +9307,59 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work)
rpc = ksmbd_session_rpc_get(work, id);
if (IS_ERR(rpc)) {
err = PTR_ERR(rpc);
- rpc_resp = NULL;
goto out;
}
- rpc_resp = ksmbd_rpc_read(work->sess, rpc);
- if (rpc_resp) {
- void *aux_payload_buf;

+ length = le32_to_cpu(req->Length);
+ if (!length)
+ goto empty;
+
+ if (!rpc->pending) {
+ rpc_resp = ksmbd_rpc_read(work->sess, rpc);
+ if (!rpc_resp) {
+ err = -EIO;
+ goto out_put;
+ }
if (rpc_resp->flags != KSMBD_RPC_OK) {
+ kvfree(rpc_resp);
err = -EINVAL;
goto out_put;
}
+ rpc->pending = rpc_resp;
+ }

- aux_payload_buf =
- kvmalloc(ALIGN(rpc_resp->payload_sz, 8),
- KSMBD_DEFAULT_GFP);
- if (!aux_payload_buf) {
- err = -ENOMEM;
- goto out_put;
- }
+ rpc_resp = rpc->pending;
+ nbytes = min(length, rpc_resp->payload_sz - rpc->pending_offset);
+ if (!nbytes) {
+ smb2_pipe_response_done(rpc, 0);
+ goto empty;
+ }

- memcpy(aux_payload_buf, rpc_resp->payload, rpc_resp->payload_sz);
- if (rpc_resp->payload_sz & 7)
- memset(aux_payload_buf + rpc_resp->payload_sz, 0,
- ALIGN(rpc_resp->payload_sz, 8) -
- rpc_resp->payload_sz);
+ aux_payload_buf = kvmalloc(ALIGN(nbytes, 8), KSMBD_DEFAULT_GFP);
+ if (!aux_payload_buf) {
+ err = -ENOMEM;
+ goto out_put;
+ }
+ memcpy(aux_payload_buf, rpc_resp->payload + rpc->pending_offset, nbytes);
+ if (nbytes & 7)
+ memset(aux_payload_buf + nbytes, 0, ALIGN(nbytes, 8) - nbytes);

- nbytes = rpc_resp->payload_sz;
- err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
- offsetof(struct smb2_read_rsp, Buffer),
- aux_payload_buf, nbytes);
- if (err) {
- kvfree(aux_payload_buf);
- goto out_put;
- }
- kvfree(rpc_resp);
- } else {
- err = -EIO;
+ err = ksmbd_iov_pin_rsp_read(work, rsp,
+ offsetof(struct smb2_read_rsp, Buffer),
+ aux_payload_buf, nbytes);
+ if (err) {
+ kvfree(aux_payload_buf);
goto out_put;
}
+ smb2_pipe_response_done(rpc, nbytes);
+ goto done;

+empty:
+ err = ksmbd_iov_pin_rsp(work, rsp,
+ offsetof(struct smb2_read_rsp, Buffer));
+ if (err)
+ goto out_put;
+done:
rsp->StructureSize = cpu_to_le16(17);
rsp->DataOffset = 80;
rsp->Reserved = 0;
@@ -9349,7 +9375,6 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work)
rsp->hdr.Status = smb2_pipe_error_status(err,
STATUS_UNEXPECTED_IO_ERROR);
smb2_set_err_rsp(work);
- kvfree(rpc_resp);
return err;
}

@@ -9947,7 +9972,7 @@ static noinline int smb2_write_pipe(struct ksmbd_work *work)
if (IS_ERR(rpc)) {
err = PTR_ERR(rpc);
rsp->hdr.Status = smb2_pipe_error_status(err,
- STATUS_INVALID_HANDLE);
+ STATUS_INVALID_HANDLE);
smb2_set_err_rsp(work);
return err;
}
@@ -11440,20 +11465,25 @@ static int fsctl_pipe_transceive(struct ksmbd_work *work, u64 id,
struct ksmbd_rpc_command *rpc_resp;
struct ksmbd_session_rpc *rpc;
char *data_buf = (char *)req + le32_to_cpu(req->InputOffset);
- unsigned int nbytes;
- int err;
+ unsigned int nbytes = 0;
+ int ret;

rpc = ksmbd_session_rpc_get(work, id);
if (IS_ERR(rpc)) {
- err = PTR_ERR(rpc);
- rsp->hdr.Status = smb2_pipe_error_status(err,
- STATUS_INVALID_PARAMETER);
+ ret = PTR_ERR(rpc);
+ rsp->hdr.Status = smb2_pipe_error_status(ret,
+ STATUS_INVALID_PARAMETER);
smb2_set_err_rsp(work);
return 0;
}

- rpc_resp = ksmbd_rpc_ioctl(work->sess, rpc, data_buf,
- le32_to_cpu(req->InputCount));
+ /* Write new input without reading past an already buffered reply. */
+ if (rpc->pending)
+ rpc_resp = ksmbd_rpc_write(work->sess, rpc, data_buf,
+ le32_to_cpu(req->InputCount));
+ else
+ rpc_resp = ksmbd_rpc_ioctl(work->sess, rpc, data_buf,
+ le32_to_cpu(req->InputCount));
if (!rpc_resp) {
ksmbd_session_rpc_put(rpc);
rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
@@ -11462,39 +11492,41 @@ static int fsctl_pipe_transceive(struct ksmbd_work *work, u64 id,
}

if (rpc_resp->flags == KSMBD_RPC_SOME_NOT_MAPPED) {
- /*
- * set STATUS_SOME_NOT_MAPPED response
- * for unknown domain sid.
- */
rsp->hdr.Status = STATUS_SOME_NOT_MAPPED;
} else if (rpc_resp->flags == KSMBD_RPC_ENOTIMPLEMENTED) {
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
- goto error;
+ goto free_resp;
} else if (rpc_resp->flags != KSMBD_RPC_OK) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
- goto error;
+ goto free_resp;
}

- nbytes = rpc_resp->payload_sz;
- if (rpc_resp->payload_sz > out_buf_len) {
- rsp->hdr.Status = STATUS_BUFFER_OVERFLOW;
- nbytes = out_buf_len;
- }
-
- if (!rpc_resp->payload_sz) {
+ if (rpc->pending) {
+ kvfree(rpc_resp);
+ } else if (!rpc_resp->payload_sz) {
rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
- goto error;
+ goto free_resp;
+ } else {
+ rpc->pending = rpc_resp;
}

- memcpy((char *)rsp->Buffer, rpc_resp->payload, nbytes);
- err = smb2_ioctl_rsp(work, rsp, FSCTL_PIPE_TRANSCEIVE, nbytes);
+ rpc_resp = rpc->pending;
+ nbytes = rpc_resp->payload_sz - rpc->pending_offset;
+ if (nbytes > out_buf_len) {
+ rsp->hdr.Status = STATUS_BUFFER_OVERFLOW;
+ nbytes = out_buf_len;
+ }
+ memcpy(rsp->Buffer, rpc_resp->payload + rpc->pending_offset, nbytes);
+ ret = smb2_ioctl_rsp(work, rsp, FSCTL_PIPE_TRANSCEIVE, nbytes);
+ if (!ret)
+ smb2_pipe_response_done(rpc, nbytes);
ksmbd_session_rpc_put(rpc);
- kvfree(rpc_resp);
- return err;
+ return ret;

-error:
- ksmbd_session_rpc_put(rpc);
+free_resp:
kvfree(rpc_resp);
+ rsp->hdr.Status = smb2_pipe_error_status(-EINVAL, rsp->hdr.Status);
+ ksmbd_session_rpc_put(rpc);
smb2_set_err_rsp(work);
return 0;
}
@@ -11829,7 +11861,7 @@ int smb2_ioctl(struct ksmbd_work *work)
rsp->VolatileFileId = id;
out_buf_len = min_t(u32, KSMBD_IPC_MAX_PAYLOAD, out_buf_len);
ret = fsctl_pipe_transceive(work, id, out_buf_len, req, rsp);
- if (ret < 0)
+ if (ret)
goto out;
return 0;
case FSCTL_VALIDATE_NEGOTIATE_INFO:
@@ -12210,16 +12242,7 @@ int smb2_ioctl(struct ksmbd_work *work)
goto out;
}

- rsp->CtlCode = cpu_to_le32(cnt_code);
- rsp->InputCount = cpu_to_le32(0);
- rsp->InputOffset = cpu_to_le32(112);
- rsp->OutputOffset = cpu_to_le32(112);
- rsp->OutputCount = cpu_to_le32(nbytes);
- rsp->StructureSize = cpu_to_le16(49);
- rsp->Reserved = cpu_to_le16(0);
- rsp->Flags = cpu_to_le32(0);
- rsp->Reserved2 = cpu_to_le32(0);
- ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_ioctl_rsp) + nbytes);
+ ret = smb2_ioctl_rsp(work, rsp, cnt_code, nbytes);
if (!ret)
return ret;

--
2.56.0