]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
cifs: Fix parsing native symlinks relative to the export
authorPali Rohár <pali@kernel.org>
Mon, 23 Sep 2024 20:40:38 +0000 (22:40 +0200)
committerSteve French <stfrench@microsoft.com>
Mon, 25 Nov 2024 20:50:32 +0000 (14:50 -0600)
SMB symlink which has SYMLINK_FLAG_RELATIVE set is relative (as opposite of
the absolute) and it can be relative either to the current directory (where
is the symlink stored) or relative to the top level export path. To what it
is relative depends on the first character of the symlink target path.

If the first character is path separator then symlink is relative to the
export, otherwise to the current directory. Linux (and generally POSIX
systems) supports only symlink paths relative to the current directory
where is symlink stored.

Currently if Linux SMB client reads relative SMB symlink with first
character as path separator (slash), it let as is. Which means that Linux
interpret it as absolute symlink pointing from the root (/). But this
location is different than the top level directory of SMB export (unless
SMB export was mounted to the root) and thefore SMB symlinks relative to
the export are interpreted wrongly by Linux SMB client.

Fix this problem. As Linux does not have equivalent of the path relative to
the top of the mount point, convert such symlink target path relative to
the current directory. Do this by prepending "../" pattern N times before
the SMB target path, where N is the number of path separators found in SMB
symlink path.

So for example, if SMB share is mounted to Linux path /mnt/share/, symlink
is stored in file /mnt/share/test/folder1/symlink (so SMB symlink path is
test\folder1\symlink) and SMB symlink target points to \test\folder2\file,
then convert symlink target path to Linux path ../../test/folder2/file.

Deduplicate code for parsing SMB symlinks in native form from functions
smb2_parse_symlink_response() and parse_reparse_native_symlink() into new
function smb2_parse_native_symlink() and pass into this new function a new
full_path parameter from callers, which specify SMB full path where is
symlink stored.

This change fixes resolving of the native Windows symlinks relative to the
top level directory of the SMB share.

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

index fc33dfe7e925453e6366144934bffa30af5bbe58..e97e6dfd665cdc6d64de128da9479b48ff2adf56 100644 (file)
@@ -594,6 +594,7 @@ struct smb_version_operations {
        /* Check for STATUS_NETWORK_NAME_DELETED */
        bool (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv);
        int (*parse_reparse_point)(struct cifs_sb_info *cifs_sb,
+                                  const char *full_path,
                                   struct kvec *rsp_iov,
                                   struct cifs_open_info_data *data);
        int (*create_reparse_symlink)(const unsigned int xid,
index 075985bfb13a8c5296c562b9d596b43a9e4edceb..c5be95f102a49f6235273799b451f5d3060f16d4 100644 (file)
@@ -661,6 +661,7 @@ char *extract_hostname(const char *unc);
 char *extract_sharename(const char *unc);
 int parse_reparse_point(struct reparse_data_buffer *buf,
                        u32 plen, struct cifs_sb_info *cifs_sb,
+                       const char *full_path,
                        bool unicode, struct cifs_open_info_data *data);
 int __cifs_sfu_make_node(unsigned int xid, struct inode *inode,
                         struct dentry *dentry, struct cifs_tcon *tcon,
index de8063b44072fb76eaed45b10fdde4d61a9cb81c..befe43460dcb2855f7b460801d3376ea9d90df2d 100644 (file)
@@ -1137,6 +1137,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
                        rc = 0;
                } else if (iov && server->ops->parse_reparse_point) {
                        rc = server->ops->parse_reparse_point(cifs_sb,
+                                                             full_path,
                                                              iov, data);
                }
                break;
index 90da1e2b6217b6afe00f4cff9be601e8a5aff2d7..f74d0a86f44a4e81f5884f3a4acbc5c71f19d985 100644 (file)
@@ -535,9 +535,76 @@ static int parse_reparse_posix(struct reparse_posix_data *buf,
        return 0;
 }
 
+int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len,
+                             bool unicode, bool relative,
+                             const char *full_path,
+                             struct cifs_sb_info *cifs_sb)
+{
+       char sep = CIFS_DIR_SEP(cifs_sb);
+       char *linux_target = NULL;
+       char *smb_target = NULL;
+       int levels;
+       int rc;
+       int i;
+
+       smb_target = cifs_strndup_from_utf16(buf, len, unicode, cifs_sb->local_nls);
+       if (!smb_target) {
+               rc = -ENOMEM;
+               goto out;
+       }
+
+       if (smb_target[0] == sep && relative) {
+               /*
+                * This is a relative SMB symlink from the top of the share,
+                * which is the top level directory of the Linux mount point.
+                * Linux does not support such relative symlinks, so convert
+                * it to the relative symlink from the current directory.
+                * full_path is the SMB path to the symlink (from which is
+                * extracted current directory) and smb_target is the SMB path
+                * where symlink points, therefore full_path must always be on
+                * the SMB share.
+                */
+               int smb_target_len = strlen(smb_target)+1;
+               levels = 0;
+               for (i = 1; full_path[i]; i++) { /* i=1 to skip leading sep */
+                       if (full_path[i] == sep)
+                               levels++;
+               }
+               linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL);
+               if (!linux_target) {
+                       rc = -ENOMEM;
+                       goto out;
+               }
+               for (i = 0; i < levels; i++) {
+                       linux_target[i*3 + 0] = '.';
+                       linux_target[i*3 + 1] = '.';
+                       linux_target[i*3 + 2] = sep;
+               }
+               memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */
+       } else {
+               linux_target = smb_target;
+               smb_target = NULL;
+       }
+
+       if (sep == '\\')
+               convert_delimiter(linux_target, '/');
+
+       rc = 0;
+       *target = linux_target;
+
+       cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, *target);
+
+out:
+       if (rc != 0)
+               kfree(linux_target);
+       kfree(smb_target);
+       return rc;
+}
+
 static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
                                 u32 plen, bool unicode,
                                 struct cifs_sb_info *cifs_sb,
+                                const char *full_path,
                                 struct cifs_open_info_data *data)
 {
        unsigned int len;
@@ -552,20 +619,18 @@ static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
                return -EIO;
        }
 
-       data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs,
-                                                      len, unicode,
-                                                      cifs_sb->local_nls);
-       if (!data->symlink_target)
-               return -ENOMEM;
-
-       convert_delimiter(data->symlink_target, '/');
-       cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target);
-
-       return 0;
+       return smb2_parse_native_symlink(&data->symlink_target,
+                                        sym->PathBuffer + offs,
+                                        len,
+                                        unicode,
+                                        le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE,
+                                        full_path,
+                                        cifs_sb);
 }
 
 int parse_reparse_point(struct reparse_data_buffer *buf,
                        u32 plen, struct cifs_sb_info *cifs_sb,
+                       const char *full_path,
                        bool unicode, struct cifs_open_info_data *data)
 {
        struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
@@ -580,7 +645,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
        case IO_REPARSE_TAG_SYMLINK:
                return parse_reparse_symlink(
                        (struct reparse_symlink_data_buffer *)buf,
-                       plen, unicode, cifs_sb, data);
+                       plen, unicode, cifs_sb, full_path, data);
        case IO_REPARSE_TAG_LX_SYMLINK:
        case IO_REPARSE_TAG_AF_UNIX:
        case IO_REPARSE_TAG_LX_FIFO:
@@ -596,6 +661,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
 }
 
 int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
+                            const char *full_path,
                             struct kvec *rsp_iov,
                             struct cifs_open_info_data *data)
 {
@@ -605,7 +671,7 @@ int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
 
        buf = (struct reparse_data_buffer *)((u8 *)io +
                                             le32_to_cpu(io->OutputOffset));
-       return parse_reparse_point(buf, plen, cifs_sb, true, data);
+       return parse_reparse_point(buf, plen, cifs_sb, full_path, true, data);
 }
 
 static void wsl_to_fattr(struct cifs_open_info_data *data,
index 2a9f4f9f79de08c40cf66f63b2a5c511bd59c4ad..ff05b0e75c92840668b3798843c8d624b4dcd498 100644 (file)
@@ -117,7 +117,9 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
 int smb2_mknod_reparse(unsigned int xid, struct inode *inode,
                       struct dentry *dentry, struct cifs_tcon *tcon,
                       const char *full_path, umode_t mode, dev_t dev);
-int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, struct kvec *rsp_iov,
+int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
+                            const char *full_path,
+                            struct kvec *rsp_iov,
                             struct cifs_open_info_data *data);
 
 #endif /* _CIFS_REPARSE_H */
index 9a6ece66c4d34ef7cc068d1ba94aca62a92a3a0f..abca214f923c21224eb502b12a34494e7dade864 100644 (file)
@@ -994,6 +994,7 @@ static int cifs_query_symlink(const unsigned int xid,
 }
 
 static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb,
+                                   const char *full_path,
                                    struct kvec *rsp_iov,
                                    struct cifs_open_info_data *data)
 {
@@ -1004,7 +1005,7 @@ static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb,
 
        buf = (struct reparse_data_buffer *)((__u8 *)&io->hdr.Protocol +
                                             le32_to_cpu(io->DataOffset));
-       return parse_reparse_point(buf, plen, cifs_sb, unicode, data);
+       return parse_reparse_point(buf, plen, cifs_sb, full_path, unicode, data);
 }
 
 static bool
index e301349b0078d18792721256f4eda67bf41e6255..e836bc2193ddd30df2d54a5abf791af5343aa4cc 100644 (file)
@@ -63,12 +63,12 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
        return sym;
 }
 
-int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path)
+int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov,
+                               const char *full_path, char **path)
 {
        struct smb2_symlink_err_rsp *sym;
        unsigned int sub_offs, sub_len;
        unsigned int print_offs, print_len;
-       char *s;
 
        if (!cifs_sb || !iov || !iov->iov_base || !iov->iov_len || !path)
                return -EINVAL;
@@ -86,15 +86,13 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec
            iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len)
                return -EINVAL;
 
-       s = cifs_strndup_from_utf16((char *)sym->PathBuffer + sub_offs, sub_len, true,
-                                   cifs_sb->local_nls);
-       if (!s)
-               return -ENOMEM;
-       convert_delimiter(s, '/');
-       cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, s);
-
-       *path = s;
-       return 0;
+       return smb2_parse_native_symlink(path,
+                                        (char *)sym->PathBuffer + sub_offs,
+                                        sub_len,
+                                        true,
+                                        le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE,
+                                        full_path,
+                                        cifs_sb);
 }
 
 int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock, void *buf)
@@ -126,6 +124,7 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32
                        goto out;
                if (hdr->Status == STATUS_STOPPED_ON_SYMLINK) {
                        rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov,
+                                                        oparms->path,
                                                         &data->symlink_target);
                        if (!rc) {
                                memset(smb2_data, 0, sizeof(*smb2_data));
index e49d0c25eb0384e04a0c5663029d20a76752887e..a188908914fe8f4626f79935d6960c4dbc8b5f74 100644 (file)
@@ -828,6 +828,7 @@ finished:
 
 static int parse_create_response(struct cifs_open_info_data *data,
                                 struct cifs_sb_info *cifs_sb,
+                                const char *full_path,
                                 const struct kvec *iov)
 {
        struct smb2_create_rsp *rsp = iov->iov_base;
@@ -841,6 +842,7 @@ static int parse_create_response(struct cifs_open_info_data *data,
                break;
        case STATUS_STOPPED_ON_SYMLINK:
                rc = smb2_parse_symlink_response(cifs_sb, iov,
+                                                full_path,
                                                 &data->symlink_target);
                if (rc)
                        return rc;
@@ -930,14 +932,14 @@ int smb2_query_path_info(const unsigned int xid,
 
        switch (rc) {
        case 0:
-               rc = parse_create_response(data, cifs_sb, &out_iov[0]);
+               rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]);
                break;
        case -EOPNOTSUPP:
                /*
                 * BB TODO: When support for special files added to Samba
                 * re-verify this path.
                 */
-               rc = parse_create_response(data, cifs_sb, &out_iov[0]);
+               rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]);
                if (rc || !data->reparse_point)
                        goto out;
 
index 71504b30909e1e1f1cb78774e1d98e5cb0dc9a78..09349fa8da039aff8412c5090a9b242a58226025 100644 (file)
@@ -111,7 +111,14 @@ 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_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path);
+int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len,
+                             bool unicode, bool relative,
+                             const char *full_path,
+                             struct cifs_sb_info *cifs_sb);
+int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb,
+                               const struct kvec *iov,
+                               const char *full_path,
+                               char **path);
 int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock,
                   void *buf);
 extern int smb2_unlock_range(struct cifsFileInfo *cfile,