diff options
author | Ilya Dryomov <idryomov@gmail.com> | 2015-11-02 11:13:58 -0500 |
---|---|---|
committer | Ilya Dryomov <idryomov@gmail.com> | 2015-11-02 17:37:46 -0500 |
commit | 583d0fef756a7615e50f0f68ea0892a497d03971 (patch) | |
tree | 6064fa517086baae5daa12f683d9af51d4be9ed4 | |
parent | a51983e4dd2d4d63912aab939f657c4cd476e21a (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.c | 45 | ||||
-rw-r--r-- | net/ceph/osd_client.c | 3 |
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) | |||
637 | static void ceph_msg_remove(struct ceph_msg *msg) | 637 | static 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 | */ |
2439 | static void process_message(struct ceph_connection *con) | 2435 | static 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 | } |
2978 | EXPORT_SYMBOL(ceph_messenger_fini); | 2969 | EXPORT_SYMBOL(ceph_messenger_fini); |
2979 | 2970 | ||
2971 | static 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 | |||
2980 | static void clear_standby(struct ceph_connection *con) | 2980 | static 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 | */ |
3072 | void ceph_msg_revoke_incoming(struct ceph_msg *msg) | 3069 | void 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) { |