ceph: fix recursion between ceph_set_acl() and __ceph_setattr()
authorYan, Zheng <zyan@redhat.com>
Wed, 19 Apr 2017 02:01:48 +0000 (10:01 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 25 May 2017 12:30:13 +0000 (14:30 +0200)
commit 8179a101eb5f4ef0ac9a915fcea9a9d3109efa90 upstream.

ceph_set_acl() calls __ceph_setattr() if the setacl operation needs
to modify inode's i_mode. __ceph_setattr() updates inode's i_mode,
then calls posix_acl_chmod().

The problem is that __ceph_setattr() calls posix_acl_chmod() before
sending the setattr request. The get_acl() call in posix_acl_chmod()
can trigger a getxattr request. The reply of the getxattr request
can restore inode's i_mode to its old value. The set_acl() call in
posix_acl_chmod() sees old value of inode's i_mode, so it calls
__ceph_setattr() again.

Cc: stable@vger.kernel.org # needs backporting for < 4.9
Link: http://tracker.ceph.com/issues/19688
Reported-by: Jerry Lee <leisurelysw24@gmail.com>
Signed-off-by: "Yan, Zheng" <zyan@redhat.com>
Reviewed-by: Jeff Layton <jlayton@redhat.com>
Tested-by: Luis Henriques <lhenriques@suse.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
[luis: introduce __ceph_setattr() and make ceph_set_acl() call it, as
 suggested by Yan.]
Signed-off-by: Luis Henriques <lhenriques@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: “Yan, Zheng” <zyan@redhat.com>
fs/ceph/acl.c
fs/ceph/inode.c
fs/ceph/super.h

index 4d8caeb94a118d339031b79a55c46ba59a537a05..bdb9c94335f16281b08501dddcad7a6c2945227a 100644 (file)
@@ -128,7 +128,7 @@ int ceph_set_acl(struct inode *inode, struct posix_acl *acl, int type)
        if (new_mode != old_mode) {
                newattrs.ia_mode = new_mode;
                newattrs.ia_valid = ATTR_MODE;
-               ret = ceph_setattr(dentry, &newattrs);
+               ret = __ceph_setattr(dentry, &newattrs);
                if (ret)
                        goto out_dput;
        }
@@ -138,7 +138,7 @@ int ceph_set_acl(struct inode *inode, struct posix_acl *acl, int type)
                if (new_mode != old_mode) {
                        newattrs.ia_mode = old_mode;
                        newattrs.ia_valid = ATTR_MODE;
-                       ceph_setattr(dentry, &newattrs);
+                       __ceph_setattr(dentry, &newattrs);
                }
                goto out_dput;
        }
index d98536c8abfc03adf01c744c33fe014cbda9fabc..9f0d99094cc10b46e5008e4f7938204ad815ece8 100644 (file)
@@ -1773,7 +1773,7 @@ static const struct inode_operations ceph_symlink_iops = {
 /*
  * setattr
  */
-int ceph_setattr(struct dentry *dentry, struct iattr *attr)
+int __ceph_setattr(struct dentry *dentry, struct iattr *attr)
 {
        struct inode *inode = d_inode(dentry);
        struct ceph_inode_info *ci = ceph_inode(inode);
@@ -1975,11 +1975,6 @@ int ceph_setattr(struct dentry *dentry, struct iattr *attr)
        if (inode_dirty_flags)
                __mark_inode_dirty(inode, inode_dirty_flags);
 
-       if (ia_valid & ATTR_MODE) {
-               err = posix_acl_chmod(inode, attr->ia_mode);
-               if (err)
-                       goto out_put;
-       }
 
        if (mask) {
                req->r_inode = inode;
@@ -1993,13 +1988,23 @@ int ceph_setattr(struct dentry *dentry, struct iattr *attr)
             ceph_cap_string(dirtied), mask);
 
        ceph_mdsc_put_request(req);
-       if (mask & CEPH_SETATTR_SIZE)
-               __ceph_do_pending_vmtruncate(inode);
        ceph_free_cap_flush(prealloc_cf);
+
+       if (err >= 0 && (mask & CEPH_SETATTR_SIZE))
+               __ceph_do_pending_vmtruncate(inode);
+
        return err;
-out_put:
-       ceph_mdsc_put_request(req);
-       ceph_free_cap_flush(prealloc_cf);
+}
+
+int ceph_setattr(struct dentry *dentry, struct iattr *attr)
+{
+       int err;
+
+       err = __ceph_setattr(dentry, attr);
+
+       if (err >= 0 && (attr->ia_valid & ATTR_MODE))
+               err = posix_acl_chmod(d_inode(dentry), attr->ia_mode);
+
        return err;
 }
 
index 75b7d125ce668a9b3f4f851d2be1c688d6d5f964..8c8cb8fe3d32f90e6ff61dd2b980e5405d544c88 100644 (file)
@@ -788,6 +788,7 @@ static inline int ceph_do_getattr(struct inode *inode, int mask, bool force)
        return __ceph_do_getattr(inode, NULL, mask, force);
 }
 extern int ceph_permission(struct inode *inode, int mask);
+extern int __ceph_setattr(struct dentry *dentry, struct iattr *attr);
 extern int ceph_setattr(struct dentry *dentry, struct iattr *attr);
 extern int ceph_getattr(struct vfsmount *mnt, struct dentry *dentry,
                        struct kstat *stat);