[PATCH] cifs: Add support for parsing WSL-style symlinks

Pali Rohár posted 1 patch 1 month, 3 weeks ago
fs/smb/client/reparse.c | 49 +++++++++++++++++++++++++++++++++++++++++
fs/smb/common/smb2pdu.h |  9 ++++++++
2 files changed, 58 insertions(+)
[PATCH] cifs: Add support for parsing WSL-style symlinks
Posted by Pali Rohár 1 month, 3 weeks ago
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

Re: [PATCH] cifs: Add support for parsing WSL-style symlinks
Posted by Steve French 2 days, 1 hour ago
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
Re: [PATCH] cifs: Add support for parsing WSL-style symlinks
Posted by Pali Rohár 2 days ago
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.