summaryrefslogtreecommitdiff
path: root/block
diff options
context:
space:
mode:
authorFabio Checconi <fabio@gandalf.sssup.i>2008-04-10 08:28:01 +0200
committerJens Axboe <jens.axboe@oracle.com>2008-04-10 08:28:01 +0200
commit4faa3c8150c1d4f7b38d962eda7851083e218e3f (patch)
treea9110e7d49bc5f76d51d437a684844b47c31be3a /block
parent8191ecd1d14c6914c660dfa007154860a7908857 (diff)
downloadkernel-common-4faa3c8150c1d4f7b38d962eda7851083e218e3f.tar.gz
kernel-common-4faa3c8150c1d4f7b38d962eda7851083e218e3f.tar.bz2
kernel-common-4faa3c8150c1d4f7b38d962eda7851083e218e3f.zip
cfq-iosched: do not leak ioc_data across iosched switches
When switching scheduler from cfq, cfq_exit_queue() does not clear ioc->ioc_data, leaving a dangling pointer that can deceive the following lookups when the iosched is switched back to cfq. The pattern that can trigger that is the following: - elevator switch from cfq to something else; - module unloading, with elv_unregister() that calls cfq_free_io_context() on ioc freeing the cic (via the .trim op); - module gets reloaded and the elevator switches back to cfq; - reallocation of a cic at the same address as before (with a valid key). To fix it just assign NULL to ioc_data in __cfq_exit_single_io_context(), that is called from the regular exit path and from the elevator switching code. The only path that frees a cic and is not covered is the error handling one, but cic's freed in this way are never cached in ioc_data. Signed-off-by: Fabio Checconi <fabio@gandalf.sssup.it> Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
Diffstat (limited to 'block')
-rw-r--r--block/cfq-iosched.c9
1 files changed, 6 insertions, 3 deletions
diff --git a/block/cfq-iosched.c b/block/cfq-iosched.c
index f26da2bfcc15..f4e1006c253d 100644
--- a/block/cfq-iosched.c
+++ b/block/cfq-iosched.c
@@ -1214,6 +1214,8 @@ static void cfq_exit_cfqq(struct cfq_data *cfqd, struct cfq_queue *cfqq)
static void __cfq_exit_single_io_context(struct cfq_data *cfqd,
struct cfq_io_context *cic)
{
+ struct io_context *ioc = cic->ioc;
+
list_del_init(&cic->queue_list);
/*
@@ -1223,6 +1225,9 @@ static void __cfq_exit_single_io_context(struct cfq_data *cfqd,
cic->dead_key = (unsigned long) cic->key;
cic->key = NULL;
+ if (ioc->ioc_data == cic)
+ rcu_assign_pointer(ioc->ioc_data, NULL);
+
if (cic->cfqq[ASYNC]) {
cfq_exit_cfqq(cfqd, cic->cfqq[ASYNC]);
cic->cfqq[ASYNC] = NULL;
@@ -1255,7 +1260,6 @@ static void cfq_exit_single_io_context(struct io_context *ioc,
*/
static void cfq_exit_io_context(struct io_context *ioc)
{
- rcu_assign_pointer(ioc->ioc_data, NULL);
call_for_each_cic(ioc, cfq_exit_single_io_context);
}
@@ -1478,8 +1482,7 @@ cfq_drop_dead_cic(struct cfq_data *cfqd, struct io_context *ioc,
spin_lock_irqsave(&ioc->lock, flags);
- if (ioc->ioc_data == cic)
- rcu_assign_pointer(ioc->ioc_data, NULL);
+ BUG_ON(ioc->ioc_data == cic);
radix_tree_delete(&ioc->radix_root, (unsigned long) cfqd);
hlist_del_rcu(&cic->cic_list);