[PATCH v2] smb: client: preserve leading slash for POSIX absolute symlink targets

Steve French <[email protected]>
Newsgroups org.kernel.vger.linux-cifs
Message-ID <[email protected]>
When creating a native SMB symbolic link (CIFS_SYMLINK_TYPE_NATIVE) whose
target is an absolute path on a mount that uses POSIX paths, the leading
path separator was silently dropped from the stored symlink target.

create_native_symlink() converted the target to UTF-16 with
cifs_convert_path_to_utf16().  That helper is intended for share-relative
SMB paths and therefore unconditionally strips a leading path separator.
For an absolute POSIX symlink target the leading '/' is significant, so a
target of "/foo/bar" was stored - and read back - as "foo/bar", even
though the reparse point was still flagged as absolute
(SYMLINK_FLAG_RELATIVE cleared).

On a POSIX paths mount the symlink target is stored verbatim, so convert
it directly with cifs_strndup_to_utf16() instead.  This preserves the
leading separator, avoids the leading-backslash stripping that
cifs_convert_path_to_utf16() also performs (a backslash is a valid POSIX
filename character), and uses NO_MAP_UNI_RSVD to match the readback path
in smb2_parse_native_symlink(), which always converts the target with
cifs_strndup_from_utf16() / NO_MAP_UNI_RSVD.  This mirrors how the NFS and
WSL reparse symlink creators convert their targets.

The NT-style absolute symlink handling, which needs the "\??\" prefix and
drive-letter colon preserved, continues to use cifs_convert_path_to_utf16()
together with the existing masking of those bytes.

Fixes: 12b466eb52d9 ("cifs: Fix creating and resolving absolute NT-style symlinks")
Signed-off-by: Steve French <[email protected]>
---
 fs/smb/client/reparse.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c
index cd1e1eaee67a..5cc5b0410d48 100644
--- a/fs/smb/client/reparse.c
+++ b/fs/smb/client/reparse.c
@@ -67,6 +67,7 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,
 	char *sym = NULL;
 	struct kvec iov;
 	bool directory;
+	int path_len;
 	int rc = 0;
 
 	if (strlen(symname) > REPARSE_SYM_PATH_MAX)
@@ -168,7 +169,21 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,
 	if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/')
 		sym[0] = sym[1] = sym[2] = sym[5] = '_';
 
-	path = cifs_convert_path_to_utf16(sym, cifs_sb);
+	/*
+	 * On a POSIX paths mount the symlink target is stored verbatim, so
+	 * convert it with cifs_strndup_to_utf16().  cifs_convert_path_to_utf16()
+	 * must not be used here: it strips a leading path separator (it is
+	 * meant for share-relative SMB paths), which would corrupt an absolute
+	 * POSIX symlink target such as "/foo/bar".  Using NO_MAP_UNI_RSVD also
+	 * matches the readback path in smb2_parse_native_symlink().
+	 */
+	if (sbflags & CIFS_MOUNT_POSIX_PATHS)
+		path = cifs_strndup_to_utf16(sym, strlen(sym), &path_len,
+					     cifs_sb->local_nls,
+					     NO_MAP_UNI_RSVD);
+	else
+		path = cifs_convert_path_to_utf16(sym, cifs_sb);
+
 	if (!path) {
 		rc = -ENOMEM;
 		goto out;
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.