summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNamjae Jeon <linkinjeon@kernel.org>2026-10-27 14:12:59 +0900
committerNamjae Jeon <linkinjeon@kernel.org>2026-10-28 17:57:07 +0900
commit206fc30f71b005156085fd8523ccc84a2c67791b (patch)
tree2fa7f716a9d53ca688627f20dc9ad2afe9d5229d
parent03bd8f5279e490e15e0c0f0fa388ffbd2fbacd65 (diff)
ksmbd: return complete and aligned stream information
FILE_STREAM_INFORMATION responses are assembled without aligning entries and truncated to the client buffer length after the fact. A short query can therefore return STATUS_BUFFER_OVERFLOW with an incomplete entry and a nonzero NextEntryOffset, which clients may reject as an invalid network response. Standalone queries can also exhaust the server response buffer and silently omit streams while reporting success. Allocate the same large response buffer used by other variable-length QUERY_INFO classes for stream queries. Pack only complete entries within the requested output length and available response space, aligning each entry to eight bytes and sizing names after UTF-16 conversion. Return STATUS_BUFFER_OVERFLOW when more entries remain, using an SMB2 error response if no entry fits. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Fixes: 6b8b79226bc3 ("ksmbd: fix partial file information responses") Link: https://github.com/namjaejeon/ksmbd/issues/522#issuecomment-6391152923 Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
-rw-r--r--fs/smb/server/smb2pdu.c183
1 files changed, 115 insertions, 68 deletions
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 9f2b435fd437..be226664b8af 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -989,7 +989,8 @@ int smb2_allocate_rsp_buf(struct ksmbd_work *work)
if ((req->InfoType == SMB2_O_INFO_FILE &&
(req->FileInfoClass == FILE_FULL_EA_INFORMATION ||
req->FileInfoClass == FILE_ALL_INFORMATION ||
- req->FileInfoClass == FILE_NORMALIZED_NAME_INFORMATION)) ||
+ req->FileInfoClass == FILE_NORMALIZED_NAME_INFORMATION ||
+ req->FileInfoClass == FILE_STREAM_INFORMATION)) ||
req->InfoType == SMB2_O_INFO_SECURITY)
sz = large_sz;
}
@@ -7372,41 +7373,107 @@ static int get_file_normalized_name_info(struct ksmbd_work *work,
return 0;
}
+struct smb2_stream_info_buffer {
+ struct smb2_query_info_rsp *rsp;
+ struct smb2_file_stream_info *last;
+ const struct nls_table *local_nls;
+ unsigned int length;
+ unsigned int capacity;
+};
+
+static int smb2_add_stream_info(struct smb2_stream_info_buffer *buf,
+ const char *name, loff_t size, u64 allocation_size)
+{
+ struct smb2_file_stream_info *file_info;
+ __le16 *utf16_name;
+ size_t name_len = strlen(name);
+ unsigned int offset = ALIGN(buf->length, 8), name_bytes;
+ int conv_len, ret = 0;
+
+ if (offset > buf->capacity ||
+ buf->capacity - offset < sizeof(*file_info))
+ return -ENOSPC;
+
+ utf16_name = kmalloc_array(name_len + 1, sizeof(*utf16_name),
+ KSMBD_DEFAULT_GFP);
+ if (!utf16_name)
+ return -ENOMEM;
+
+ conv_len = smbConvertToUTF16(utf16_name, name, name_len,
+ buf->local_nls, 0);
+ if (conv_len < 0) {
+ ret = conv_len;
+ goto out;
+ }
+
+ name_bytes = conv_len * sizeof(*utf16_name);
+ if (name_bytes > buf->capacity - offset - sizeof(*file_info)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ file_info = (struct smb2_file_stream_info *)&buf->rsp->Buffer[offset];
+ if (buf->last) {
+ memset(&buf->rsp->Buffer[buf->length], 0, offset - buf->length);
+ buf->last->NextEntryOffset =
+ cpu_to_le32((char *)file_info - (char *)buf->last);
+ }
+ file_info->NextEntryOffset = 0;
+ file_info->StreamNameLength = cpu_to_le32(name_bytes);
+ file_info->StreamSize = cpu_to_le64(size);
+ file_info->StreamAllocationSize = cpu_to_le64(allocation_size);
+ memcpy(file_info->StreamName, utf16_name, name_bytes);
+
+ buf->last = file_info;
+ buf->length = offset + sizeof(*file_info) + name_bytes;
+out:
+ kfree(utf16_name);
+ return ret;
+}
+
static int get_file_stream_info(struct ksmbd_work *work,
+ struct smb2_query_info_req *req,
struct smb2_query_info_rsp *rsp,
- struct ksmbd_file *fp,
- void *rsp_org)
+ struct ksmbd_file *fp)
{
- struct ksmbd_conn *conn = work->conn;
- struct smb2_file_stream_info *file_info;
+ struct smb2_stream_info_buffer buf = {
+ .rsp = rsp,
+ .local_nls = work->conn->local_nls,
+ };
char *stream_name, *xattr_list = NULL, *stream_buf;
struct kstat stat;
const struct path *path = &fp->filp->f_path;
ssize_t xattr_list_len;
ssize_t slen;
loff_t ssize;
- int nbytes = 0, streamlen, stream_name_len, next, idx = 0;
+ unsigned int req_output_len = le32_to_cpu(req->OutputBufferLength);
+ int streamlen, idx = 0;
int buf_free_len;
int ret;
+ if (req_output_len < FILE_STREAM_INFORMATION_SIZE) {
+ rsp->hdr.Status = STATUS_INFO_LENGTH_MISMATCH;
+ return -EINVAL;
+ }
+
+ buf_free_len = smb2_calc_max_out_buf_len(work,
+ offsetof(struct smb2_query_info_rsp, Buffer),
+ req_output_len);
+ if (buf_free_len < 0)
+ return buf_free_len;
+ buf.capacity = buf_free_len;
+
ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS,
AT_STATX_SYNC_AS_STAT);
if (ret)
return ret;
- file_info = (struct smb2_file_stream_info *)rsp->Buffer;
-
- buf_free_len = smb2_resp_buf_len(work,
- offsetof(struct smb2_query_info_rsp, Buffer));
- if (buf_free_len < 0)
- goto out;
-
xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
if (xattr_list_len < 0) {
- goto out;
+ goto default_stream;
} else if (!xattr_list_len) {
ksmbd_debug(SMB, "empty xattr in the file\n");
- goto out;
+ goto default_stream;
}
while (idx < xattr_list_len) {
@@ -7416,69 +7483,49 @@ static int get_file_stream_info(struct ksmbd_work *work,
ksmbd_debug(SMB, "%s, len %d\n", stream_name, streamlen);
- if (strncmp(&stream_name[XATTR_USER_PREFIX_LEN],
- STREAM_PREFIX, STREAM_PREFIX_LEN))
+ if (strncmp(stream_name, XATTR_NAME_STREAM,
+ XATTR_NAME_STREAM_LEN))
continue;
- stream_name_len = streamlen - (XATTR_USER_PREFIX_LEN +
- STREAM_PREFIX_LEN);
- streamlen = stream_name_len;
-
- /* plus : size */
- streamlen += 1;
- stream_buf = kmalloc(streamlen + 1, KSMBD_DEFAULT_GFP);
- if (!stream_buf)
- break;
-
- streamlen = snprintf(stream_buf, streamlen + 1,
- ":%s", &stream_name[XATTR_NAME_STREAM_LEN]);
-
- next = sizeof(struct smb2_file_stream_info) + streamlen * 2;
- /* smbConvertToUTF16() also writes a terminating UTF-16 unit. */
- if (next + sizeof(__le16) > buf_free_len) {
- kfree(stream_buf);
- break;
+ stream_buf = kasprintf(KSMBD_DEFAULT_GFP, ":%s",
+ &stream_name[XATTR_NAME_STREAM_LEN]);
+ if (!stream_buf) {
+ ret = -ENOMEM;
+ goto out;
}
-
- file_info = (struct smb2_file_stream_info *)&rsp->Buffer[nbytes];
- streamlen = smbConvertToUTF16((__le16 *)file_info->StreamName,
- stream_buf, streamlen,
- conn->local_nls, 0);
- streamlen *= 2;
- kfree(stream_buf);
- file_info->StreamNameLength = cpu_to_le32(streamlen);
slen = ksmbd_vfs_xattr_len(file_mnt_idmap(fp->filp),
path->dentry, stream_name);
ssize = slen < 0 ? 0 : (loff_t)slen;
- file_info->StreamSize = cpu_to_le64(ssize);
- file_info->StreamAllocationSize = cpu_to_le64(ssize);
+ ret = smb2_add_stream_info(&buf, stream_buf, ssize, ssize);
+ kfree(stream_buf);
+ if (ret == -ENOSPC)
+ goto overflow;
+ if (ret)
+ goto out;
+ }
- nbytes += next;
- buf_free_len -= next;
- file_info->NextEntryOffset = cpu_to_le32(next);
+default_stream:
+ if (!S_ISDIR(stat.mode)) {
+ ret = smb2_add_stream_info(&buf, "::$DATA", stat.size,
+ stat.blocks << 9);
+ if (ret == -ENOSPC)
+ goto overflow;
+ if (ret)
+ goto out;
}
+ ret = 0;
+ goto out;
+
+overflow:
+ rsp->hdr.Status = STATUS_BUFFER_OVERFLOW;
+ /* An empty overflow needs an SMB2 error response. */
+ ret = buf.length ? 0 : -EOVERFLOW;
out:
- if (!S_ISDIR(stat.mode) &&
- buf_free_len >= sizeof(struct smb2_file_stream_info) + (7 + 1) * 2) {
- file_info = (struct smb2_file_stream_info *)
- &rsp->Buffer[nbytes];
- streamlen = smbConvertToUTF16((__le16 *)file_info->StreamName,
- "::$DATA", 7, conn->local_nls, 0);
- streamlen *= 2;
- file_info->StreamNameLength = cpu_to_le32(streamlen);
- file_info->StreamSize = cpu_to_le64(stat.size);
- file_info->StreamAllocationSize = cpu_to_le64(stat.blocks << 9);
- nbytes += sizeof(struct smb2_file_stream_info) + streamlen;
- }
-
- /* last entry offset should be 0 */
- file_info->NextEntryOffset = 0;
+ if (!ret)
+ rsp->OutputBufferLength = cpu_to_le32(buf.length);
kvfree(xattr_list);
-
- rsp->OutputBufferLength = cpu_to_le32(nbytes);
-
- return 0;
+ return ret;
}
static int get_file_internal_info(struct smb2_query_info_rsp *rsp,
@@ -7823,7 +7870,7 @@ static int smb2_get_info_file(struct ksmbd_work *work,
break;
case FILE_STREAM_INFORMATION:
- rc = get_file_stream_info(work, rsp, fp, work->response_buf);
+ rc = get_file_stream_info(work, req, rsp, fp);
break;
case FILE_INTERNAL_INFORMATION: