| From c056dffcd0a1eb41aaf71444d06c5d741a86134b Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Wed, 1 Jul 2020 01:52:48 -0400 |
| Subject: ceph: fix potential mdsc use-after-free crash |
| |
| From: Xiubo Li <xiubli@redhat.com> |
| |
| [ Upstream commit fa9967734227b44acb1b6918033f9122dc7825b9 ] |
| |
| Make sure the delayed work stopped before releasing the resources. |
| |
| cancel_delayed_work_sync() will only guarantee that the work finishes |
| executing if the work is already in the ->worklist. That means after |
| the cancel_delayed_work_sync() returns, it will leave the work requeued |
| if it was rearmed at the end. That can lead to a use after free once the |
| work struct is freed. |
| |
| Fix it by flushing the delayed work instead of trying to cancel it, and |
| ensure that the work doesn't rearm if the mdsc is stopping. |
| |
| URL: https://tracker.ceph.com/issues/46293 |
| Signed-off-by: Xiubo Li <xiubli@redhat.com> |
| Reviewed-by: Jeff Layton <jlayton@kernel.org> |
| Signed-off-by: Ilya Dryomov <idryomov@gmail.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| fs/ceph/mds_client.c | 14 +++++++++++++- |
| 1 file changed, 13 insertions(+), 1 deletion(-) |
| |
| diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c |
| index 3139fbd4c34e3..4ec5a109df82b 100644 |
| --- a/fs/ceph/mds_client.c |
| +++ b/fs/ceph/mds_client.c |
| @@ -3386,6 +3386,9 @@ static void delayed_work(struct work_struct *work) |
| dout("mdsc delayed_work\n"); |
| ceph_check_delayed_caps(mdsc); |
| |
| + if (mdsc->stopping) |
| + return; |
| + |
| mutex_lock(&mdsc->mutex); |
| renew_interval = mdsc->mdsmap->m_session_timeout >> 2; |
| renew_caps = time_after_eq(jiffies, HZ*renew_interval + |
| @@ -3717,7 +3720,16 @@ void ceph_mdsc_force_umount(struct ceph_mds_client *mdsc) |
| static void ceph_mdsc_stop(struct ceph_mds_client *mdsc) |
| { |
| dout("stop\n"); |
| - cancel_delayed_work_sync(&mdsc->delayed_work); /* cancel timer */ |
| + /* |
| + * Make sure the delayed work stopped before releasing |
| + * the resources. |
| + * |
| + * Because the cancel_delayed_work_sync() will only |
| + * guarantee that the work finishes executing. But the |
| + * delayed work will re-arm itself again after that. |
| + */ |
| + flush_delayed_work(&mdsc->delayed_work); |
| + |
| if (mdsc->mdsmap) |
| ceph_mdsmap_destroy(mdsc->mdsmap); |
| kfree(mdsc->sessions); |
| -- |
| 2.25.1 |
| |