diff options
author | Josh Durgin <josh.durgin@inktank.com> | 2013-08-29 20:26:31 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-01-09 15:24:26 -0500 |
commit | 5b213542db631f8b0bf7b257e8ae2d37b134895c (patch) | |
tree | 0d9e50b2f261536604c18e1d2dc7e85f13a0cef5 | |
parent | b10f19aaa9a8e818254731a6219754b5015d7588 (diff) |
rbd: fix use-after free of rbd_dev->disk
commit 9875201e10496612080e7d164acc8f625c18725c upstream.
Removing a device deallocates the disk, unschedules the watch, and
finally cleans up the rbd_dev structure. rbd_dev_refresh(), called
from the watch callback, updates the disk size and rbd_dev
structure. With no locking between them, rbd_dev_refresh() may use the
device or rbd_dev after they've been freed.
To fix this, check whether RBD_DEV_FLAG_REMOVING is set before
updating the disk size in rbd_dev_refresh(). In order to prevent a
race where rbd_dev_refresh() is already revalidating the disk when
rbd_remove() is called, move the call to rbd_bus_del_dev() after the
watch is unregistered and all notifies are complete. It's safe to
defer deleting this structure because no new requests can be submitted
once the RBD_DEV_FLAG_REMOVING is set, since the device cannot be
opened.
Fixes: http://tracker.ceph.com/issues/5636
Signed-off-by: Josh Durgin <josh.durgin@inktank.com>
Reviewed-by: Alex Elder <elder@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/block/rbd.c | 40 |
1 files changed, 33 insertions, 7 deletions
diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index a1dff1cf1bca..58a4e03b7311 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c | |||
@@ -3336,6 +3336,31 @@ static void rbd_exists_validate(struct rbd_device *rbd_dev) | |||
3336 | clear_bit(RBD_DEV_FLAG_EXISTS, &rbd_dev->flags); | 3336 | clear_bit(RBD_DEV_FLAG_EXISTS, &rbd_dev->flags); |
3337 | } | 3337 | } |
3338 | 3338 | ||
3339 | static void rbd_dev_update_size(struct rbd_device *rbd_dev) | ||
3340 | { | ||
3341 | sector_t size; | ||
3342 | bool removing; | ||
3343 | |||
3344 | /* | ||
3345 | * Don't hold the lock while doing disk operations, | ||
3346 | * or lock ordering will conflict with the bdev mutex via: | ||
3347 | * rbd_add() -> blkdev_get() -> rbd_open() | ||
3348 | */ | ||
3349 | spin_lock_irq(&rbd_dev->lock); | ||
3350 | removing = test_bit(RBD_DEV_FLAG_REMOVING, &rbd_dev->flags); | ||
3351 | spin_unlock_irq(&rbd_dev->lock); | ||
3352 | /* | ||
3353 | * If the device is being removed, rbd_dev->disk has | ||
3354 | * been destroyed, so don't try to update its size | ||
3355 | */ | ||
3356 | if (!removing) { | ||
3357 | size = (sector_t)rbd_dev->mapping.size / SECTOR_SIZE; | ||
3358 | dout("setting size to %llu sectors", (unsigned long long)size); | ||
3359 | set_capacity(rbd_dev->disk, size); | ||
3360 | revalidate_disk(rbd_dev->disk); | ||
3361 | } | ||
3362 | } | ||
3363 | |||
3339 | static int rbd_dev_refresh(struct rbd_device *rbd_dev) | 3364 | static int rbd_dev_refresh(struct rbd_device *rbd_dev) |
3340 | { | 3365 | { |
3341 | u64 mapping_size; | 3366 | u64 mapping_size; |
@@ -3354,12 +3379,7 @@ static int rbd_dev_refresh(struct rbd_device *rbd_dev) | |||
3354 | rbd_exists_validate(rbd_dev); | 3379 | rbd_exists_validate(rbd_dev); |
3355 | mutex_unlock(&ctl_mutex); | 3380 | mutex_unlock(&ctl_mutex); |
3356 | if (mapping_size != rbd_dev->mapping.size) { | 3381 | if (mapping_size != rbd_dev->mapping.size) { |
3357 | sector_t size; | 3382 | rbd_dev_update_size(rbd_dev); |
3358 | |||
3359 | size = (sector_t)rbd_dev->mapping.size / SECTOR_SIZE; | ||
3360 | dout("setting size to %llu sectors", (unsigned long long)size); | ||
3361 | set_capacity(rbd_dev->disk, size); | ||
3362 | revalidate_disk(rbd_dev->disk); | ||
3363 | } | 3383 | } |
3364 | 3384 | ||
3365 | return ret; | 3385 | return ret; |
@@ -5147,7 +5167,6 @@ static ssize_t rbd_remove(struct bus_type *bus, | |||
5147 | if (ret < 0 || already) | 5167 | if (ret < 0 || already) |
5148 | goto done; | 5168 | goto done; |
5149 | 5169 | ||
5150 | rbd_bus_del_dev(rbd_dev); | ||
5151 | ret = rbd_dev_header_watch_sync(rbd_dev, false); | 5170 | ret = rbd_dev_header_watch_sync(rbd_dev, false); |
5152 | if (ret) | 5171 | if (ret) |
5153 | rbd_warn(rbd_dev, "failed to cancel watch event (%d)\n", ret); | 5172 | rbd_warn(rbd_dev, "failed to cancel watch event (%d)\n", ret); |
@@ -5158,6 +5177,13 @@ static ssize_t rbd_remove(struct bus_type *bus, | |||
5158 | */ | 5177 | */ |
5159 | dout("%s: flushing notifies", __func__); | 5178 | dout("%s: flushing notifies", __func__); |
5160 | ceph_osdc_flush_notifies(&rbd_dev->rbd_client->client->osdc); | 5179 | ceph_osdc_flush_notifies(&rbd_dev->rbd_client->client->osdc); |
5180 | /* | ||
5181 | * Don't free anything from rbd_dev->disk until after all | ||
5182 | * notifies are completely processed. Otherwise | ||
5183 | * rbd_bus_del_dev() will race with rbd_watch_cb(), resulting | ||
5184 | * in a potential use after free of rbd_dev->disk or rbd_dev. | ||
5185 | */ | ||
5186 | rbd_bus_del_dev(rbd_dev); | ||
5161 | rbd_dev_image_release(rbd_dev); | 5187 | rbd_dev_image_release(rbd_dev); |
5162 | module_put(THIS_MODULE); | 5188 | module_put(THIS_MODULE); |
5163 | ret = count; | 5189 | ret = count; |