fs/smb/client/reparse.c | 49 +++++++++++++++++++++++++++++++++++++++++ fs/smb/common/smb2pdu.h | 9 ++++++++ 2 files changed, 58 insertions(+)
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 <pali@kernel.org>
---
fs/smb/client/reparse.c | 49 +++++++++++++++++++++++++++++++++++++++++
fs/smb/common/smb2pdu.h | 9 ++++++++
2 files changed, 58 insertions(+)
diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c
index a577b2d2a4fc..6e9d914bac41 100644
--- a/fs/smb/client/reparse.c
+++ b/fs/smb/client/reparse.c
@@ -875,6 +875,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,
@@ -894,6 +940,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:
diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h
index c769f9dbc0b4..275184c31a89 100644
--- a/fs/smb/common/smb2pdu.h
+++ b/fs/smb/common/smb2pdu.h
@@ -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];
--
2.20.1
This patch commit 06a7adf318a30bdcfa1222ed6d2640e6bb266d7b Author: Pali Rohár <pali@kernel.org> Date: Sat Sep 28 13:21:24 2024 +0200 cifs: Add support for parsing WSL-style symlinks led to compile warning: CC [M] fs/smb/client/reparse.o CHECK fs/smb/client/reparse.c fs/smb/client/reparse.c:679:45: warning: incorrect type in argument 4 (different base types) fs/smb/client/reparse.c:679:45: expected unsigned short [usertype] *pwcs fs/smb/client/reparse.c:679:45: got restricted __le16 [usertype] *[assigned] symname_utf16 Let me know if updated version needed On Sun, Oct 6, 2024 at 4:07 AM Pali Rohár <pali@kernel.org> wrote: > > 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 <pali@kernel.org> > --- > fs/smb/client/reparse.c | 49 +++++++++++++++++++++++++++++++++++++++++ > fs/smb/common/smb2pdu.h | 9 ++++++++ > 2 files changed, 58 insertions(+) > > diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c > index a577b2d2a4fc..6e9d914bac41 100644 > --- a/fs/smb/client/reparse.c > +++ b/fs/smb/client/reparse.c > @@ -875,6 +875,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, > @@ -894,6 +940,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: > diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h > index c769f9dbc0b4..275184c31a89 100644 > --- a/fs/smb/common/smb2pdu.h > +++ b/fs/smb/common/smb2pdu.h > @@ -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]; > -- > 2.20.1 > > -- Thanks, Steve
On Monday 25 November 2024 15:24:03 Steve French wrote: > This patch > > commit 06a7adf318a30bdcfa1222ed6d2640e6bb266d7b > Author: Pali Rohár <pali@kernel.org> > Date: Sat Sep 28 13:21:24 2024 +0200 > > cifs: Add support for parsing WSL-style symlinks > > > led to compile warning: > > CC [M] fs/smb/client/reparse.o > CHECK fs/smb/client/reparse.c > fs/smb/client/reparse.c:679:45: warning: incorrect type in argument 4 > (different base types) > fs/smb/client/reparse.c:679:45: expected unsigned short [usertype] *pwcs > fs/smb/client/reparse.c:679:45: got restricted __le16 [usertype] > *[assigned] symname_utf16 > > > Let me know if updated version needed I have already reacted for this problem more than month ago: https://lore.kernel.org/oe-kbuild-all/20241007213401.bsfqwlb4c2k6ynlr@pali/ But nobody on the list replied :-( So I do not know what is the best way to handle this issue.
© 2016 - 2024 Red Hat, Inc.