diff mbox series

cifs: Add support for parsing WSL-style symlinks

Message ID 20241006090548.30053-1-pali@kernel.org (mailing list archive)
State New, archived
Headers show
Series cifs: Add support for parsing WSL-style symlinks | expand

Commit Message

Pali Rohár Oct. 6, 2024, 9:05 a.m. UTC
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(+)

Comments

Pali Rohár Oct. 7, 2024, 9:34 p.m. UTC | #1
On Tuesday 08 October 2024 04:21:30 kernel test robot wrote:
> Hi Pali,
> 
> kernel test robot noticed the following build warnings:
> 
> [auto build test WARNING on cifs/for-next]
> [also build test WARNING on linus/master v6.12-rc2 next-20241004]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
> 
> url:    https://github.com/intel-lab-lkp/linux/commits/Pali-Roh-r/cifs-Add-support-for-parsing-WSL-style-symlinks/20241006-170802
> base:   git://git.samba.org/sfrench/cifs-2.6.git for-next
> patch link:    https://lore.kernel.org/r/20241006090548.30053-1-pali%40kernel.org
> patch subject: [PATCH] cifs: Add support for parsing WSL-style symlinks
> config: x86_64-randconfig-122-20241008 (https://download.01.org/0day-ci/archive/20241008/202410080458.o4vvmJkR-lkp@intel.com/config)
> compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241008/202410080458.o4vvmJkR-lkp@intel.com/reproduce)
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202410080458.o4vvmJkR-lkp@intel.com/
> 
> sparse warnings: (new ones prefixed by >>)
> >> fs/smb/client/reparse.c:676:45: sparse: sparse: incorrect type in argument 4 (different base types) @@     expected unsigned short [usertype] *pwcs @@     got restricted __le16 [usertype] *[assigned] symname_utf16 @@
>    fs/smb/client/reparse.c:676:45: sparse:     expected unsigned short [usertype] *pwcs
>    fs/smb/client/reparse.c:676:45: sparse:     got restricted __le16 [usertype] *[assigned] symname_utf16
> 
> vim +676 fs/smb/client/reparse.c
> 
>    646	
>    647	static int parse_reparse_wsl_symlink(struct reparse_wsl_symlink_data_buffer *buf,
>    648					     struct cifs_sb_info *cifs_sb,
>    649					     struct cifs_open_info_data *data)
>    650	{
>    651		int len = le16_to_cpu(buf->ReparseDataLength);
>    652		int symname_utf8_len;
>    653		__le16 *symname_utf16;
>    654		int symname_utf16_len;
>    655	
>    656		if (len <= sizeof(buf->Flags)) {
>    657			cifs_dbg(VFS, "srv returned malformed wsl symlink buffer\n");
>    658			return -EIO;
>    659		}
>    660	
>    661		/* PathBuffer is in UTF-8 but without trailing null-term byte */
>    662		symname_utf8_len = len - sizeof(buf->Flags);
>    663		/*
>    664		 * Check that buffer does not contain null byte
>    665		 * because Linux cannot process symlink with null byte.
>    666		 */
>    667		if (strnlen(buf->PathBuffer, symname_utf8_len) != symname_utf8_len) {
>    668			cifs_dbg(VFS, "srv returned null byte in wsl symlink target location\n");
>    669			return -EIO;
>    670		}
>    671		symname_utf16 = kzalloc(symname_utf8_len * 2, GFP_KERNEL);
>    672		if (!symname_utf16)
>    673			return -ENOMEM;
>    674		symname_utf16_len = utf8s_to_utf16s(buf->PathBuffer, symname_utf8_len,
>    675						    UTF16_LITTLE_ENDIAN,
>  > 676						    symname_utf16, symname_utf8_len * 2);

Hello, I'm not sure if there is a real problem or it is just a
false-positive report. Please look at it.

I saw that in cifs code is common pattern to use __le16* type for
UTF-16-LE string.

Kernel nls library function utf8s_to_utf16s for output buffer is
expecting wchar_t* type, also for the case when it is encoding to
UTF-16-LE (requested by UTF16_LITTLE_ENDIAN).

And cifs_strndup_from_utf16 for input UTF-16-LE string is using u8*
type.

So what kind of type for symname_utf16 buffer should be used? It stores
UTF-16-LE string. Type u8*, u16*, __le16*, wchar_t* or short*?

I guess that it should be possible to mute this report by adding another
explicit cast and call utf8s_to_utf16s() with "(wchar_t*)symname_utf16"
as 4th parameter.

>    677		if (symname_utf16_len < 0) {
>    678			kfree(symname_utf16);
>    679			return symname_utf16_len;
>    680		}
>    681		symname_utf16_len *= 2; /* utf8s_to_utf16s() returns number of u16 items, not byte length */
>    682	
>    683		data->symlink_target = cifs_strndup_from_utf16((u8 *)symname_utf16,
>    684							       symname_utf16_len, true,
>    685							       cifs_sb->local_nls);
>    686		kfree(symname_utf16);
>    687		if (!data->symlink_target)
>    688			return -ENOMEM;
>    689	
>    690		return 0;
>    691	}
>    692	
> 
> -- 
> 0-DAY CI Kernel Test Service
> https://github.com/intel/lkp-tests/wiki
diff mbox series

Patch

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];