]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ksmbd: check if a mount point is crossed during path lookup
authorNamjae Jeon <linkinjeon@kernel.org>
Mon, 18 Dec 2023 15:34:15 +0000 (00:34 +0900)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 23 Dec 2023 09:41:57 +0000 (10:41 +0100)
[ Upstream commit 2b57a4322b1b14348940744fdc02f9a86cbbdbeb ]

Since commit 74d7970febf7 ("ksmbd: fix racy issue from using ->d_parent and
->d_name"), ksmbd can not lookup cross mount points. If last component is
a cross mount point during path lookup, check if it is crossed to follow it
down. And allow path lookup to cross a mount point when a crossmnt
parameter is set to 'yes' in smb.conf.

Cc: stable@vger.kernel.org
Fixes: 74d7970febf7 ("ksmbd: fix racy issue from using ->d_parent and ->d_name")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/ksmbd/ksmbd_netlink.h
fs/ksmbd/smb2pdu.c
fs/ksmbd/vfs.c
fs/ksmbd/vfs.h

index fb9626383f865fb68de8e7d42653d63231f5bea5..821ed8e3cbee2a0bb2971c2e0c37f32b6a9db7da 100644 (file)
@@ -352,7 +352,8 @@ enum KSMBD_TREE_CONN_STATUS {
 #define KSMBD_SHARE_FLAG_STREAMS               BIT(11)
 #define KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS       BIT(12)
 #define KSMBD_SHARE_FLAG_ACL_XATTR             BIT(13)
-#define KSMBD_SHARE_FLAG_UPDATE                BIT(14)
+#define KSMBD_SHARE_FLAG_UPDATE                        BIT(14)
+#define KSMBD_SHARE_FLAG_CROSSMNT              BIT(15)
 
 /*
  * Tree connect request flags.
index e99ebbe63a7aed89f2f5c0fa14dd71df8cca803f..ecabb0b2eb13d1ebdc9c76c27bc2a69e531914f7 100644 (file)
@@ -2475,8 +2475,9 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
        }
 }
 
-static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
-                     int open_flags, umode_t posix_mode, bool is_dir)
+static int smb2_creat(struct ksmbd_work *work, struct path *parent_path,
+                     struct path *path, char *name, int open_flags,
+                     umode_t posix_mode, bool is_dir)
 {
        struct ksmbd_tree_connect *tcon = work->tcon;
        struct ksmbd_share_config *share = tcon->share_conf;
@@ -2503,7 +2504,7 @@ static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
                        return rc;
        }
 
-       rc = ksmbd_vfs_kern_path_locked(work, name, 0, path, 0);
+       rc = ksmbd_vfs_kern_path_locked(work, name, 0, parent_path, path, 0);
        if (rc) {
                pr_err("cannot get linux path (%s), err = %d\n",
                       name, rc);
@@ -2570,7 +2571,7 @@ int smb2_open(struct ksmbd_work *work)
        struct ksmbd_tree_connect *tcon = work->tcon;
        struct smb2_create_req *req;
        struct smb2_create_rsp *rsp;
-       struct path path;
+       struct path path, parent_path;
        struct ksmbd_share_config *share = tcon->share_conf;
        struct ksmbd_file *fp = NULL;
        struct file *filp = NULL;
@@ -2791,7 +2792,8 @@ int smb2_open(struct ksmbd_work *work)
                goto err_out1;
        }
 
-       rc = ksmbd_vfs_kern_path_locked(work, name, LOOKUP_NO_SYMLINKS, &path, 1);
+       rc = ksmbd_vfs_kern_path_locked(work, name, LOOKUP_NO_SYMLINKS,
+                                       &parent_path, &path, 1);
        if (!rc) {
                file_present = true;
 
@@ -2911,7 +2913,8 @@ int smb2_open(struct ksmbd_work *work)
 
        /*create file if not present */
        if (!file_present) {
-               rc = smb2_creat(work, &path, name, open_flags, posix_mode,
+               rc = smb2_creat(work, &parent_path, &path, name, open_flags,
+                               posix_mode,
                                req->CreateOptions & FILE_DIRECTORY_FILE_LE);
                if (rc) {
                        if (rc == -ENOENT) {
@@ -3326,8 +3329,9 @@ int smb2_open(struct ksmbd_work *work)
 
 err_out:
        if (file_present || created) {
-               inode_unlock(d_inode(path.dentry->d_parent));
-               dput(path.dentry);
+               inode_unlock(d_inode(parent_path.dentry));
+               path_put(&path);
+               path_put(&parent_path);
        }
        ksmbd_revert_fsids(work);
 err_out1:
@@ -5553,7 +5557,7 @@ static int smb2_create_link(struct ksmbd_work *work,
                            struct nls_table *local_nls)
 {
        char *link_name = NULL, *target_name = NULL, *pathname = NULL;
-       struct path path;
+       struct path path, parent_path;
        bool file_present = false;
        int rc;
 
@@ -5583,7 +5587,7 @@ static int smb2_create_link(struct ksmbd_work *work,
 
        ksmbd_debug(SMB, "target name is %s\n", target_name);
        rc = ksmbd_vfs_kern_path_locked(work, link_name, LOOKUP_NO_SYMLINKS,
-                                       &path, 0);
+                                       &parent_path, &path, 0);
        if (rc) {
                if (rc != -ENOENT)
                        goto out;
@@ -5613,8 +5617,9 @@ static int smb2_create_link(struct ksmbd_work *work,
                rc = -EINVAL;
 out:
        if (file_present) {
-               inode_unlock(d_inode(path.dentry->d_parent));
+               inode_unlock(d_inode(parent_path.dentry));
                path_put(&path);
+               path_put(&parent_path);
        }
        if (!IS_ERR(link_name))
                kfree(link_name);
index f2013963cef014936a785444745ca6eb4cf6a310..8cb46abf2c76dda06c5bac04985d04ca3026a488 100644 (file)
@@ -63,13 +63,13 @@ int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child)
 
 static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
                                        char *pathname, unsigned int flags,
+                                       struct path *parent_path,
                                        struct path *path)
 {
        struct qstr last;
        struct filename *filename;
        struct path *root_share_path = &share_conf->vfs_path;
        int err, type;
-       struct path parent_path;
        struct dentry *d;
 
        if (pathname[0] == '\0') {
@@ -84,7 +84,7 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
                return PTR_ERR(filename);
 
        err = vfs_path_parent_lookup(filename, flags,
-                                    &parent_path, &last, &type,
+                                    parent_path, &last, &type,
                                     root_share_path);
        if (err) {
                putname(filename);
@@ -92,13 +92,13 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
        }
 
        if (unlikely(type != LAST_NORM)) {
-               path_put(&parent_path);
+               path_put(parent_path);
                putname(filename);
                return -ENOENT;
        }
 
-       inode_lock_nested(parent_path.dentry->d_inode, I_MUTEX_PARENT);
-       d = lookup_one_qstr_excl(&last, parent_path.dentry, 0);
+       inode_lock_nested(parent_path->dentry->d_inode, I_MUTEX_PARENT);
+       d = lookup_one_qstr_excl(&last, parent_path->dentry, 0);
        if (IS_ERR(d))
                goto err_out;
 
@@ -108,15 +108,22 @@ static int ksmbd_vfs_path_lookup_locked(struct ksmbd_share_config *share_conf,
        }
 
        path->dentry = d;
-       path->mnt = share_conf->vfs_path.mnt;
-       path_put(&parent_path);
-       putname(filename);
+       path->mnt = mntget(parent_path->mnt);
+
+       if (test_share_config_flag(share_conf, KSMBD_SHARE_FLAG_CROSSMNT)) {
+               err = follow_down(path);
+               if (err < 0) {
+                       path_put(path);
+                       goto err_out;
+               }
+       }
 
+       putname(filename);
        return 0;
 
 err_out:
-       inode_unlock(parent_path.dentry->d_inode);
-       path_put(&parent_path);
+       inode_unlock(d_inode(parent_path->dentry));
+       path_put(parent_path);
        putname(filename);
        return -ENOENT;
 }
@@ -1197,14 +1204,14 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name,
  * Return:     0 on success, otherwise error
  */
 int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
-                              unsigned int flags, struct path *path,
-                              bool caseless)
+                              unsigned int flags, struct path *parent_path,
+                              struct path *path, bool caseless)
 {
        struct ksmbd_share_config *share_conf = work->tcon->share_conf;
        int err;
-       struct path parent_path;
 
-       err = ksmbd_vfs_path_lookup_locked(share_conf, name, flags, path);
+       err = ksmbd_vfs_path_lookup_locked(share_conf, name, flags, parent_path,
+                                          path);
        if (!err)
                return 0;
 
@@ -1219,10 +1226,10 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
                path_len = strlen(filepath);
                remain_len = path_len;
 
-               parent_path = share_conf->vfs_path;
-               path_get(&parent_path);
+               *parent_path = share_conf->vfs_path;
+               path_get(parent_path);
 
-               while (d_can_lookup(parent_path.dentry)) {
+               while (d_can_lookup(parent_path->dentry)) {
                        char *filename = filepath + path_len - remain_len;
                        char *next = strchrnul(filename, '/');
                        size_t filename_len = next - filename;
@@ -1231,7 +1238,7 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
                        if (filename_len == 0)
                                break;
 
-                       err = ksmbd_vfs_lookup_in_dir(&parent_path, filename,
+                       err = ksmbd_vfs_lookup_in_dir(parent_path, filename,
                                                      filename_len,
                                                      work->conn->um);
                        if (err)
@@ -1248,8 +1255,8 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
                                goto out2;
                        else if (is_last)
                                goto out1;
-                       path_put(&parent_path);
-                       parent_path = *path;
+                       path_put(parent_path);
+                       *parent_path = *path;
 
                        next[0] = '/';
                        remain_len -= filename_len + 1;
@@ -1257,16 +1264,17 @@ int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
 
                err = -EINVAL;
 out2:
-               path_put(&parent_path);
+               path_put(parent_path);
 out1:
                kfree(filepath);
        }
 
        if (!err) {
-               err = ksmbd_vfs_lock_parent(parent_path.dentry, path->dentry);
-               if (err)
-                       dput(path->dentry);
-               path_put(&parent_path);
+               err = ksmbd_vfs_lock_parent(parent_path->dentry, path->dentry);
+               if (err) {
+                       path_put(path);
+                       path_put(parent_path);
+               }
        }
        return err;
 }
index 2e87136f9eba8b9d204a4f82cebfed735dc63cb5..b544c882c61175524646020b1bcadbd5911e9fcf 100644 (file)
@@ -154,8 +154,8 @@ int ksmbd_vfs_xattr_stream_name(char *stream_name, char **xattr_stream_name,
 int ksmbd_vfs_remove_xattr(struct user_namespace *user_ns,
                           const struct path *path, char *attr_name);
 int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name,
-                              unsigned int flags, struct path *path,
-                              bool caseless);
+                              unsigned int flags, struct path *parent_path,
+                              struct path *path, bool caseless);
 struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
                                          const char *name,
                                          unsigned int flags,