From: Namjae Jeon namjae.jeon@samsung.com
mainline inclusion from mainline-5.15-rc1 commit ff1d57272552e4d48e0aab015a457d0297915e0b category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/I60T7G CVE: NA
Reference: https://git.kernel.org/torvalds/linux/c/ff1d57272552
-------------------------------
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 Signed-off-by: Jason Yan yanaijie@huawei.com Signed-off-by: Zhong Jinghua zhongjinghua@huawei.com --- fs/cifsd/smb2pdu.c | 12 +++--- fs/cifsd/vfs.c | 98 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 80 insertions(+), 30 deletions(-)
diff --git a/fs/cifsd/smb2pdu.c b/fs/cifsd/smb2pdu.c index cc4e8f11c487..3fbd8e4925bb 100644 --- a/fs/cifsd/smb2pdu.c +++ b/fs/cifsd/smb2pdu.c @@ -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 @@ int smb2_open(struct ksmbd_work *work) 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 @@ int smb2_set_info(struct ksmbd_work *work) 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; diff --git a/fs/cifsd/vfs.c b/fs/cifsd/vfs.c index 5985d2d1f276..f818aeff244f 100644 --- a/fs/cifsd/vfs.c +++ b/fs/cifsd/vfs.c @@ -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);