Skip to content

Commit

Permalink
cifs: Add support for parsing WSL-style symlinks
Browse files Browse the repository at this point in the history
Linux CIFS client currently does not implement readlink() for WSL-style
symlinks. It is only able to detect that file is of WSL-style symlink, but
is not able to read target symlink location.

Add this missing functionality and implement support for parsing content of
WSL-style symlink.

The important note is that symlink target location stored for WSL symlink
reparse point (IO_REPARSE_TAG_LX_SYMLINK) is in UTF-8 encoding instead of
UTF-16 (which is used in whole SMB protocol and also in all other symlink
styles). So for proper locale/cp support it is needed to do conversion from
UTF-8 to local_nls.

Signed-off-by: Pali Rohár <[email protected]>
Signed-off-by: Steve French <[email protected]>
  • Loading branch information
pali authored and Steve French committed Nov 25, 2024
1 parent dd26bc0 commit 06a7adf
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 0 deletions.
49 changes: 49 additions & 0 deletions fs/smb/client/reparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,52 @@ static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
cifs_sb);
}

static int parse_reparse_wsl_symlink(struct reparse_wsl_symlink_data_buffer *buf,
struct cifs_sb_info *cifs_sb,
struct cifs_open_info_data *data)
{
int len = le16_to_cpu(buf->ReparseDataLength);
int symname_utf8_len;
__le16 *symname_utf16;
int symname_utf16_len;

if (len <= sizeof(buf->Flags)) {
cifs_dbg(VFS, "srv returned malformed wsl symlink buffer\n");
return -EIO;
}

/* PathBuffer is in UTF-8 but without trailing null-term byte */
symname_utf8_len = len - sizeof(buf->Flags);
/*
* Check that buffer does not contain null byte
* because Linux cannot process symlink with null byte.
*/
if (strnlen(buf->PathBuffer, symname_utf8_len) != symname_utf8_len) {
cifs_dbg(VFS, "srv returned null byte in wsl symlink target location\n");
return -EIO;
}
symname_utf16 = kzalloc(symname_utf8_len * 2, GFP_KERNEL);
if (!symname_utf16)
return -ENOMEM;
symname_utf16_len = utf8s_to_utf16s(buf->PathBuffer, symname_utf8_len,
UTF16_LITTLE_ENDIAN,
symname_utf16, symname_utf8_len * 2);
if (symname_utf16_len < 0) {
kfree(symname_utf16);
return symname_utf16_len;
}
symname_utf16_len *= 2; /* utf8s_to_utf16s() returns number of u16 items, not byte length */

data->symlink_target = cifs_strndup_from_utf16((u8 *)symname_utf16,
symname_utf16_len, true,
cifs_sb->local_nls);
kfree(symname_utf16);
if (!data->symlink_target)
return -ENOMEM;

return 0;
}

int parse_reparse_point(struct reparse_data_buffer *buf,
u32 plen, struct cifs_sb_info *cifs_sb,
const char *full_path,
Expand All @@ -666,6 +712,9 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
(struct reparse_symlink_data_buffer *)buf,
plen, unicode, cifs_sb, full_path, data);
case IO_REPARSE_TAG_LX_SYMLINK:
return parse_reparse_wsl_symlink(
(struct reparse_wsl_symlink_data_buffer *)buf,
cifs_sb, data);
case IO_REPARSE_TAG_AF_UNIX:
case IO_REPARSE_TAG_LX_FIFO:
case IO_REPARSE_TAG_LX_CHR:
Expand Down
9 changes: 9 additions & 0 deletions fs/smb/common/smb2pdu.h
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,15 @@ struct reparse_symlink_data_buffer {

/* See MS-FSCC 2.1.2.6 and cifspdu.h for struct reparse_posix_data */

/* For IO_REPARSE_TAG_LX_SYMLINK */
struct reparse_wsl_symlink_data_buffer {
__le32 ReparseTag;
__le16 ReparseDataLength;
__u16 Reserved;
__le32 Flags;
__u8 PathBuffer[]; /* Variable Length UTF-8 string without nul-term */
} __packed;

struct validate_negotiate_info_req {
__le32 Capabilities;
__u8 Guid[SMB2_CLIENT_GUID_SIZE];
Expand Down

0 comments on commit 06a7adf

Please sign in to comment.