ceph: fix buffer free while holding i_ceph_lock in __ceph_setxattr() [Linux 4.9.192]

This Linux kernel change "ceph: fix buffer free while holding i_ceph_lock in __ceph_setxattr()" is included in the Linux 4.9.192 release. This change is authored by Luis Henriques <lhenriques [at] suse.com> on Fri Jul 19 15:32:20 2019 +0100. The commit for this change in Linux stable tree is 9cec64d (patch) which is from upstream commit 86968ef. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream 86968ef.

ceph: fix buffer free while holding i_ceph_lock in __ceph_setxattr()

[ Upstream commit 86968ef21596515958d5f0a40233d02be78ecec0 ]

Calling ceph_buffer_put() in __ceph_setxattr() may end up freeing the
i_xattrs.prealloc_blob buffer while holding the i_ceph_lock.  This can be
fixed by postponing the call until later, when the lock is released.

The following backtrace was triggered by fstests generic/117.

  BUG: sleeping function called from invalid context at mm/vmalloc.c:2283
  in_atomic(): 1, irqs_disabled(): 0, pid: 650, name: fsstress
  3 locks held by fsstress/650:
   #0: 00000000870a0fe8 (sb_writers#8){.+.+}, at: mnt_want_write+0x20/0x50
   #1: 00000000ba0c4c74 (&type->i_mutex_dir_key#6){++++}, at: vfs_setxattr+0x55/0xa0
   #2: 000000008dfbb3f2 (&(&ci->i_ceph_lock)->rlock){+.+.}, at: __ceph_setxattr+0x297/0x810
  CPU: 1 PID: 650 Comm: fsstress Not tainted 5.2.0+ #437
  Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.12.1-0-ga5cab58-prebuilt.qemu.org 04/01/2014
  Call Trace:
   ? filename_lookup+0xc9/0x140
   ? rcu_read_lock_sched_held+0x74/0x80
   ? rcu_sync_lockdep_assert+0x2e/0x60
   ? __sb_start_write+0x142/0x1a0
   ? mnt_want_write+0x20/0x50
  RIP: 0033:0x7ff23514359a

Signed-off-by: Luis Henriques <lhenriques@suse.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>

There are 8 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 fs/ceph/xattr.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/fs/ceph/xattr.c b/fs/ceph/xattr.c
index 81144a8..18b999d 100644
--- a/fs/ceph/xattr.c
+++ b/fs/ceph/xattr.c
@@ -951,6 +951,7 @@ int __ceph_setxattr(struct inode *inode, const char *name,
    struct ceph_inode_info *ci = ceph_inode(inode);
    struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
    struct ceph_cap_flush *prealloc_cf = NULL;
+   struct ceph_buffer *old_blob = NULL;
    int issued;
    int err;
    int dirty = 0;
@@ -1019,13 +1020,15 @@ int __ceph_setxattr(struct inode *inode, const char *name,
        struct ceph_buffer *blob;

-       dout(" preaallocating new blob size=%d\n", required_blob_size);
+       ceph_buffer_put(old_blob); /* Shouldn't be required */
+       dout(" pre-allocating new blob size=%d\n", required_blob_size);
        blob = ceph_buffer_new(required_blob_size, GFP_NOFS);
        if (!blob)
            goto do_sync_unlocked;
+       /* prealloc_blob can't be released while holding i_ceph_lock */
        if (ci->i_xattrs.prealloc_blob)
-           ceph_buffer_put(ci->i_xattrs.prealloc_blob);
+           old_blob = ci->i_xattrs.prealloc_blob;
        ci->i_xattrs.prealloc_blob = blob;
        goto retry;
@@ -1041,6 +1044,7 @@ int __ceph_setxattr(struct inode *inode, const char *name,

+   ceph_buffer_put(old_blob);
    if (lock_snap_rwsem)
    if (dirty)

Leave a Reply

Your email address will not be published. Required fields are marked *