[PATCH 1/1] libceph: use RCU to protect monmap in ceph_compare_options()
Ren Wei <[email protected]> Tue, 7 Jul 2026 10:44:04 +0800
| Newsgroups | org.kernel.vger.ceph-devel |
|---|---|
| Message-ID | <8f8d2b03575328c52405f3d660b5d31e0c741ae9.1783342642.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: 4e7a5dcd1bba ("ceph: negotiate authentication protocol; implement AUTH_NONE protocol") 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]> --- include/linux/ceph/mon_client.h | 4 +++- net/ceph/ceph_common.c | 16 ++++++++++++---- net/ceph/mon_client.c | 34 ++++++++++++++++++++++----------- 3 files changed, 38 insertions(+), 16 deletions(-) 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..705800e07591 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); diff --git a/net/ceph/mon_client.c b/net/ceph/mon_client.c index d2cdc8ee3155..1c7f1affa37c 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); @@ -1267,7 +1279,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(monc->monmap, rcu); } EXPORT_SYMBOL(ceph_monc_stop); -- 2.53.0