aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/block/rbd.c
diff options
context:
space:
mode:
authorAlex Elder <elder@dreamhost.com>2012-01-29 14:57:43 -0500
committerSage Weil <sage@newdream.net>2012-02-02 15:56:59 -0500
commitd23a4b3fd6ef70b80411b39b8c8bc548a219ce70 (patch)
treef42dbc4c8544acce987e35df5b3002f1def54cff /drivers/block/rbd.c
parent97bb59a03dd6767fcc00be09b0c6d9e5294eeea6 (diff)
rbd: fix safety of rbd_put_client()
The rbd_client structure uses a kref to arrange for cleaning up and freeing an instance when its last reference is dropped. The cleanup routine is rbd_client_release(), and one of the things it does is delete the rbd_client from rbd_client_list. It acquires node_lock to do so, but the way it is done is still not safe. The problem is that when attempting to reuse an existing rbd_client, the structure found might already be in the process of getting destroyed and cleaned up. Here's the scenario, with "CLIENT" representing an existing rbd_client that's involved in the race: Thread on CPU A | Thread on CPU B --------------- | --------------- rbd_put_client(CLIENT) | rbd_get_client() kref_put() | (acquires node_lock) kref->refcount becomes 0 | __rbd_client_find() returns CLIENT calls rbd_client_release() | kref_get(&CLIENT->kref); | (releases node_lock) (acquires node_lock) | deletes CLIENT from list | ...and starts using CLIENT... (releases node_lock) | and frees CLIENT | <-- but CLIENT gets freed here Fix this by having rbd_put_client() acquire node_lock. The result could still be improved, but at least it avoids this problem. Signed-off-by: Alex Elder <elder@dreamhost.com> Signed-off-by: Sage Weil <sage@newdream.net>
Diffstat (limited to 'drivers/block/rbd.c')
-rw-r--r--drivers/block/rbd.c6
1 files changed, 4 insertions, 2 deletions
diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c
index 7d8f8ddb3359..7f40cb4553c9 100644
--- a/drivers/block/rbd.c
+++ b/drivers/block/rbd.c
@@ -407,15 +407,15 @@ done_err:
407 407
408/* 408/*
409 * Destroy ceph client 409 * Destroy ceph client
410 *
411 * Caller must hold node_lock.
410 */ 412 */
411static void rbd_client_release(struct kref *kref) 413static void rbd_client_release(struct kref *kref)
412{ 414{
413 struct rbd_client *rbdc = container_of(kref, struct rbd_client, kref); 415 struct rbd_client *rbdc = container_of(kref, struct rbd_client, kref);
414 416
415 dout("rbd_release_client %p\n", rbdc); 417 dout("rbd_release_client %p\n", rbdc);
416 spin_lock(&node_lock);
417 list_del(&rbdc->node); 418 list_del(&rbdc->node);
418 spin_unlock(&node_lock);
419 419
420 ceph_destroy_client(rbdc->client); 420 ceph_destroy_client(rbdc->client);
421 kfree(rbdc->rbd_opts); 421 kfree(rbdc->rbd_opts);
@@ -428,7 +428,9 @@ static void rbd_client_release(struct kref *kref)
428 */ 428 */
429static void rbd_put_client(struct rbd_device *rbd_dev) 429static void rbd_put_client(struct rbd_device *rbd_dev)
430{ 430{
431 spin_lock(&node_lock);
431 kref_put(&rbd_dev->rbd_client->kref, rbd_client_release); 432 kref_put(&rbd_dev->rbd_client->kref, rbd_client_release);
433 spin_unlock(&node_lock);
432 rbd_dev->rbd_client = NULL; 434 rbd_dev->rbd_client = NULL;
433 rbd_dev->client = NULL; 435 rbd_dev->client = NULL;
434} 436}