]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
cifs: Fix parsing native symlinks directory/file type
authorPali Rohár <pali@kernel.org>
Mon, 23 Sep 2024 20:29:30 +0000 (22:29 +0200)
committerSteve French <stfrench@microsoft.com>
Fri, 31 Jan 2025 18:51:44 +0000 (12:51 -0600)
As SMB protocol distinguish between symlink to directory and symlink to
file, add some mechanism to disallow resolving incompatible types.

When SMB symlink is of the directory type, ensure that its target path ends
with slash. This forces Linux to not allow resolving such symlink to file.

And when SMB symlink is of the file type and its target path ends with
slash then returns an error as such symlink is unresolvable. Such symlink
always points to invalid location as file cannot end with slash.

As POSIX server does not distinguish between symlinks to file and symlink
directory, do not apply this change for symlinks from POSIX SMB server. For
POSIX SMB servers, this change does nothing.

This mimics Windows behavior of native SMB symlinks.

Signed-off-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/inode.c
fs/smb/client/smb2file.c
fs/smb/client/smb2inode.c
fs/smb/client/smb2proto.h

index 8896c88320c8dec1de251dbb4f3d0b38e25c1b8e..9cc31cf6ebd071a612fda3e3cbac10cc67e68d1c 100644 (file)
@@ -1216,6 +1216,11 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
                                                              full_path,
                                                              iov, data);
                }
+
+               if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc) {
+                       bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY;
+                       rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb);
+               }
                break;
        }
 
index c5e689b2fc4973f522163b4c77c6725326193135..d609a20fb98a9c9fe9509e382ce8b4430e7ea682 100644 (file)
@@ -63,6 +63,52 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
        return sym;
 }
 
+int smb2_fix_symlink_target_type(char **target, bool directory, struct cifs_sb_info *cifs_sb)
+{
+       char *buf;
+       int len;
+
+       /*
+        * POSIX server does not distinguish between symlinks to file and
+        * symlink directory. So nothing is needed to fix on the client side.
+        */
+       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS)
+               return 0;
+
+       if (!*target)
+               return -EIO;
+
+       len = strlen(*target);
+       if (!len)
+               return -EIO;
+
+       /*
+        * If this is directory symlink and it does not have trailing slash then
+        * append it. Trailing slash simulates Windows/SMB behavior which do not
+        * allow resolving directory symlink to file.
+        */
+       if (directory && (*target)[len-1] != '/') {
+               buf = krealloc(*target, len+2, GFP_KERNEL);
+               if (!buf)
+                       return -ENOMEM;
+               buf[len] = '/';
+               buf[len+1] = '\0';
+               *target = buf;
+               len++;
+       }
+
+       /*
+        * If this is a file (non-directory) symlink and it points to path name
+        * with trailing slash then this is an invalid symlink because file name
+        * cannot contain slash character. File name with slash is invalid on
+        * both Windows and Linux systems. So return an error for such symlink.
+        */
+       if (!directory && (*target)[len-1] == '/')
+               return -EIO;
+
+       return 0;
+}
+
 int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov,
                                const char *full_path, char **path)
 {
@@ -132,6 +178,11 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32
                                               NULL, NULL, NULL);
                                oparms->create_options &= ~OPEN_REPARSE_POINT;
                        }
+                       if (!rc) {
+                               bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY;
+                               rc = smb2_fix_symlink_target_type(&data->symlink_target,
+                                                                 directory, oparms->cifs_sb);
+                       }
                }
        }
 
index c97f14757c27c3b9e58fe30b2a1c2ecbc2f577b2..5dfb30b0a852c9b921f8c76955ee2b088a8645e8 100644 (file)
@@ -1010,6 +1010,11 @@ int smb2_query_path_info(const unsigned int xid,
                        else
                                rc = -EOPNOTSUPP;
                }
+
+               if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc) {
+                       bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY;
+                       rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb);
+               }
                break;
        case -EREMOTE:
                break;
index 10f5e37d153092e98abe74443865ad3693dfab01..2336dfb23f363b2f24faa4f62de5fa99b3b4acb8 100644 (file)
@@ -111,6 +111,7 @@ extern int smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
                          struct cifs_sb_info *cifs_sb,
                          const unsigned char *path, char *pbuf,
                          unsigned int *pbytes_read);
+int smb2_fix_symlink_target_type(char **target, bool directory, struct cifs_sb_info *cifs_sb);
 int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len,
                              bool relative,
                              const char *full_path,