diff options
author | Xiubo Li <xiubli@redhat.com> | 2023-06-01 08:59:31 +0800 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2023-06-14 10:35:26 +0200 |
commit | b221b7189f3a5f3ae40da3ba597a1ce5d55ca604 (patch) | |
tree | fc463bcc85f895cb7b8a0142ecd30a781f87fac1 | |
parent | 35a9464e3402c26bfb847f127a78d4c38da95249 (diff) | |
download | linux-stable-b221b7189f3a5f3ae40da3ba597a1ce5d55ca604.tar.gz linux-stable-b221b7189f3a5f3ae40da3ba597a1ce5d55ca604.tar.bz2 linux-stable-b221b7189f3a5f3ae40da3ba597a1ce5d55ca604.zip |
ceph: fix use-after-free bug for inodes when flushing capsnaps
commit 409e873ea3c1fd3079909718bbeb06ac1ec7f38b upstream.
There is a race between capsnaps flush and removing the inode from
'mdsc->snap_flush_list' list:
== Thread A == == Thread B ==
ceph_queue_cap_snap()
-> allocate 'capsnapA'
->ihold('&ci->vfs_inode')
->add 'capsnapA' to 'ci->i_cap_snaps'
->add 'ci' to 'mdsc->snap_flush_list'
...
== Thread C ==
ceph_flush_snaps()
->__ceph_flush_snaps()
->__send_flush_snap()
handle_cap_flushsnap_ack()
->iput('&ci->vfs_inode')
this also will release 'ci'
...
== Thread D ==
ceph_handle_snap()
->flush_snaps()
->iterate 'mdsc->snap_flush_list'
->get the stale 'ci'
->remove 'ci' from ->ihold(&ci->vfs_inode) this
'mdsc->snap_flush_list' will WARNING
To fix this we will increase the inode's i_count ref when adding 'ci'
to the 'mdsc->snap_flush_list' list.
[ idryomov: need_put int -> bool ]
Cc: stable@vger.kernel.org
Link: https://bugzilla.redhat.com/show_bug.cgi?id=2209299
Signed-off-by: Xiubo Li <xiubli@redhat.com>
Reviewed-by: Milind Changire <mchangir@redhat.com>
Reviewed-by: Ilya Dryomov <idryomov@gmail.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | fs/ceph/caps.c | 6 | ||||
-rw-r--r-- | fs/ceph/snap.c | 4 |
2 files changed, 9 insertions, 1 deletions
diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index a0168d9bb85d..e36786f574b8 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -1437,6 +1437,7 @@ void ceph_flush_snaps(struct ceph_inode_info *ci, struct inode *inode = &ci->vfs_inode; struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc; struct ceph_mds_session *session = NULL; + bool need_put = false; int mds; dout("ceph_flush_snaps %p\n", inode); @@ -1490,8 +1491,13 @@ out: } /* we flushed them all; remove this inode from the queue */ spin_lock(&mdsc->snap_flush_lock); + if (!list_empty(&ci->i_snap_flush_item)) + need_put = true; list_del_init(&ci->i_snap_flush_item); spin_unlock(&mdsc->snap_flush_lock); + + if (need_put) + iput(inode); } /* diff --git a/fs/ceph/snap.c b/fs/ceph/snap.c index e12fa3a723db..ec917ad13595 100644 --- a/fs/ceph/snap.c +++ b/fs/ceph/snap.c @@ -623,8 +623,10 @@ int __ceph_finish_cap_snap(struct ceph_inode_info *ci, capsnap->size); spin_lock(&mdsc->snap_flush_lock); - if (list_empty(&ci->i_snap_flush_item)) + if (list_empty(&ci->i_snap_flush_item)) { + ihold(inode); list_add_tail(&ci->i_snap_flush_item, &mdsc->snap_flush_list); + } spin_unlock(&mdsc->snap_flush_lock); return 1; /* caller may want to ceph_flush_snaps */ } |