History

libceph: fix potential use-after-free on linger ping and resends

request_reinit() is not only ugly as the comment rightfully suggests, but also unsafe. Even though it is called with osdc->lock held for write in all cases, resetting the OSD request refcount can still race with handle_reply() and result in use-after-free. Taking linger ping as an example: handle_timeout thread handle_reply thread down_read(&osdc->lock) req = lookup_request(...) ... finish_request(req) # unregisters up_read(&osdc->lock) __complete_request(req) linger_ping_cb(req) # req->r_kref == 2 because handle_reply still holds its ref down_write(&osdc->lock) send_linger_ping(lreq) req = lreq->ping_req # same req # cancel_linger_request is NOT # called - handle_reply already # unregistered request_reinit(req) WARN_ON(req->r_kref != 1) # fires request_init(req) kref_init(req->r_kref) # req->r_kref == 1 after kref_init ceph_osdc_put_request(req) kref_put(req->r_kref) # req->r_kref == 0 after kref_put, req is freed <further req initialization/use> !!! This happens because send_linger_ping() always (re)uses the same OSD request for watch ping requests, relying on cancel_linger_request() to unregister it from the OSD client and rip its messages out from the messenger. send_linger() does the same for watch/notify registration and watch reconnect requests. Unfortunately cancel_request() doesn't guarantee that after it returns the OSD client would be completely done with the OSD request -- a ref could still be held and the callback (if specified) could still be invoked too. The original motivation for request_reinit() was inability to deal with allocation failures in send_linger() and send_linger_ping(). Switching to using osdc->req_mempool (currently only used by CephFS) respects that and allows us to get rid of request_reinit().

Ilya Dryomov committed 4 years ago

Commit 75dbb681 parent 42226c92 files · +122 −183Browse files

Unified · Split

2 files
  1. include/linux/ceph/osd_client.h +3 −0
  2. net/ceph/osd_client.c +119 −183
include/linux/ceph/osd_client.h+3−0
@@ -287,6 +287,9 @@ struct ceph_osd_linger_request { · 286 unmodified lines
287287 rados_watcherrcb_t errcb;
288288 void *data;
289289
290+ struct ceph_pagelist *request_pl;
291+ struct page **notify_id_pages;
292+
290293 struct page ***preply_pages;
291294 size_t *preply_len;
292295 };
net/ceph/osd_client.c+119−183
@@ -537,43 +537,6 @@ static void request_init(struct ceph_osd_request *req) · 536 unmodified lines
537537 target_init(&req->r_t);
538538 }
539539
540−/*
541− * This is ugly, but it allows us to reuse linger registration and ping
542− * requests, keeping the structure of the code around send_linger{_ping}()
543− * reasonable. Setting up a min_nr=2 mempool for each linger request
544− * and dealing with copying ops (this blasts req only, watch op remains
545− * intact) isn't any better.
546− */
547−static void request_reinit(struct ceph_osd_request *req)
548−{
549− struct ceph_osd_client *osdc = req->r_osdc;
550− bool mempool = req->r_mempool;
551− unsigned int num_ops = req->r_num_ops;
552− u64 snapid = req->r_snapid;
553− struct ceph_snap_context *snapc = req->r_snapc;
554− bool linger = req->r_linger;
555− struct ceph_msg *request_msg = req->r_request;
556− struct ceph_msg *reply_msg = req->r_reply;
557−
558− dout("%s req %p\n", __func__, req);
559− WARN_ON(kref_read(&req->r_kref) != 1);
560− request_release_checks(req);
561−
562− WARN_ON(kref_read(&request_msg->kref) != 1);
563− WARN_ON(kref_read(&reply_msg->kref) != 1);
564− target_destroy(&req->r_t);
565−
566− request_init(req);
567− req->r_osdc = osdc;
568− req->r_mempool = mempool;
569− req->r_num_ops = num_ops;
570− req->r_snapid = snapid;
571− req->r_snapc = snapc;
572− req->r_linger = linger;
573− req->r_request = request_msg;
574− req->r_reply = reply_msg;
575−}
576−
577540 struct ceph_osd_request *ceph_osdc_alloc_request(struct ceph_osd_client *osdc,
578541 struct ceph_snap_context *snapc,
579542 unsigned int num_ops,
@@ -918,14 +881,30 @@ EXPORT_SYMBOL(osd_req_op_xattr_init); · 338 unmodified lines
918881 * @watch_opcode: CEPH_OSD_WATCH_OP_*
919882 */
920883 static void osd_req_op_watch_init(struct ceph_osd_request *req, int which,
921− u64 cookie, u8 watch_opcode)
884+ u8 watch_opcode, u64 cookie, u32 gen)
922885 {
923886 struct ceph_osd_req_op *op;
924887
925888 op = osd_req_op_init(req, which, CEPH_OSD_OP_WATCH, 0);
926889 op->watch.cookie = cookie;
927890 op->watch.op = watch_opcode;
928− op->watch.gen = 0;
891+ op->watch.gen = gen;
892+}
893+
894+/*
895+ * prot_ver, timeout and notify payload (may be empty) should already be
896+ * encoded in @request_pl
897+ */
898+static void osd_req_op_notify_init(struct ceph_osd_request *req, int which,
899+ u64 cookie, struct ceph_pagelist *request_pl)
900+{
901+ struct ceph_osd_req_op *op;
902+
903+ op = osd_req_op_init(req, which, CEPH_OSD_OP_NOTIFY, 0);
904+ op->notify.cookie = cookie;
905+
906+ ceph_osd_data_pagelist_init(&op->notify.request_data, request_pl);
907+ op->indata_len = request_pl->length;
929908 }
930909
931910 /*
@@ -2731,10 +2710,13 @@ static void linger_release(struct kref *kref) · 1799 unmodified lines
27312710 WARN_ON(!list_empty(&lreq->pending_lworks));
27322711 WARN_ON(lreq->osd);
27332712
2734− if (lreq->reg_req)
2735− ceph_osdc_put_request(lreq->reg_req);
2736− if (lreq->ping_req)
2737− ceph_osdc_put_request(lreq->ping_req);
2713+ if (lreq->request_pl)
2714+ ceph_pagelist_release(lreq->request_pl);
2715+ if (lreq->notify_id_pages)
2716+ ceph_release_page_vector(lreq->notify_id_pages, 1);
2717+
2718+ ceph_osdc_put_request(lreq->reg_req);
2719+ ceph_osdc_put_request(lreq->ping_req);
27382720 target_destroy(&lreq->t);
27392721 kfree(lreq);
27402722 }
@@ -3003,6 +2985,12 @@ static void linger_commit_cb(struct ceph_osd_request *req) · 262 unmodified lines
30032985 struct ceph_osd_linger_request *lreq = req->r_priv;
30042986
30052987 mutex_lock(&lreq->lock);
2988+ if (req != lreq->reg_req) {
2989+ dout("%s lreq %p linger_id %llu unknown req (%p != %p)\n",
2990+ __func__, lreq, lreq->linger_id, req, lreq->reg_req);
2991+ goto out;
2992+ }
2993+
30062994 dout("%s lreq %p linger_id %llu result %d\n", __func__, lreq,
30072995 lreq->linger_id, req->r_result);
30082996 linger_reg_commit_complete(lreq, req->r_result);
@@ -3026,6 +3014,7 @@ static void linger_commit_cb(struct ceph_osd_request *req) · 17 unmodified lines
30263014 }
30273015 }
30283016
3017+out:
30293018 mutex_unlock(&lreq->lock);
30303019 linger_put(lreq);
30313020 }
@@ -3048,6 +3037,12 @@ static void linger_reconnect_cb(struct ceph_osd_request *req) · 16 unmodified lines
30483037 struct ceph_osd_linger_request *lreq = req->r_priv;
30493038
30503039 mutex_lock(&lreq->lock);
3040+ if (req != lreq->reg_req) {
3041+ dout("%s lreq %p linger_id %llu unknown req (%p != %p)\n",
3042+ __func__, lreq, lreq->linger_id, req, lreq->reg_req);
3043+ goto out;
3044+ }
3045+
30513046 dout("%s lreq %p linger_id %llu result %d last_error %d\n", __func__,
30523047 lreq, lreq->linger_id, req->r_result, lreq->last_error);
30533048 if (req->r_result < 0) {
@@ -3057,46 +3052,64 @@ static void linger_reconnect_cb(struct ceph_osd_request *req) · 3 unmodified lines
30573052 }
30583053 }
30593054
3055+out:
30603056 mutex_unlock(&lreq->lock);
30613057 linger_put(lreq);
30623058 }
30633059
30643060 static void send_linger(struct ceph_osd_linger_request *lreq)
30653061 {
3066− struct ceph_osd_request *req = lreq->reg_req;
3067− struct ceph_osd_req_op *op = &req->r_ops[0];
3062+ struct ceph_osd_client *osdc = lreq->osdc;
3063+ struct ceph_osd_request *req;
3064+ int ret;
30683065
3069− verify_osdc_wrlocked(req->r_osdc);
3066+ verify_osdc_wrlocked(osdc);
3067+ mutex_lock(&lreq->lock);
30703068 dout("%s lreq %p linger_id %llu\n", __func__, lreq, lreq->linger_id);
30713069
3072− if (req->r_osd)
3073− cancel_linger_request(req);
3070+ if (lreq->reg_req) {
3071+ if (lreq->reg_req->r_osd)
3072+ cancel_linger_request(lreq->reg_req);
3073+ ceph_osdc_put_request(lreq->reg_req);
3074+ }
3075+
3076+ req = ceph_osdc_alloc_request(osdc, NULL, 1, true, GFP_NOIO);
3077+ BUG_ON(!req);
30743078
3075− request_reinit(req);
30763079 target_copy(&req->r_t, &lreq->t);
30773080 req->r_mtime = lreq->mtime;
30783081
3079− mutex_lock(&lreq->lock);
30803082 if (lreq->is_watch && lreq->committed) {
3081− WARN_ON(op->op != CEPH_OSD_OP_WATCH ||
3082− op->watch.cookie != lreq->linger_id);
3083− op->watch.op = CEPH_OSD_WATCH_OP_RECONNECT;
3084− op->watch.gen = ++lreq->register_gen;
3083+ osd_req_op_watch_init(req, 0, CEPH_OSD_WATCH_OP_RECONNECT,
3084+ lreq->linger_id, ++lreq->register_gen);
30853085 dout("lreq %p reconnect register_gen %u\n", lreq,
3086− op->watch.gen);
3086+ req->r_ops[0].watch.gen);
30873087 req->r_callback = linger_reconnect_cb;
30883088 } else {
3089− if (!lreq->is_watch)
3089+ if (lreq->is_watch) {
3090+ osd_req_op_watch_init(req, 0, CEPH_OSD_WATCH_OP_WATCH,
3091+ lreq->linger_id, 0);
3092+ } else {
30903093 lreq->notify_id = 0;
3091− else
3092− WARN_ON(op->watch.op != CEPH_OSD_WATCH_OP_WATCH);
3094+
3095+ refcount_inc(&lreq->request_pl->refcnt);
3096+ osd_req_op_notify_init(req, 0, lreq->linger_id,
3097+ lreq->request_pl);
3098+ ceph_osd_data_pages_init(
3099+ osd_req_op_data(req, 0, notify, response_data),
3100+ lreq->notify_id_pages, PAGE_SIZE, 0, false, false);
3101+ }
30933102 dout("lreq %p register\n", lreq);
30943103 req->r_callback = linger_commit_cb;
30953104 }
3096− mutex_unlock(&lreq->lock);
3105+
3106+ ret = ceph_osdc_alloc_messages(req, GFP_NOIO);
3107+ BUG_ON(ret);
30973108
30983109 req->r_priv = linger_get(lreq);
30993110 req->r_linger = true;
3111+ lreq->reg_req = req;
3112+ mutex_unlock(&lreq->lock);
31003113
31013114 submit_request(req, true);
31023115 }
@@ -3106,6 +3119,12 @@ static void linger_ping_cb(struct ceph_osd_request *req) · 3 unmodified lines
31063119 struct ceph_osd_linger_request *lreq = req->r_priv;
31073120
31083121 mutex_lock(&lreq->lock);
3122+ if (req != lreq->ping_req) {
3123+ dout("%s lreq %p linger_id %llu unknown req (%p != %p)\n",
3124+ __func__, lreq, lreq->linger_id, req, lreq->ping_req);
3125+ goto out;
3126+ }
3127+
31093128 dout("%s lreq %p linger_id %llu result %d ping_sent %lu last_error %d\n",
31103129 __func__, lreq, lreq->linger_id, req->r_result, lreq->ping_sent,
31113130 lreq->last_error);
@@ -3121,6 +3140,7 @@ static void linger_ping_cb(struct ceph_osd_request *req) · 9 unmodified lines
31213140 lreq->register_gen, req->r_ops[0].watch.gen);
31223141 }
31233142
3143+out:
31243144 mutex_unlock(&lreq->lock);
31253145 linger_put(lreq);
31263146 }
@@ -3128,8 +3148,8 @@ static void linger_ping_cb(struct ceph_osd_request *req) · 1 unmodified line
31283148 static void send_linger_ping(struct ceph_osd_linger_request *lreq)
31293149 {
31303150 struct ceph_osd_client *osdc = lreq->osdc;
3131− struct ceph_osd_request *req = lreq->ping_req;
3132− struct ceph_osd_req_op *op = &req->r_ops[0];
3151+ struct ceph_osd_request *req;
3152+ int ret;
31333153
31343154 if (ceph_osdmap_flag(osdc, CEPH_OSDMAP_PAUSERD)) {
31353155 dout("%s PAUSERD\n", __func__);
@@ -3141,19 +3161,26 @@ static void send_linger_ping(struct ceph_osd_linger_request *lreq) · 5 unmodified lines
31413161 __func__, lreq, lreq->linger_id, lreq->ping_sent,
31423162 lreq->register_gen);
31433163
3144− if (req->r_osd)
3145− cancel_linger_request(req);
3164+ if (lreq->ping_req) {
3165+ if (lreq->ping_req->r_osd)
3166+ cancel_linger_request(lreq->ping_req);
3167+ ceph_osdc_put_request(lreq->ping_req);
3168+ }
31463169
3147− request_reinit(req);
3148− target_copy(&req->r_t, &lreq->t);
3170+ req = ceph_osdc_alloc_request(osdc, NULL, 1, true, GFP_NOIO);
3171+ BUG_ON(!req);
31493172
3150− WARN_ON(op->op != CEPH_OSD_OP_WATCH ||
3151− op->watch.cookie != lreq->linger_id ||
3152− op->watch.op != CEPH_OSD_WATCH_OP_PING);
3153− op->watch.gen = lreq->register_gen;
3173+ target_copy(&req->r_t, &lreq->t);
3174+ osd_req_op_watch_init(req, 0, CEPH_OSD_WATCH_OP_PING, lreq->linger_id,
3175+ lreq->register_gen);
31543176 req->r_callback = linger_ping_cb;
3177+
3178+ ret = ceph_osdc_alloc_messages(req, GFP_NOIO);
3179+ BUG_ON(ret);
3180+
31553181 req->r_priv = linger_get(lreq);
31563182 req->r_linger = true;
3183+ lreq->ping_req = req;
31573184
31583185 ceph_osdc_get_request(req);
31593186 account_request(req);
@@ -3169,12 +3196,6 @@ static void linger_submit(struct ceph_osd_linger_request *lreq) · 9 unmodified lines
31693196
31703197 down_write(&osdc->lock);
31713198 linger_register(lreq);
3172− if (lreq->is_watch) {
3173− lreq->reg_req->r_ops[0].watch.cookie = lreq->linger_id;
3174− lreq->ping_req->r_ops[0].watch.cookie = lreq->linger_id;
3175− } else {
3176− lreq->reg_req->r_ops[0].notify.cookie = lreq->linger_id;
3177− }
31783199
31793200 calc_target(osdc, &lreq->t, false);
31803201 osd = lookup_create_osd(osdc, lreq->t.osd, true);
@@ -3206,9 +3227,9 @@ static void cancel_linger_map_check(struct ceph_osd_linger_request *lreq) · 25 unmodified lines
32063227 */
32073228 static void __linger_cancel(struct ceph_osd_linger_request *lreq)
32083229 {
3209− if (lreq->is_watch && lreq->ping_req->r_osd)
3230+ if (lreq->ping_req && lreq->ping_req->r_osd)
32103231 cancel_linger_request(lreq->ping_req);
3211− if (lreq->reg_req->r_osd)
3232+ if (lreq->reg_req && lreq->reg_req->r_osd)
32123233 cancel_linger_request(lreq->reg_req);
32133234 cancel_linger_map_check(lreq);
32143235 unlink_linger(lreq->osd, lreq);
@@ -4657,43 +4678,6 @@ again: · 1442 unmodified lines
46574678 }
46584679 EXPORT_SYMBOL(ceph_osdc_sync);
46594680
4660−static struct ceph_osd_request *
4661−alloc_linger_request(struct ceph_osd_linger_request *lreq)
4662−{
4663− struct ceph_osd_request *req;
4664−
4665− req = ceph_osdc_alloc_request(lreq->osdc, NULL, 1, false, GFP_NOIO);
4666− if (!req)
4667− return NULL;
4668−
4669− ceph_oid_copy(&req->r_base_oid, &lreq->t.base_oid);
4670− ceph_oloc_copy(&req->r_base_oloc, &lreq->t.base_oloc);
4671− return req;
4672−}
4673−
4674−static struct ceph_osd_request *
4675−alloc_watch_request(struct ceph_osd_linger_request *lreq, u8 watch_opcode)
4676−{
4677− struct ceph_osd_request *req;
4678−
4679− req = alloc_linger_request(lreq);
4680− if (!req)
4681− return NULL;
4682−
4683− /*
4684− * Pass 0 for cookie because we don't know it yet, it will be
4685− * filled in by linger_submit().
4686− */
4687− osd_req_op_watch_init(req, 0, 0, watch_opcode);
4688−
4689− if (ceph_osdc_alloc_messages(req, GFP_NOIO)) {
4690− ceph_osdc_put_request(req);
4691− return NULL;
4692− }
4693−
4694− return req;
4695−}
4696−
46974681 /*
46984682 * Returns a handle, caller owns a ref.
46994683 */
@@ -4723,18 +4707,6 @@ ceph_osdc_watch(struct ceph_osd_client *osdc, · 23 unmodified lines
47234707 lreq->t.flags = CEPH_OSD_FLAG_WRITE;
47244708 ktime_get_real_ts64(&lreq->mtime);
47254709
4726− lreq->reg_req = alloc_watch_request(lreq, CEPH_OSD_WATCH_OP_WATCH);
4727− if (!lreq->reg_req) {
4728− ret = -ENOMEM;
4729− goto err_put_lreq;
4730− }
4731−
4732− lreq->ping_req = alloc_watch_request(lreq, CEPH_OSD_WATCH_OP_PING);
4733− if (!lreq->ping_req) {
4734− ret = -ENOMEM;
4735− goto err_put_lreq;
4736− }
4737−
47384710 linger_submit(lreq);
47394711 ret = linger_reg_commit_wait(lreq);
47404712 if (ret) {
@@ -4772,8 +4744,8 @@ int ceph_osdc_unwatch(struct ceph_osd_client *osdc, · 31 unmodified lines
47724744 ceph_oloc_copy(&req->r_base_oloc, &lreq->t.base_oloc);
47734745 req->r_flags = CEPH_OSD_FLAG_WRITE;
47744746 ktime_get_real_ts64(&req->r_mtime);
4775− osd_req_op_watch_init(req, 0, lreq->linger_id,
4776− CEPH_OSD_WATCH_OP_UNWATCH);
4747+ osd_req_op_watch_init(req, 0, CEPH_OSD_WATCH_OP_UNWATCH,
4748+ lreq->linger_id, 0);
47774749
47784750 ret = ceph_osdc_alloc_messages(req, GFP_NOIO);
47794751 if (ret)
@@ -4859,35 +4831,6 @@ out_put_req: · 79 unmodified lines
48594831 }
48604832 EXPORT_SYMBOL(ceph_osdc_notify_ack);
48614833
4862−static int osd_req_op_notify_init(struct ceph_osd_request *req, int which,
4863− u64 cookie, u32 prot_ver, u32 timeout,
4864− void *payload, u32 payload_len)
4865−{
4866− struct ceph_osd_req_op *op;
4867− struct ceph_pagelist *pl;
4868− int ret;
4869−
4870− op = osd_req_op_init(req, which, CEPH_OSD_OP_NOTIFY, 0);
4871− op->notify.cookie = cookie;
4872−
4873− pl = ceph_pagelist_alloc(GFP_NOIO);
4874− if (!pl)
4875− return -ENOMEM;
4876−
4877− ret = ceph_pagelist_encode_32(pl, 1); /* prot_ver */
4878− ret |= ceph_pagelist_encode_32(pl, timeout);
4879− ret |= ceph_pagelist_encode_32(pl, payload_len);
4880− ret |= ceph_pagelist_append(pl, payload, payload_len);
4881− if (ret) {
4882− ceph_pagelist_release(pl);
4883− return -ENOMEM;
4884− }
4885−
4886− ceph_osd_data_pagelist_init(&op->notify.request_data, pl);
4887− op->indata_len = pl->length;
4888− return 0;
4889−}
4890−
48914834 /*
48924835 * @timeout: in seconds
48934836 *
@@ -4906,7 +4849,6 @@ int ceph_osdc_notify(struct ceph_osd_client *osdc, · 12 unmodified lines
49064849 size_t *preply_len)
49074850 {
49084851 struct ceph_osd_linger_request *lreq;
4909− struct page **pages;
49104852 int ret;
49114853
49124854 WARN_ON(!timeout);
@@ -4919,41 +4861,35 @@ int ceph_osdc_notify(struct ceph_osd_client *osdc, · 6 unmodified lines
49194861 if (!lreq)
49204862 return -ENOMEM;
49214863
4922− lreq->preply_pages = preply_pages;
4923− lreq->preply_len = preply_len;
4924−
4925− ceph_oid_copy(&lreq->t.base_oid, oid);
4926− ceph_oloc_copy(&lreq->t.base_oloc, oloc);
4927− lreq->t.flags = CEPH_OSD_FLAG_READ;
4928−
4929− lreq->reg_req = alloc_linger_request(lreq);
4930− if (!lreq->reg_req) {
4864+ lreq->request_pl = ceph_pagelist_alloc(GFP_NOIO);
4865+ if (!lreq->request_pl) {
49314866 ret = -ENOMEM;
49324867 goto out_put_lreq;
49334868 }
49344869
4935− /*
4936− * Pass 0 for cookie because we don't know it yet, it will be
4937− * filled in by linger_submit().
4938− */
4939− ret = osd_req_op_notify_init(lreq->reg_req, 0, 0, 1, timeout,
4940− payload, payload_len);
4941− if (ret)
4870+ ret = ceph_pagelist_encode_32(lreq->request_pl, 1); /* prot_ver */
4871+ ret |= ceph_pagelist_encode_32(lreq->request_pl, timeout);
4872+ ret |= ceph_pagelist_encode_32(lreq->request_pl, payload_len);
4873+ ret |= ceph_pagelist_append(lreq->request_pl, payload, payload_len);
4874+ if (ret) {
4875+ ret = -ENOMEM;
49424876 goto out_put_lreq;
4877+ }
49434878
49444879 /* for notify_id */
4945− pages = ceph_alloc_page_vector(1, GFP_NOIO);
4946− if (IS_ERR(pages)) {
4947− ret = PTR_ERR(pages);
4880+ lreq->notify_id_pages = ceph_alloc_page_vector(1, GFP_NOIO);
4881+ if (IS_ERR(lreq->notify_id_pages)) {
4882+ ret = PTR_ERR(lreq->notify_id_pages);
4883+ lreq->notify_id_pages = NULL;
49484884 goto out_put_lreq;
49494885 }
4950− ceph_osd_data_pages_init(osd_req_op_data(lreq->reg_req, 0, notify,
4951− response_data),
4952− pages, PAGE_SIZE, 0, false, true);
49534886
4954− ret = ceph_osdc_alloc_messages(lreq->reg_req, GFP_NOIO);
4955− if (ret)
4956− goto out_put_lreq;
4887+ lreq->preply_pages = preply_pages;
4888+ lreq->preply_len = preply_len;
4889+
4890+ ceph_oid_copy(&lreq->t.base_oid, oid);
4891+ ceph_oloc_copy(&lreq->t.base_oloc, oloc);
4892+ lreq->t.flags = CEPH_OSD_FLAG_READ;
49574893
49584894 linger_submit(lreq);
49594895 ret = linger_reg_commit_wait(lreq);