aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIlya Dryomov <idryomov@gmail.com>2015-11-02 11:13:58 -0500
committerIlya Dryomov <idryomov@gmail.com>2015-11-02 17:37:46 -0500
commit583d0fef756a7615e50f0f68ea0892a497d03971 (patch)
tree6064fa517086baae5daa12f683d9af51d4be9ed4
parenta51983e4dd2d4d63912aab939f657c4cd476e21a (diff)
libceph: clear msg->con in ceph_msg_release() only
The following bit in ceph_msg_revoke_incoming() is unsafe: struct ceph_connection *con = msg->con; if (!con) return; mutex_lock(&con->mutex); <more msg->con use> There is nothing preventing con from getting destroyed right after msg->con test. One easy way to reproduce this is to disable message signing only on the server side and try to map an image. The system will go into a libceph: read_partial_message ffff880073f0ab68 signature check failed libceph: osd0 192.168.255.155:6801 bad crc/signature libceph: read_partial_message ffff880073f0ab68 signature check failed libceph: osd0 192.168.255.155:6801 bad crc/signature loop which has to be interrupted with Ctrl-C. Hit Ctrl-C and you are likely to end up with a random GP fault if the reset handler executes "within" ceph_msg_revoke_incoming(): <yet another reply w/o a signature> ... <Ctrl-C> rbd_obj_request_end ceph_osdc_cancel_request __unregister_request ceph_osdc_put_request ceph_msg_revoke_incoming ... osd_reset __kick_osd_requests __reset_osd remove_osd ceph_con_close reset_connection <clear con->in_msg->con> <put con ref> put_osd <free osd/con> <msg->con use> <-- !!! If ceph_msg_revoke_incoming() executes "before" the reset handler, osd/con will be leaked because ceph_msg_revoke_incoming() clears con->in_msg but doesn't put con ref, while reset_connection() only puts con ref if con->in_msg != NULL. The current msg->con scheme was introduced by commits 38941f8031bf ("libceph: have messages point to their connection") and 92ce034b5a74 ("libceph: have messages take a connection reference"), which defined when messages get associated with a connection and when that association goes away. Part of the problem is that this association is supposed to go away in much too many places; closing this race entirely requires either a rework of the existing or an addition of a new layer of synchronization. In lieu of that, we can make it *much* less likely to hit by disassociating messages only on their destruction and resend through a different connection. This makes the code simpler and is probably a good thing to do regardless - this patch adds a msg_con_set() helper which is is called from only three places: ceph_con_send() and ceph_con_in_msg_alloc() to set msg->con and ceph_msg_release() to clear it. Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
-rw-r--r--net/ceph/messenger.c45
-rw-r--r--net/ceph/osd_client.c3
2 files changed, 20 insertions, 28 deletions
diff --git a/net/ceph/messenger.c b/net/ceph/messenger.c
index 0cc5608b2c8f..9981039ef4ff 100644
--- a/net/ceph/messenger.c
+++ b/net/ceph/messenger.c
@@ -637,9 +637,6 @@ static int con_close_socket(struct ceph_connection *con)
637static void ceph_msg_remove(struct ceph_msg *msg) 637static void ceph_msg_remove(struct ceph_msg *msg)
638{ 638{
639 list_del_init(&msg->list_head); 639 list_del_init(&msg->list_head);
640 BUG_ON(msg->con == NULL);
641 msg->con->ops->put(msg->con);
642 msg->con = NULL;
643 640
644 ceph_msg_put(msg); 641 ceph_msg_put(msg);
645} 642}
@@ -662,15 +659,14 @@ static void reset_connection(struct ceph_connection *con)
662 659
663 if (con->in_msg) { 660 if (con->in_msg) {
664 BUG_ON(con->in_msg->con != con); 661 BUG_ON(con->in_msg->con != con);
665 con->in_msg->con = NULL;
666 ceph_msg_put(con->in_msg); 662 ceph_msg_put(con->in_msg);
667 con->in_msg = NULL; 663 con->in_msg = NULL;
668 con->ops->put(con);
669 } 664 }
670 665
671 con->connect_seq = 0; 666 con->connect_seq = 0;
672 con->out_seq = 0; 667 con->out_seq = 0;
673 if (con->out_msg) { 668 if (con->out_msg) {
669 BUG_ON(con->out_msg->con != con);
674 ceph_msg_put(con->out_msg); 670 ceph_msg_put(con->out_msg);
675 con->out_msg = NULL; 671 con->out_msg = NULL;
676 } 672 }
@@ -2438,13 +2434,10 @@ static int read_partial_message(struct ceph_connection *con)
2438 */ 2434 */
2439static void process_message(struct ceph_connection *con) 2435static void process_message(struct ceph_connection *con)
2440{ 2436{
2441 struct ceph_msg *msg; 2437 struct ceph_msg *msg = con->in_msg;
2442 2438
2443 BUG_ON(con->in_msg->con != con); 2439 BUG_ON(con->in_msg->con != con);
2444 con->in_msg->con = NULL;
2445 msg = con->in_msg;
2446 con->in_msg = NULL; 2440 con->in_msg = NULL;
2447 con->ops->put(con);
2448 2441
2449 /* if first message, set peer_name */ 2442 /* if first message, set peer_name */
2450 if (con->peer_name.type == 0) 2443 if (con->peer_name.type == 0)
@@ -2918,10 +2911,8 @@ static void con_fault(struct ceph_connection *con)
2918 2911
2919 if (con->in_msg) { 2912 if (con->in_msg) {
2920 BUG_ON(con->in_msg->con != con); 2913 BUG_ON(con->in_msg->con != con);
2921 con->in_msg->con = NULL;
2922 ceph_msg_put(con->in_msg); 2914 ceph_msg_put(con->in_msg);
2923 con->in_msg = NULL; 2915 con->in_msg = NULL;
2924 con->ops->put(con);
2925 } 2916 }
2926 2917
2927 /* Requeue anything that hasn't been acked */ 2918 /* Requeue anything that hasn't been acked */
@@ -2977,6 +2968,15 @@ void ceph_messenger_fini(struct ceph_messenger *msgr)
2977} 2968}
2978EXPORT_SYMBOL(ceph_messenger_fini); 2969EXPORT_SYMBOL(ceph_messenger_fini);
2979 2970
2971static void msg_con_set(struct ceph_msg *msg, struct ceph_connection *con)
2972{
2973 if (msg->con)
2974 msg->con->ops->put(msg->con);
2975
2976 msg->con = con ? con->ops->get(con) : NULL;
2977 BUG_ON(msg->con != con);
2978}
2979
2980static void clear_standby(struct ceph_connection *con) 2980static void clear_standby(struct ceph_connection *con)
2981{ 2981{
2982 /* come back from STANDBY? */ 2982 /* come back from STANDBY? */
@@ -3008,9 +3008,7 @@ void ceph_con_send(struct ceph_connection *con, struct ceph_msg *msg)
3008 return; 3008 return;
3009 } 3009 }
3010 3010
3011 BUG_ON(msg->con != NULL); 3011 msg_con_set(msg, con);
3012 msg->con = con->ops->get(con);
3013 BUG_ON(msg->con == NULL);
3014 3012
3015 BUG_ON(!list_empty(&msg->list_head)); 3013 BUG_ON(!list_empty(&msg->list_head));
3016 list_add_tail(&msg->list_head, &con->out_queue); 3014 list_add_tail(&msg->list_head, &con->out_queue);
@@ -3038,16 +3036,15 @@ void ceph_msg_revoke(struct ceph_msg *msg)
3038{ 3036{
3039 struct ceph_connection *con = msg->con; 3037 struct ceph_connection *con = msg->con;
3040 3038
3041 if (!con) 3039 if (!con) {
3040 dout("%s msg %p null con\n", __func__, msg);
3042 return; /* Message not in our possession */ 3041 return; /* Message not in our possession */
3042 }
3043 3043
3044 mutex_lock(&con->mutex); 3044 mutex_lock(&con->mutex);
3045 if (!list_empty(&msg->list_head)) { 3045 if (!list_empty(&msg->list_head)) {
3046 dout("%s %p msg %p - was on queue\n", __func__, con, msg); 3046 dout("%s %p msg %p - was on queue\n", __func__, con, msg);
3047 list_del_init(&msg->list_head); 3047 list_del_init(&msg->list_head);
3048 BUG_ON(msg->con == NULL);
3049 msg->con->ops->put(msg->con);
3050 msg->con = NULL;
3051 msg->hdr.seq = 0; 3048 msg->hdr.seq = 0;
3052 3049
3053 ceph_msg_put(msg); 3050 ceph_msg_put(msg);
@@ -3071,16 +3068,13 @@ void ceph_msg_revoke(struct ceph_msg *msg)
3071 */ 3068 */
3072void ceph_msg_revoke_incoming(struct ceph_msg *msg) 3069void ceph_msg_revoke_incoming(struct ceph_msg *msg)
3073{ 3070{
3074 struct ceph_connection *con; 3071 struct ceph_connection *con = msg->con;
3075 3072
3076 BUG_ON(msg == NULL); 3073 if (!con) {
3077 if (!msg->con) {
3078 dout("%s msg %p null con\n", __func__, msg); 3074 dout("%s msg %p null con\n", __func__, msg);
3079
3080 return; /* Message not in our possession */ 3075 return; /* Message not in our possession */
3081 } 3076 }
3082 3077
3083 con = msg->con;
3084 mutex_lock(&con->mutex); 3078 mutex_lock(&con->mutex);
3085 if (con->in_msg == msg) { 3079 if (con->in_msg == msg) {
3086 unsigned int front_len = le32_to_cpu(con->in_hdr.front_len); 3080 unsigned int front_len = le32_to_cpu(con->in_hdr.front_len);
@@ -3326,9 +3320,8 @@ static int ceph_con_in_msg_alloc(struct ceph_connection *con, int *skip)
3326 } 3320 }
3327 if (msg) { 3321 if (msg) {
3328 BUG_ON(*skip); 3322 BUG_ON(*skip);
3323 msg_con_set(msg, con);
3329 con->in_msg = msg; 3324 con->in_msg = msg;
3330 con->in_msg->con = con->ops->get(con);
3331 BUG_ON(con->in_msg->con == NULL);
3332 } else { 3325 } else {
3333 /* 3326 /*
3334 * Null message pointer means either we should skip 3327 * Null message pointer means either we should skip
@@ -3375,6 +3368,8 @@ static void ceph_msg_release(struct kref *kref)
3375 dout("%s %p\n", __func__, m); 3368 dout("%s %p\n", __func__, m);
3376 WARN_ON(!list_empty(&m->list_head)); 3369 WARN_ON(!list_empty(&m->list_head));
3377 3370
3371 msg_con_set(m, NULL);
3372
3378 /* drop middle, data, if any */ 3373 /* drop middle, data, if any */
3379 if (m->middle) { 3374 if (m->middle) {
3380 ceph_buffer_put(m->middle); 3375 ceph_buffer_put(m->middle);
diff --git a/net/ceph/osd_client.c b/net/ceph/osd_client.c
index 118e4ce37ecc..f8f235930d88 100644
--- a/net/ceph/osd_client.c
+++ b/net/ceph/osd_client.c
@@ -2850,9 +2850,6 @@ static struct ceph_msg *get_reply(struct ceph_connection *con,
2850 goto out; 2850 goto out;
2851 } 2851 }
2852 2852
2853 if (req->r_reply->con)
2854 dout("%s revoking msg %p from old con %p\n", __func__,
2855 req->r_reply, req->r_reply->con);
2856 ceph_msg_revoke_incoming(req->r_reply); 2853 ceph_msg_revoke_incoming(req->r_reply);
2857 2854
2858 if (front_len > req->r_reply->front_alloc_len) { 2855 if (front_len > req->r_reply->front_alloc_len) {