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

Yong Wang <[email protected]> Fri, 10 Jul 2026 10:16:02 +0800
Newsgroups org.kernel.vger.ceph-devel
Message-ID <[email protected]>

在 2026/7/9 6:43, Viacheslav Dubeyko 写道:
> On Wed, 2026-07-08 at 11:03 +0800, Ren Wei wrote:
>> 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.
> 
> The approach makes sense to me. However, I have some concern. If the replaced
> monmap(s) could be in use with newly allocated one(s), then are we safe here?

The new monmap is fully allocated and initialized before publication, then
installed with rcu_assign_pointer() under monc->mutex. Readers only dereference
an RCU snapshot and treat the monmap as immutable, while the replaced monmap is
freed with kfree_rcu(), which means the old map is not reclaimed immediately, 
but is delayed until all readers that are still accessing it have exited the 
read-side critical section.Readers only see either the old map or the new map.

So it is safe to use.

> Have you tried to run xfstests for the patch?

I ran the ceph xfstests in QEMU on the patched kernel, and all ceph-
specific tests in the current tree passed: ceph/001-006.

> Thanks,
> Slava.
> 
>>
>> 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);
>>