diff options
| author | Namjae Jeon <linkinjeon@kernel.org> | 2026-10-27 14:12:59 +0900 |
|---|---|---|
| committer | Namjae Jeon <linkinjeon@kernel.org> | 2026-10-28 17:57:07 +0900 |
| commit | 206fc30f71b005156085fd8523ccc84a2c67791b (patch) | |
| tree | 2fa7f716a9d53ca688627f20dc9ad2afe9d5229d | |
| parent | 03bd8f5279e490e15e0c0f0fa388ffbd2fbacd65 (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.c | 183 |
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: |
