From 10aeff72ab82c264238dda270984cafb30175bad Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 7 Jul 2026 00:19:55 +0900 Subject: [PATCH] ksmbd: support normalized name information FILE_NORMALIZED_NAME_INFORMATION is not handled and is returned as STATUS_INVALID_INFO_CLASS. SMB 3.1.1 clients use this information class to obtain the share-relative path with the on-disk name casing. Build the normalized path from the opened dentry, remove the leading share-relative separator, and recover the canonical named-stream casing from its backing xattr. Return an empty name for the share root and STATUS_NOT_SUPPORTED for dialects older than SMB 3.1.1. Also distinguish a named $DATA stream on a directory from the unnamed data stream so that directory:stream:$DATA can be opened normally. This fixes smb2.getinfo.normalized. Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 81 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index a477aaf4e940..1959fcd5ebdc 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -3618,7 +3618,7 @@ int smb2_open(struct ksmbd_work *work) } } else { if (file_present && S_ISDIR(d_inode(path.dentry)->i_mode) && - s_type == DATA_STREAM) { + !stream_name && s_type == DATA_STREAM) { rc = -EIO; rsp->hdr.Status = STATUS_FILE_IS_A_DIRECTORY; } @@ -5578,6 +5578,80 @@ static void get_file_alternate_info(struct ksmbd_work *work, cpu_to_le32(struct_size(file_info, FileName, conv_len)); } +static char *smb2_get_normalized_stream_name(struct ksmbd_file *fp) +{ + char *name, *stream_name = NULL, *xattr_list = NULL; + ssize_t xattr_list_len; + + if (!ksmbd_stream_fd(fp)) + return NULL; + + xattr_list_len = ksmbd_vfs_listxattr(fp->filp->f_path.dentry, + &xattr_list); + if (xattr_list_len <= 0) + goto out; + + for (name = xattr_list; name - xattr_list < xattr_list_len; + name += strlen(name) + 1) { + char *type; + + if (strlen(name) + 1 != fp->stream.size || + strncasecmp(name, fp->stream.name, fp->stream.size - 1)) + continue; + + name += XATTR_NAME_STREAM_LEN; + type = strrchr(name, ':'); + if (type) + stream_name = kstrndup(name, type - name, + KSMBD_DEFAULT_GFP); + break; + } +out: + kvfree(xattr_list); + return stream_name; +} + +static int get_file_normalized_name_info(struct ksmbd_work *work, + struct smb2_query_info_rsp *rsp, + struct ksmbd_file *fp) +{ + struct smb2_file_alt_name_info *file_info; + char *filename, *normalized, *stream_name; + int conv_len, filename_len; + + if (work->conn->dialect < SMB311_PROT_ID) { + rsp->hdr.Status = STATUS_NOT_SUPPORTED; + return -EOPNOTSUPP; + } + + filename = convert_to_nt_pathname(work->tcon->share_conf, + &fp->filp->f_path); + if (IS_ERR(filename)) + return PTR_ERR(filename); + if (filename[0] == '\\') + memmove(filename, filename + 1, strlen(filename)); + + stream_name = smb2_get_normalized_stream_name(fp); + normalized = kasprintf(KSMBD_DEFAULT_GFP, "%s%s%s", filename, + stream_name ? ":" : "", + stream_name ? stream_name : ""); + kfree(stream_name); + kfree(filename); + if (!normalized) + return -ENOMEM; + + filename_len = strlen(normalized); + file_info = (struct smb2_file_alt_name_info *)rsp->Buffer; + conv_len = smbConvertToUTF16((__le16 *)file_info->FileName, + normalized, filename_len, + work->conn->local_nls, 0); + kfree(normalized); + conv_len *= 2; + file_info->FileNameLength = cpu_to_le32(conv_len); + rsp->OutputBufferLength = cpu_to_le32(sizeof(*file_info) + conv_len); + return 0; +} + static int get_file_stream_info(struct ksmbd_work *work, struct smb2_query_info_rsp *rsp, struct ksmbd_file *fp, @@ -5969,6 +6043,9 @@ static int smb2_get_info_file(struct ksmbd_work *work, case FILE_ALTERNATE_NAME_INFORMATION: get_file_alternate_info(work, rsp, fp, work->response_buf); break; + case FILE_NORMALIZED_NAME_INFORMATION: + rc = get_file_normalized_name_info(work, rsp, fp); + break; case FILE_STREAM_INFORMATION: rc = get_file_stream_info(work, rsp, fp, work->response_buf); @@ -6478,7 +6555,7 @@ int smb2_query_info(struct ksmbd_work *work) rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES; else if (rc == -EINVAL && rsp->hdr.Status == 0) rsp->hdr.Status = STATUS_INVALID_PARAMETER; - else if (rc == -EOPNOTSUPP || rsp->hdr.Status == 0) + else if (rsp->hdr.Status == 0) rsp->hdr.Status = STATUS_INVALID_INFO_CLASS; smb2_set_err_rsp(work);