[PATCH 1/1] libceph: use RCU to protect monmap in ceph_compare_options()

Ren Wei <[email protected]> Wed, 8 Jul 2026 11:03:41 +0800
Newsgroups org.kernel.vger.ceph-devel
Message-ID <f9b8b94904378d14111606a3cfee801c60492681.1783423648.git.edragain@163.com>
From: Yong Wang <[email protected]>

ceph_compare_options() checks whether a new mount shares any monitor
address with an existing client by walking client->monc.monmap via
ceph_monmap_contains().  That comparison can run under sb_lock or
rbd_client_list_lock, so it cannot take monc->mutex.

Meanwhile, monmap update handling replaces monc->monmap under
monc->mutex and frees the old map immediately.  A concurrent shared-
mount comparison can therefore dereference a freed monmap and walk
stale mon_inst[] entries, triggering a use-after-free.

Protect the compare path with RCU and publish/free monitor maps with
rcu_assign_pointer() and kfree_rcu().  Annotate monc->monmap as an
RCU pointer and use rcu_dereference_protected() in mutex-protected
paths to keep the accesses consistent with the new pointer contract.

This keeps the existing non-blocking comparison semantics while
ensuring that replaced monmaps remain alive until readers are done.

Fixes: 4e7a5dcd1bbab ("ceph: negotiate authentication protocol; implement AUTH_NONE protocol")
Cc: [email protected]
Cc: [email protected]
Reported-by: Yuan Tan <[email protected]>
Reported-by: Xin Liu <[email protected]>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Yong Wang <[email protected]>
Reviewed-by: Ren Wei <[email protected]>
---
 fs/ceph/super.c                 |  7 +++--
 include/linux/ceph/mon_client.h |  4 ++-
 net/ceph/ceph_common.c          | 21 ++++++++++----
 net/ceph/debugfs.c              | 11 ++++---
 net/ceph/mon_client.c           | 51 ++++++++++++++++++++++-----------
 5 files changed, 65 insertions(+), 29 deletions(-)

diff --git a/fs/ceph/super.c b/fs/ceph/super.c
index c05fbd4237f8..4145594ba710 100644
--- a/fs/ceph/super.c
+++ b/fs/ceph/super.c
@@ -60,6 +60,7 @@ static int ceph_statfs(struct dentry *dentry, struct kstatfs *buf)
 {
 	struct ceph_fs_client *fsc = ceph_inode_to_fs_client(d_inode(dentry));
 	struct ceph_mon_client *monc = &fsc->client->monc;
+	struct ceph_monmap *monmap;
 	struct ceph_statfs st;
 	int i, err;
 	u64 data_pool;
@@ -111,8 +112,10 @@ static int ceph_statfs(struct dentry *dentry, struct kstatfs *buf)
 	/* Must convert the fsid, for consistent values across arches */
 	buf->f_fsid.val[0] = 0;
 	mutex_lock(&monc->mutex);
-	for (i = 0 ; i < sizeof(monc->monmap->fsid) / sizeof(__le32) ; ++i)
-		buf->f_fsid.val[0] ^= le32_to_cpu(((__le32 *)&monc->monmap->fsid)[i]);
+	monmap = rcu_dereference_protected(monc->monmap,
+					   lockdep_is_held(&monc->mutex));
+	for (i = 0 ; i < sizeof(monmap->fsid) / sizeof(__le32) ; ++i)
+		buf->f_fsid.val[0] ^= le32_to_cpu(((__le32 *)&monmap->fsid)[i]);
 	mutex_unlock(&monc->mutex);
 
 	/* fold the fs_cluster_id into the upper bits */
diff --git a/include/linux/ceph/mon_client.h b/include/linux/ceph/mon_client.h
index 7a9a40163c0f..98a745f6d9ab 100644
--- a/include/linux/ceph/mon_client.h
+++ b/include/linux/ceph/mon_client.h
@@ -4,6 +4,7 @@
 
 #include <linux/completion.h>
 #include <linux/kref.h>
+#include <linux/rcupdate.h>
 #include <linux/rbtree.h>
 
 #include <linux/ceph/messenger.h>
@@ -19,6 +20,7 @@ struct ceph_monmap {
 	struct ceph_fsid fsid;
 	u32 epoch;
 	u32 num_mon;
+	struct rcu_head rcu;
 	struct ceph_entity_inst mon_inst[] __counted_by(num_mon);
 };
 
@@ -69,7 +71,7 @@ struct ceph_mon_generic_request {
 
 struct ceph_mon_client {
 	struct ceph_client *client;
-	struct ceph_monmap *monmap;
+	struct ceph_monmap __rcu *monmap;
 
 	struct mutex mutex;
 	struct delayed_work delayed_work;
diff --git a/net/ceph/ceph_common.c b/net/ceph/ceph_common.c
index 952121849180..929df54cf3bb 100644
--- a/net/ceph/ceph_common.c
+++ b/net/ceph/ceph_common.c
@@ -133,6 +133,7 @@ int ceph_compare_options(struct ceph_options *new_opt,
 {
 	struct ceph_options *opt1 = new_opt;
 	struct ceph_options *opt2 = client->options;
+	struct ceph_monmap *monmap;
 	int ofs = offsetof(struct ceph_options, mon_addr);
 	int i;
 	int ret;
@@ -180,13 +181,20 @@ int ceph_compare_options(struct ceph_options *new_opt,
 	if (ret)
 		return ret;
 
+	rcu_read_lock();
+	monmap = rcu_dereference(client->monc.monmap);
+	ret = -1;
+
 	/* any matching mon ip implies a match */
 	for (i = 0; i < opt1->num_mon; i++) {
-		if (ceph_monmap_contains(client->monc.monmap,
-				 &opt1->mon_addr[i]))
-			return 0;
+		if (ceph_monmap_contains(monmap, &opt1->mon_addr[i])) {
+			ret = 0;
+			break;
+		}
 	}
-	return -1;
+
+	rcu_read_unlock();
+	return ret;
 }
 EXPORT_SYMBOL(ceph_compare_options);
 
@@ -791,6 +799,7 @@ int __ceph_open_session(struct ceph_client *client)
 {
 	DEFINE_WAIT_FUNC(wait, woken_wake_function);
 	long timeout = ceph_timeout_jiffies(client->options->mount_timeout);
+	struct ceph_monmap *monmap;
 	bool have_monmap, have_osdmap;
 	int err;
 
@@ -803,7 +812,9 @@ int __ceph_open_session(struct ceph_client *client)
 	for (;;) {
 		mutex_lock(&client->monc.mutex);
 		err = client->auth_err;
-		have_monmap = client->monc.monmap && client->monc.monmap->epoch;
+		monmap = rcu_dereference_protected(client->monc.monmap,
+						   lockdep_is_held(&client->monc.mutex));
+		have_monmap = monmap && monmap->epoch;
 		mutex_unlock(&client->monc.mutex);
 
 		down_read(&client->osdc.lock);
diff --git a/net/ceph/debugfs.c b/net/ceph/debugfs.c
index 83c270bce63c..f0aa4631cf6e 100644
--- a/net/ceph/debugfs.c
+++ b/net/ceph/debugfs.c
@@ -35,15 +35,18 @@ static int monmap_show(struct seq_file *s, void *p)
 {
 	int i;
 	struct ceph_client *client = s->private;
+	struct ceph_monmap *monmap;
 
 	mutex_lock(&client->monc.mutex);
-	if (client->monc.monmap == NULL)
+	monmap = rcu_dereference_protected(client->monc.monmap,
+					   lockdep_is_held(&client->monc.mutex));
+	if (!monmap)
 		goto out_unlock;
 
-	seq_printf(s, "epoch %d\n", client->monc.monmap->epoch);
-	for (i = 0; i < client->monc.monmap->num_mon; i++) {
+	seq_printf(s, "epoch %d\n", monmap->epoch);
+	for (i = 0; i < monmap->num_mon; i++) {
 		struct ceph_entity_inst *inst =
-			&client->monc.monmap->mon_inst[i];
+			&monmap->mon_inst[i];
 
 		seq_printf(s, "\t%s%lld\t%s\n",
 			   ENTITY_NAME(inst->name),
diff --git a/net/ceph/mon_client.c b/net/ceph/mon_client.c
index d2cdc8ee3155..d7e600a74ca5 100644
--- a/net/ceph/mon_client.c
+++ b/net/ceph/mon_client.c
@@ -206,19 +206,22 @@ static void __close_session(struct ceph_mon_client *monc)
  */
 static void pick_new_mon(struct ceph_mon_client *monc)
 {
+	struct ceph_monmap *monmap =
+		rcu_dereference_protected(monc->monmap,
+					  lockdep_is_held(&monc->mutex));
 	int old_mon = monc->cur_mon;
 
-	BUG_ON(monc->monmap->num_mon < 1);
+	BUG_ON(monmap->num_mon < 1);
 
-	if (monc->monmap->num_mon == 1) {
+	if (monmap->num_mon == 1) {
 		monc->cur_mon = 0;
 	} else {
-		int max = monc->monmap->num_mon;
+		int max = monmap->num_mon;
 		int o = -1;
 		int n;
 
 		if (monc->cur_mon >= 0) {
-			if (monc->cur_mon < monc->monmap->num_mon)
+			if (monc->cur_mon < monmap->num_mon)
 				o = monc->cur_mon;
 			if (o >= 0)
 				max--;
@@ -232,7 +235,7 @@ static void pick_new_mon(struct ceph_mon_client *monc)
 	}
 
 	dout("%s mon%d -> mon%d out of %d mons\n", __func__, old_mon,
-	     monc->cur_mon, monc->monmap->num_mon);
+	     monc->cur_mon, monmap->num_mon);
 }
 
 /*
@@ -240,6 +243,9 @@ static void pick_new_mon(struct ceph_mon_client *monc)
  */
 static void __open_session(struct ceph_mon_client *monc)
 {
+	struct ceph_monmap *monmap =
+		rcu_dereference_protected(monc->monmap,
+					  lockdep_is_held(&monc->mutex));
 	int ret;
 
 	pick_new_mon(monc);
@@ -256,7 +262,7 @@ static void __open_session(struct ceph_mon_client *monc)
 
 	dout("%s opening mon%d\n", __func__, monc->cur_mon);
 	ceph_con_open(&monc->con, CEPH_ENTITY_TYPE_MON, monc->cur_mon,
-		      &monc->monmap->mon_inst[monc->cur_mon].addr);
+		      &monmap->mon_inst[monc->cur_mon].addr);
 
 	/*
 	 * Queue a keepalive to ensure that in case of an early fault
@@ -542,6 +548,7 @@ static void ceph_monc_handle_map(struct ceph_mon_client *monc,
 				 struct ceph_msg *msg)
 {
 	struct ceph_client *client = monc->client;
+	struct ceph_monmap *old_monmap;
 	struct ceph_monmap *monmap;
 	void *p, *end;
 
@@ -564,10 +571,12 @@ static void ceph_monc_handle_map(struct ceph_mon_client *monc,
 		goto out;
 	}
 
-	kfree(monc->monmap);
-	monc->monmap = monmap;
+	old_monmap = rcu_dereference_protected(monc->monmap,
+					       lockdep_is_held(&monc->mutex));
+	rcu_assign_pointer(monc->monmap, monmap);
+	kfree_rcu(old_monmap, rcu);
 
-	__ceph_monc_got_map(monc, CEPH_SUB_MONMAP, monc->monmap->epoch);
+	__ceph_monc_got_map(monc, CEPH_SUB_MONMAP, monmap->epoch);
 	client->have_fsid = true;
 
 out:
@@ -775,6 +784,7 @@ static void handle_statfs_reply(struct ceph_mon_client *monc,
 int ceph_monc_do_statfs(struct ceph_mon_client *monc, u64 data_pool,
 			struct ceph_statfs *buf)
 {
+	struct ceph_monmap *monmap;
 	struct ceph_mon_generic_request *req;
 	struct ceph_mon_statfs *h;
 	int ret = -ENOMEM;
@@ -802,7 +812,9 @@ int ceph_monc_do_statfs(struct ceph_mon_client *monc, u64 data_pool,
 	h->monhdr.have_version = 0;
 	h->monhdr.session_mon = cpu_to_le16(-1);
 	h->monhdr.session_mon_tid = 0;
-	h->fsid = monc->monmap->fsid;
+	monmap = rcu_dereference_protected(monc->monmap,
+					   lockdep_is_held(&monc->mutex));
+	h->fsid = monmap->fsid;
 	h->contains_data_pool = (data_pool != CEPH_NOPOOL);
 	h->data_pool = cpu_to_le64(data_pool);
 	send_generic_request(monc, req);
@@ -975,6 +987,7 @@ static __printf(2, 0)
 int do_mon_command_vargs(struct ceph_mon_client *monc, const char *fmt,
 			 va_list ap)
 {
+	struct ceph_monmap *monmap;
 	struct ceph_mon_generic_request *req;
 	struct ceph_mon_command *h;
 	int ret = -ENOMEM;
@@ -999,7 +1012,9 @@ int do_mon_command_vargs(struct ceph_mon_client *monc, const char *fmt,
 	h->monhdr.have_version = 0;
 	h->monhdr.session_mon = cpu_to_le16(-1);
 	h->monhdr.session_mon_tid = 0;
-	h->fsid = monc->monmap->fsid;
+	monmap = rcu_dereference_protected(monc->monmap,
+					   lockdep_is_held(&monc->mutex));
+	h->fsid = monmap->fsid;
 	h->num_strs = cpu_to_le32(1);
 	len = vsprintf(h->str, fmt, ap);
 	h->str_len = cpu_to_le32(len);
@@ -1138,17 +1153,18 @@ static int build_initial_monmap(struct ceph_mon_client *monc)
 	__le32 my_type = ceph_msgr2(monc->client) ?
 		CEPH_ENTITY_ADDR_TYPE_MSGR2 : CEPH_ENTITY_ADDR_TYPE_LEGACY;
 	struct ceph_options *opt = monc->client->options;
+	struct ceph_monmap *monmap;
 	int num_mon = opt->num_mon;
 	int i;
 
 	/* build initial monmap */
-	monc->monmap = kzalloc_flex(*monc->monmap, mon_inst, num_mon);
-	if (!monc->monmap)
+	monmap = kzalloc_flex(*monmap, mon_inst, num_mon);
+	if (!monmap)
 		return -ENOMEM;
-	monc->monmap->num_mon = num_mon;
+	monmap->num_mon = num_mon;
 
 	for (i = 0; i < num_mon; i++) {
-		struct ceph_entity_inst *inst = &monc->monmap->mon_inst[i];
+		struct ceph_entity_inst *inst = &monmap->mon_inst[i];
 
 		memcpy(&inst->addr.in_addr, &opt->mon_addr[i].in_addr,
 		       sizeof(inst->addr.in_addr));
@@ -1157,6 +1173,7 @@ static int build_initial_monmap(struct ceph_mon_client *monc)
 		inst->name.type = CEPH_ENTITY_TYPE_MON;
 		inst->name.num = cpu_to_le64(i);
 	}
+	RCU_INIT_POINTER(monc->monmap, monmap);
 	return 0;
 }
 
@@ -1232,7 +1249,7 @@ int ceph_monc_init(struct ceph_mon_client *monc, struct ceph_client *cl)
 out_auth:
 	ceph_auth_destroy(monc->auth);
 out_monmap:
-	kfree(monc->monmap);
+	kfree(rcu_access_pointer(monc->monmap));
 out:
 	return err;
 }
@@ -1267,7 +1284,7 @@ void ceph_monc_stop(struct ceph_mon_client *monc)
 	ceph_msg_put(monc->m_subscribe);
 	ceph_msg_put(monc->m_subscribe_ack);
 
-	kfree(monc->monmap);
+	kfree_rcu(rcu_access_pointer(monc->monmap), rcu);
 }
 EXPORT_SYMBOL(ceph_monc_stop);
 
-- 
2.53.0