cifsd: add the check if parent is stable by unexpected rename
authorNamjae Jeon <namjae.jeon@samsung.com>
Tue, 13 Apr 2021 04:18:10 +0000 (13:18 +0900)
committerSteve French <stfrench@microsoft.com>
Tue, 11 May 2021 00:15:39 +0000 (19:15 -0500)
This patch add the check if parent is stable by unexpected rename.

Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <namjae.jeon@samsung.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifsd/smb2pdu.c
fs/cifsd/vfs.c

index cc4e8f1..3fbd8e4 100644 (file)
@@ -2844,12 +2844,10 @@ int smb2_open(struct ksmbd_work *work)
                 * is already granted.
                 */
                if (daccess & ~(FILE_READ_ATTRIBUTES_LE | FILE_READ_CONTROL_LE)) {
-                       if (ksmbd_vfs_inode_permission(path.dentry,
-                                                      open_flags & O_ACCMODE,
-                                                      may_delete)) {
-                               rc = -EACCES;
+                       rc = ksmbd_vfs_inode_permission(path.dentry,
+                                       open_flags & O_ACCMODE, may_delete);
+                       if (rc)
                                goto err_out;
-                       }
                }
        }
 
@@ -3260,7 +3258,7 @@ err_out1:
                        rsp->hdr.Status = STATUS_INVALID_PARAMETER;
                else if (rc == -EOPNOTSUPP)
                        rsp->hdr.Status = STATUS_NOT_SUPPORTED;
-               else if (rc == -EACCES)
+               else if (rc == -EACCES || rc == -ESTALE)
                        rsp->hdr.Status = STATUS_ACCESS_DENIED;
                else if (rc == -ENOENT)
                        rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
@@ -5938,7 +5936,7 @@ err_out:
                rsp->hdr.Status = STATUS_DIRECTORY_NOT_EMPTY;
        else if (rc == -EAGAIN)
                rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
-       else if (rc == -EBADF)
+       else if (rc == -EBADF || rc == -ESTALE)
                rsp->hdr.Status = STATUS_INVALID_HANDLE;
        else if (rc == -EEXIST)
                rsp->hdr.Status = STATUS_OBJECT_NAME_COLLISION;
index 5985d2d..f818aef 100644 (file)
@@ -70,7 +70,7 @@ static void ksmbd_vfs_inherit_owner(struct ksmbd_work *work,
 
 int ksmbd_vfs_inode_permission(struct dentry *dentry, int acc_mode, bool delete)
 {
-       int mask;
+       int mask, ret = 0;
 
        mask = 0;
        acc_mode &= O_ACCMODE;
@@ -86,24 +86,39 @@ int ksmbd_vfs_inode_permission(struct dentry *dentry, int acc_mode, bool delete)
                return -EACCES;
 
        if (delete) {
-               struct dentry *parent;
+               struct dentry *child, *parent;
 
                parent = dget_parent(dentry);
-               if (!parent)
-                       return -EINVAL;
+               inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
+               child = lookup_one_len(dentry->d_name.name, parent,
+                               dentry->d_name.len);
+               if (IS_ERR(child)) {
+                       ret = PTR_ERR(child);
+                       goto out_lock;
+               }
+
+               if (child != dentry) {
+                       ret = -ESTALE;
+                       dput(child);
+                       goto out_lock;
+               }
+               dput(child);
 
                if (inode_permission(&init_user_ns, d_inode(parent), MAY_EXEC | MAY_WRITE)) {
-                       dput(parent);
-                       return -EACCES;
+                       ret = -EACCES;
+                       goto out_lock;
                }
+out_lock:
+               inode_unlock(d_inode(parent));
                dput(parent);
        }
-       return 0;
+       return ret;
 }
 
 int ksmbd_vfs_query_maximal_access(struct dentry *dentry, __le32 *daccess)
 {
-       struct dentry *parent;
+       struct dentry *parent, *child;
+       int ret = 0;
 
        *daccess = cpu_to_le32(FILE_READ_ATTRIBUTES | READ_CONTROL);
 
@@ -120,13 +135,28 @@ int ksmbd_vfs_query_maximal_access(struct dentry *dentry, __le32 *daccess)
                *daccess |= FILE_EXECUTE_LE;
 
        parent = dget_parent(dentry);
-       if (!parent)
-               return 0;
+       inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
+       child = lookup_one_len(dentry->d_name.name, parent,
+                       dentry->d_name.len);
+       if (IS_ERR(child)) {
+               ret = PTR_ERR(child);
+               goto out_lock;
+       }
+
+       if (child != dentry) {
+               ret = -ESTALE;
+               dput(child);
+               goto out_lock;
+       }
+       dput(child);
 
        if (!inode_permission(&init_user_ns, d_inode(parent), MAY_EXEC | MAY_WRITE))
                *daccess |= FILE_DELETE_LE;
+
+out_lock:
+       inode_unlock(d_inode(parent));
        dput(parent);
-       return 0;
+       return ret;
 }
 
 /**
@@ -726,7 +756,7 @@ int ksmbd_vfs_fp_rename(struct ksmbd_work *work, struct ksmbd_file *fp,
 {
        struct path dst_path;
        struct dentry *src_dent_parent, *dst_dent_parent;
-       struct dentry *src_dent, *trap_dent;
+       struct dentry *src_dent, *trap_dent, *src_child;
        char *dst_name;
        int err;
 
@@ -735,11 +765,7 @@ int ksmbd_vfs_fp_rename(struct ksmbd_work *work, struct ksmbd_file *fp,
                return -EINVAL;
 
        src_dent_parent = dget_parent(fp->filp->f_path.dentry);
-       if (!src_dent_parent)
-               return -EINVAL;
-
        src_dent = fp->filp->f_path.dentry;
-       dget(src_dent);
 
        err = kern_path(newname, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &dst_path);
        if (err) {
@@ -747,20 +773,36 @@ int ksmbd_vfs_fp_rename(struct ksmbd_work *work, struct ksmbd_file *fp,
                goto out;
        }
        dst_dent_parent = dst_path.dentry;
-       dget(dst_dent_parent);
 
        trap_dent = lock_rename(src_dent_parent, dst_dent_parent);
+       dget(src_dent);
+       dget(dst_dent_parent);
+       src_child = lookup_one_len(src_dent->d_name.name, src_dent_parent,
+                       src_dent->d_name.len);
+       if (IS_ERR(src_child)) {
+               err = PTR_ERR(src_child);
+               goto out_lock;
+       }
+
+       if (src_child != src_dent) {
+               err = -ESTALE;
+               dput(src_child);
+               goto out_lock;
+       }
+       dput(src_child);
+
        err = __ksmbd_vfs_rename(work,
                                 src_dent_parent,
                                 src_dent,
                                 dst_dent_parent,
                                 trap_dent,
                                 dst_name);
-       unlock_rename(src_dent_parent, dst_dent_parent);
+out_lock:
+       dput(src_dent);
        dput(dst_dent_parent);
+       unlock_rename(src_dent_parent, dst_dent_parent);
        path_put(&dst_path);
 out:
-       dput(src_dent);
        dput(src_dent_parent);
        return err;
 }
@@ -1050,23 +1092,33 @@ int ksmbd_vfs_remove_xattr(struct dentry *dentry, char *attr_name)
 
 int ksmbd_vfs_unlink(struct dentry *dir, struct dentry *dentry)
 {
+       struct dentry *child;
        int err = 0;
 
-       dget(dentry);
        inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
-       if (!d_inode(dentry) || !d_inode(dentry)->i_nlink) {
-               err = -ENOENT;
+       dget(dentry);
+       child = lookup_one_len(dentry->d_name.name, dir,
+                       dentry->d_name.len);
+       if (IS_ERR(child)) {
+               err = PTR_ERR(child);
                goto out;
        }
 
+       if (child != dentry) {
+               err = -ESTALE;
+               dput(child);
+               goto out;
+       }
+       dput(child);
+
        if (S_ISDIR(d_inode(dentry)->i_mode))
                err = vfs_rmdir(&init_user_ns, d_inode(dir), dentry);
        else
                err = vfs_unlink(&init_user_ns, d_inode(dir), dentry, NULL);
 
 out:
-       inode_unlock(d_inode(dir));
        dput(dentry);
+       inode_unlock(d_inode(dir));
        if (err)
                ksmbd_debug(VFS, "failed to delete, err %d\n", err);