From: Xiubo Li xiubli@redhat.com
stable inclusion from stable-v6.1.23 commit 66ec619e4591f8350f99c5269a7ce160cccc7a7c category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/I9R4KH CVE: CVE-2023-52732
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
--------------------------------
[ Upstream commit a68e564adcaa69b0930809fb64d9d5f7d9c32ba9 ]
When received corrupted snap trace we don't know what exactly has happened in MDS side. And we shouldn't continue IOs and metadatas access to MDS, which may corrupt or get incorrect contents.
This patch will just block all the further IO/MDS requests immediately and then evict the kclient itself.
The reason why we still need to evict the kclient just after blocking all the further IOs is that the MDS could revoke the caps faster.
Link: https://tracker.ceph.com/issues/57686 Signed-off-by: Xiubo Li xiubli@redhat.com Reviewed-by: Venky Shankar vshankar@redhat.com Signed-off-by: Ilya Dryomov idryomov@gmail.com Signed-off-by: Sasha Levin sashal@kernel.org
Conflicts: fs/ceph/addr.c fs/ceph/caps.c fs/ceph/cache.c fs/ceph/mds_client.c fs/ceph/snap.c fs/ceph/super.c fs/ceph/super.h include/linux/ceph/libceph.h [Due to the large number of conflicts, a large number of adaptation patches need to be integrated, so the context adaptation is directly performed] Signed-off-by: Zizhi Wo wozizhi@huawei.com --- fs/ceph/addr.c | 8 ++++++++ fs/ceph/cache.c | 6 ++++++ fs/ceph/caps.c | 16 +++++++++++++--- fs/ceph/file.c | 3 +++ fs/ceph/mds_client.c | 32 ++++++++++++++++++++++++++++---- fs/ceph/snap.c | 36 ++++++++++++++++++++++++++++++++++-- include/linux/ceph/libceph.h | 1 + 7 files changed, 93 insertions(+), 9 deletions(-)
diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 65ab91faeb83..0d66e3fe7afc 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -603,6 +603,9 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc)
dout("writepage %p idx %lu\n", page, page->index);
+ if (ceph_inode_is_shutdown(inode)) + return -EIO; + /* verify this is a writeable snap context */ snapc = page_snap_context(page); if (!snapc) { @@ -1760,6 +1763,11 @@ int ceph_uninline_data(struct file *filp, struct page *locked_page) dout("uninline_data %p %llx.%llx inline_version %llu\n", inode, ceph_vinop(inode), inline_version);
+ if (ceph_inode_is_shutdown(inode)) { + err = -EIO; + goto out; + } + if (inline_version == 1 || /* initial version, no data */ inline_version == CEPH_INLINE_NONE) goto out; diff --git a/fs/ceph/cache.c b/fs/ceph/cache.c index 2f5cb6bc78e1..4cc6877e5930 100644 --- a/fs/ceph/cache.c +++ b/fs/ceph/cache.c @@ -232,6 +232,9 @@ int ceph_readpage_from_fscache(struct inode *inode, struct page *page) if (!cache_valid(ci)) return -ENOBUFS;
+ if (ceph_inode_is_shutdown(inode)) + return -EIO; + ret = fscache_read_or_alloc_page(ci->fscache, page, ceph_readpage_from_fscache_complete, NULL, GFP_KERNEL); @@ -290,6 +293,9 @@ void ceph_readpage_to_fscache(struct inode *inode, struct page *page) if (!cache_valid(ci)) return;
+ if (ceph_inode_is_shutdown(inode)) + return; + ret = fscache_write_page(ci->fscache, page, i_size_read(inode), GFP_KERNEL); if (ret) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index a8761e6acabc..8c1dd9e80e7e 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -4109,6 +4109,7 @@ void ceph_handle_caps(struct ceph_mds_session *session, void *p, *end; struct cap_extra_info extra_info = {}; bool queue_trunc; + bool close_sessions = false;
dout("handle_caps from mds%d\n", session->s_mds);
@@ -4249,9 +4250,13 @@ void ceph_handle_caps(struct ceph_mds_session *session, realm = NULL; if (snaptrace_len) { down_write(&mdsc->snap_rwsem); - ceph_update_snap_trace(mdsc, snaptrace, - snaptrace + snaptrace_len, - false, &realm); + if (ceph_update_snap_trace(mdsc, snaptrace, + snaptrace + snaptrace_len, + false, &realm)) { + up_write(&mdsc->snap_rwsem); + close_sessions = true; + goto done; + } downgrade_write(&mdsc->snap_rwsem); } else { down_read(&mdsc->snap_rwsem); @@ -4311,6 +4316,11 @@ void ceph_handle_caps(struct ceph_mds_session *session, ceph_put_string(extra_info.pool_ns); /* avoid calling iput_final() in mds dispatch threads */ ceph_async_iput(inode); + + /* Defer closing the sessions after s_mutex lock being released */ + if (close_sessions) + ceph_mdsc_close_sessions(mdsc); + return;
flush_cap_releases: diff --git a/fs/ceph/file.c b/fs/ceph/file.c index 180eb466e597..215bd9a3c535 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -2004,6 +2004,9 @@ static int ceph_zero_partial_object(struct inode *inode, loff_t zero = 0; int op;
+ if (ceph_inode_is_shutdown(inode)) + return -EIO; + if (!length) { op = offset ? CEPH_OSD_OP_DELETE : CEPH_OSD_OP_TRUNCATE; length = &zero; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index eb17aa44673f..aea1b42b9b18 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -712,6 +712,9 @@ static struct ceph_mds_session *register_session(struct ceph_mds_client *mdsc, { struct ceph_mds_session *s;
+ if (READ_ONCE(mdsc->fsc->mount_state) == CEPH_MOUNT_FENCE_IO) + return ERR_PTR(-EIO); + if (mds >= mdsc->mdsmap->possible_max_rank) return ERR_PTR(-EINVAL);
@@ -1397,6 +1400,9 @@ static int __open_session(struct ceph_mds_client *mdsc, int mstate; int mds = session->s_mds;
+ if (READ_ONCE(mdsc->fsc->mount_state) == CEPH_MOUNT_FENCE_IO) + return -EIO; + /* wait for mds to go active? */ mstate = ceph_mdsmap_get_state(mdsc->mdsmap, mds); dout("open_session to mds%d (%s)\n", mds, @@ -2717,6 +2723,11 @@ static void __do_request(struct ceph_mds_client *mdsc, return; }
+ if (READ_ONCE(mdsc->fsc->mount_state) == CEPH_MOUNT_FENCE_IO) { + dout("do_request metadata corrupted\n"); + err = -EIO; + goto finish; + } if (req->r_timeout && time_after_eq(jiffies, req->r_started + req->r_timeout)) { dout("do_request timed out\n"); @@ -3024,6 +3035,7 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg) u64 tid; int err, result; int mds = session->s_mds; + bool close_sessions = false;
if (msg->front.iov_len < sizeof(*head)) { pr_err("mdsc_handle_reply got corrupt (short) reply\n"); @@ -3142,10 +3154,17 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg) realm = NULL; if (rinfo->snapblob_len) { down_write(&mdsc->snap_rwsem); - ceph_update_snap_trace(mdsc, rinfo->snapblob, + err = ceph_update_snap_trace(mdsc, rinfo->snapblob, rinfo->snapblob + rinfo->snapblob_len, le32_to_cpu(head->op) == CEPH_MDS_OP_RMSNAP, &realm); + if (err) { + up_write(&mdsc->snap_rwsem); + close_sessions = true; + if (err == -EIO) + ceph_msg_dump(msg); + goto out_err; + } downgrade_write(&mdsc->snap_rwsem); } else { down_read(&mdsc->snap_rwsem); @@ -3203,6 +3222,10 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg) req->r_end_latency, err); out: ceph_mdsc_put_request(req); + + /* Defer closing the sessions after s_mutex lock being released */ + if (close_sessions) + ceph_mdsc_close_sessions(mdsc); return; }
@@ -4661,7 +4684,7 @@ void ceph_mdsc_sync(struct ceph_mds_client *mdsc) { u64 want_tid, want_flush;
- if (READ_ONCE(mdsc->fsc->mount_state) == CEPH_MOUNT_SHUTDOWN) + if (READ_ONCE(mdsc->fsc->mount_state) >= CEPH_MOUNT_SHUTDOWN) return;
dout("sync\n"); @@ -4698,7 +4721,7 @@ static bool done_closing_sessions(struct ceph_mds_client *mdsc, int skipped) }
/* - * called after sb is ro. + * called after sb is ro or when metadata corrupted. */ void ceph_mdsc_close_sessions(struct ceph_mds_client *mdsc) { @@ -4995,7 +5018,8 @@ static void peer_reset(struct ceph_connection *con) struct ceph_mds_client *mdsc = s->s_mdsc;
pr_warn("mds%d closed our session\n", s->s_mds); - send_mds_reconnect(mdsc, s); + if (READ_ONCE(mdsc->fsc->mount_state) != CEPH_MOUNT_FENCE_IO) + send_mds_reconnect(mdsc, s); }
static void dispatch(struct ceph_connection *con, struct ceph_msg *msg) diff --git a/fs/ceph/snap.c b/fs/ceph/snap.c index db464682b2cb..89cacdd71b63 100644 --- a/fs/ceph/snap.c +++ b/fs/ceph/snap.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/ceph/ceph_debug.h>
+#include <linux/fs.h> #include <linux/sort.h> #include <linux/slab.h> #include <linux/iversion.h> @@ -702,8 +703,10 @@ int ceph_update_snap_trace(struct ceph_mds_client *mdsc, struct ceph_snap_realm *realm; struct ceph_snap_realm *first_realm = NULL; struct ceph_snap_realm *realm_to_rebuild = NULL; + struct ceph_client *client = mdsc->fsc->client; int rebuild_snapcs; int err = -ENOMEM; + int ret; LIST_HEAD(dirty_realms);
lockdep_assert_held_write(&mdsc->snap_rwsem); @@ -820,6 +823,27 @@ int ceph_update_snap_trace(struct ceph_mds_client *mdsc, if (first_realm) ceph_put_snap_realm(mdsc, first_realm); pr_err("update_snap_trace error %d\n", err); + + /* + * When receiving a corrupted snap trace we don't know what + * exactly has happened in MDS side. And we shouldn't continue + * writing to OSD, which may corrupt the snapshot contents. + * + * Just try to blocklist this kclient and then this kclient + * must be remounted to continue after the corrupted metadata + * fixed in the MDS side. + */ + WRITE_ONCE(mdsc->fsc->mount_state, CEPH_MOUNT_FENCE_IO); + ret = ceph_monc_blocklist_add(&client->monc, &client->msgr.inst.addr); + if (ret) + pr_err("%s failed to blocklist %s: %d\n", __func__, + ceph_pr_addr(&client->msgr.inst.addr), ret); + + WARN(1, "%s: %s%sdo remount to continue%s", + __func__, ret ? "" : ceph_pr_addr(&client->msgr.inst.addr), + ret ? "" : " was blocklisted, ", + err == -EIO ? " after corrupted snaptrace is fixed" : ""); + return err; }
@@ -888,6 +912,7 @@ void ceph_handle_snap(struct ceph_mds_client *mdsc, __le64 *split_inos = NULL, *split_realms = NULL; int i; int locked_rwsem = 0; + bool close_sessions = false;
/* decode */ if (msg->front.iov_len < sizeof(*h)) @@ -1029,8 +1054,12 @@ void ceph_handle_snap(struct ceph_mds_client *mdsc, * update using the provided snap trace. if we are deleting a * snap, we can avoid queueing cap_snaps. */ - ceph_update_snap_trace(mdsc, p, e, - op == CEPH_SNAP_OP_DESTROY, NULL); + if (ceph_update_snap_trace(mdsc, p, e, + op == CEPH_SNAP_OP_DESTROY, + NULL)) { + close_sessions = true; + goto bad; + }
if (op == CEPH_SNAP_OP_SPLIT) /* we took a reference when we created the realm, above */ @@ -1049,6 +1078,9 @@ void ceph_handle_snap(struct ceph_mds_client *mdsc, out: if (locked_rwsem) up_write(&mdsc->snap_rwsem); + + if (close_sessions) + ceph_mdsc_close_sessions(mdsc); return; }
diff --git a/include/linux/ceph/libceph.h b/include/linux/ceph/libceph.h index c8645f0b797d..15ea07f9ccfb 100644 --- a/include/linux/ceph/libceph.h +++ b/include/linux/ceph/libceph.h @@ -104,6 +104,7 @@ enum { CEPH_MOUNT_UNMOUNTING, CEPH_MOUNT_UNMOUNTED, CEPH_MOUNT_SHUTDOWN, + CEPH_MOUNT_FENCE_IO, };
static inline unsigned long ceph_timeout_jiffies(unsigned long timeout)