summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuis Henriques <lhenriques@suse.com>2019-10-25 09:05:24 -0400
committerIlya Dryomov <idryomov@gmail.com>2019-10-29 17:29:51 -0400
commitea60ed6fcf29eebc78f2ce91491e6309ee005a01 (patch)
tree8e2b944717571026475f062755f50b1a16b0a5e9
parentd6d5df1db6e9d7f8f76d2911707f7d5877251b02 (diff)
ceph: fix use-after-free in __ceph_remove_cap()
KASAN reports a use-after-free when running xfstest generic/531, with the following trace: [ 293.903362] kasan_report+0xe/0x20 [ 293.903365] rb_erase+0x1f/0x790 [ 293.903370] __ceph_remove_cap+0x201/0x370 [ 293.903375] __ceph_remove_caps+0x4b/0x70 [ 293.903380] ceph_evict_inode+0x4e/0x360 [ 293.903386] evict+0x169/0x290 [ 293.903390] __dentry_kill+0x16f/0x250 [ 293.903394] dput+0x1c6/0x440 [ 293.903398] __fput+0x184/0x330 [ 293.903404] task_work_run+0xb9/0xe0 [ 293.903410] exit_to_usermode_loop+0xd3/0xe0 [ 293.903413] do_syscall_64+0x1a0/0x1c0 [ 293.903417] entry_SYSCALL_64_after_hwframe+0x44/0xa9 This happens because __ceph_remove_cap() may queue a cap release (__ceph_queue_cap_release) which can be scheduled before that cap is removed from the inode list with rb_erase(&cap->ci_node, &ci->i_caps); And, when this finally happens, the use-after-free will occur. This can be fixed by removing the cap from the inode list before being removed from the session list, and thus eliminating the risk of an UAF. Cc: stable@vger.kernel.org Signed-off-by: Luis Henriques <lhenriques@suse.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
-rw-r--r--fs/ceph/caps.c10
1 files changed, 5 insertions, 5 deletions
diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c
index d3b9c9d5c1bd..f5a38910a82b 100644
--- a/fs/ceph/caps.c
+++ b/fs/ceph/caps.c
@@ -1058,6 +1058,11 @@ void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
1058 1058
1059 dout("__ceph_remove_cap %p from %p\n", cap, &ci->vfs_inode); 1059 dout("__ceph_remove_cap %p from %p\n", cap, &ci->vfs_inode);
1060 1060
1061 /* remove from inode's cap rbtree, and clear auth cap */
1062 rb_erase(&cap->ci_node, &ci->i_caps);
1063 if (ci->i_auth_cap == cap)
1064 ci->i_auth_cap = NULL;
1065
1061 /* remove from session list */ 1066 /* remove from session list */
1062 spin_lock(&session->s_cap_lock); 1067 spin_lock(&session->s_cap_lock);
1063 if (session->s_cap_iterator == cap) { 1068 if (session->s_cap_iterator == cap) {
@@ -1091,11 +1096,6 @@ void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
1091 1096
1092 spin_unlock(&session->s_cap_lock); 1097 spin_unlock(&session->s_cap_lock);
1093 1098
1094 /* remove from inode list */
1095 rb_erase(&cap->ci_node, &ci->i_caps);
1096 if (ci->i_auth_cap == cap)
1097 ci->i_auth_cap = NULL;
1098
1099 if (removed) 1099 if (removed)
1100 ceph_put_cap(mdsc, cap); 1100 ceph_put_cap(mdsc, cap);
1101 1101