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 <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-07 00:19:55 +09:00
parent 2103add92a
commit 10aeff72ab

View File

@ -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);