diff options
author | Paolo Valente <paolo.valente@linaro.org> | 2019-04-10 04:38:33 -0400 |
---|---|---|
committer | Jens Axboe <axboe@kernel.dk> | 2019-04-10 09:54:38 -0400 |
commit | eed47d19d9362bdd958e4ab56af480b9dbf6b2b6 (patch) | |
tree | ebb19cdcb30d8e16135544007babceb40da2d08b | |
parent | 3ec482d15cb986bf08b923f9193eeddb3b9ca69f (diff) |
block, bfq: fix use after free in bfq_bfqq_expire
The function bfq_bfqq_expire() invokes the function
__bfq_bfqq_expire(), and the latter may free the in-service bfq-queue.
If this happens, then no other instruction of bfq_bfqq_expire() must
be executed, or a use-after-free will occur.
Basing on the assumption that __bfq_bfqq_expire() invokes
bfq_put_queue() on the in-service bfq-queue exactly once, the queue is
assumed to be freed if its refcounter is equal to one right before
invoking __bfq_bfqq_expire().
But, since commit 9dee8b3b057e ("block, bfq: fix queue removal from
weights tree") this assumption is false. __bfq_bfqq_expire() may also
invoke bfq_weights_tree_remove() and, since commit 9dee8b3b057e
("block, bfq: fix queue removal from weights tree"), also
the latter function may invoke bfq_put_queue(). So __bfq_bfqq_expire()
may invoke bfq_put_queue() twice, and this is the actual case where
the in-service queue may happen to be freed.
To address this issue, this commit moves the check on the refcounter
of the queue right around the last bfq_put_queue() that may be invoked
on the queue.
Fixes: 9dee8b3b057e ("block, bfq: fix queue removal from weights tree")
Reported-by: Dmitrii Tcvetkov <demfloro@demfloro.ru>
Reported-by: Douglas Anderson <dianders@chromium.org>
Tested-by: Dmitrii Tcvetkov <demfloro@demfloro.ru>
Tested-by: Douglas Anderson <dianders@chromium.org>
Signed-off-by: Paolo Valente <paolo.valente@linaro.org>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
-rw-r--r-- | block/bfq-iosched.c | 15 | ||||
-rw-r--r-- | block/bfq-iosched.h | 2 | ||||
-rw-r--r-- | block/bfq-wf2q.c | 17 |
3 files changed, 23 insertions, 11 deletions
diff --git a/block/bfq-iosched.c b/block/bfq-iosched.c index fac188dd78fa..dfb8cb0af13a 100644 --- a/block/bfq-iosched.c +++ b/block/bfq-iosched.c | |||
@@ -2822,7 +2822,7 @@ static void bfq_dispatch_remove(struct request_queue *q, struct request *rq) | |||
2822 | bfq_remove_request(q, rq); | 2822 | bfq_remove_request(q, rq); |
2823 | } | 2823 | } |
2824 | 2824 | ||
2825 | static void __bfq_bfqq_expire(struct bfq_data *bfqd, struct bfq_queue *bfqq) | 2825 | static bool __bfq_bfqq_expire(struct bfq_data *bfqd, struct bfq_queue *bfqq) |
2826 | { | 2826 | { |
2827 | /* | 2827 | /* |
2828 | * If this bfqq is shared between multiple processes, check | 2828 | * If this bfqq is shared between multiple processes, check |
@@ -2855,9 +2855,11 @@ static void __bfq_bfqq_expire(struct bfq_data *bfqd, struct bfq_queue *bfqq) | |||
2855 | /* | 2855 | /* |
2856 | * All in-service entities must have been properly deactivated | 2856 | * All in-service entities must have been properly deactivated |
2857 | * or requeued before executing the next function, which | 2857 | * or requeued before executing the next function, which |
2858 | * resets all in-service entites as no more in service. | 2858 | * resets all in-service entities as no more in service. This |
2859 | * may cause bfqq to be freed. If this happens, the next | ||
2860 | * function returns true. | ||
2859 | */ | 2861 | */ |
2860 | __bfq_bfqd_reset_in_service(bfqd); | 2862 | return __bfq_bfqd_reset_in_service(bfqd); |
2861 | } | 2863 | } |
2862 | 2864 | ||
2863 | /** | 2865 | /** |
@@ -3262,7 +3264,6 @@ void bfq_bfqq_expire(struct bfq_data *bfqd, | |||
3262 | bool slow; | 3264 | bool slow; |
3263 | unsigned long delta = 0; | 3265 | unsigned long delta = 0; |
3264 | struct bfq_entity *entity = &bfqq->entity; | 3266 | struct bfq_entity *entity = &bfqq->entity; |
3265 | int ref; | ||
3266 | 3267 | ||
3267 | /* | 3268 | /* |
3268 | * Check whether the process is slow (see bfq_bfqq_is_slow). | 3269 | * Check whether the process is slow (see bfq_bfqq_is_slow). |
@@ -3347,10 +3348,8 @@ void bfq_bfqq_expire(struct bfq_data *bfqd, | |||
3347 | * reason. | 3348 | * reason. |
3348 | */ | 3349 | */ |
3349 | __bfq_bfqq_recalc_budget(bfqd, bfqq, reason); | 3350 | __bfq_bfqq_recalc_budget(bfqd, bfqq, reason); |
3350 | ref = bfqq->ref; | 3351 | if (__bfq_bfqq_expire(bfqd, bfqq)) |
3351 | __bfq_bfqq_expire(bfqd, bfqq); | 3352 | /* bfqq is gone, no more actions on it */ |
3352 | |||
3353 | if (ref == 1) /* bfqq is gone, no more actions on it */ | ||
3354 | return; | 3353 | return; |
3355 | 3354 | ||
3356 | bfqq->injected_service = 0; | 3355 | bfqq->injected_service = 0; |
diff --git a/block/bfq-iosched.h b/block/bfq-iosched.h index 062e1c4787f4..86394e503ca9 100644 --- a/block/bfq-iosched.h +++ b/block/bfq-iosched.h | |||
@@ -995,7 +995,7 @@ bool __bfq_deactivate_entity(struct bfq_entity *entity, | |||
995 | bool ins_into_idle_tree); | 995 | bool ins_into_idle_tree); |
996 | bool next_queue_may_preempt(struct bfq_data *bfqd); | 996 | bool next_queue_may_preempt(struct bfq_data *bfqd); |
997 | struct bfq_queue *bfq_get_next_queue(struct bfq_data *bfqd); | 997 | struct bfq_queue *bfq_get_next_queue(struct bfq_data *bfqd); |
998 | void __bfq_bfqd_reset_in_service(struct bfq_data *bfqd); | 998 | bool __bfq_bfqd_reset_in_service(struct bfq_data *bfqd); |
999 | void bfq_deactivate_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq, | 999 | void bfq_deactivate_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq, |
1000 | bool ins_into_idle_tree, bool expiration); | 1000 | bool ins_into_idle_tree, bool expiration); |
1001 | void bfq_activate_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq); | 1001 | void bfq_activate_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq); |
diff --git a/block/bfq-wf2q.c b/block/bfq-wf2q.c index a11bef75483d..ae4d000ac0af 100644 --- a/block/bfq-wf2q.c +++ b/block/bfq-wf2q.c | |||
@@ -1605,7 +1605,8 @@ struct bfq_queue *bfq_get_next_queue(struct bfq_data *bfqd) | |||
1605 | return bfqq; | 1605 | return bfqq; |
1606 | } | 1606 | } |
1607 | 1607 | ||
1608 | void __bfq_bfqd_reset_in_service(struct bfq_data *bfqd) | 1608 | /* returns true if the in-service queue gets freed */ |
1609 | bool __bfq_bfqd_reset_in_service(struct bfq_data *bfqd) | ||
1609 | { | 1610 | { |
1610 | struct bfq_queue *in_serv_bfqq = bfqd->in_service_queue; | 1611 | struct bfq_queue *in_serv_bfqq = bfqd->in_service_queue; |
1611 | struct bfq_entity *in_serv_entity = &in_serv_bfqq->entity; | 1612 | struct bfq_entity *in_serv_entity = &in_serv_bfqq->entity; |
@@ -1629,8 +1630,20 @@ void __bfq_bfqd_reset_in_service(struct bfq_data *bfqd) | |||
1629 | * service tree either, then release the service reference to | 1630 | * service tree either, then release the service reference to |
1630 | * the queue it represents (taken with bfq_get_entity). | 1631 | * the queue it represents (taken with bfq_get_entity). |
1631 | */ | 1632 | */ |
1632 | if (!in_serv_entity->on_st) | 1633 | if (!in_serv_entity->on_st) { |
1634 | /* | ||
1635 | * If no process is referencing in_serv_bfqq any | ||
1636 | * longer, then the service reference may be the only | ||
1637 | * reference to the queue. If this is the case, then | ||
1638 | * bfqq gets freed here. | ||
1639 | */ | ||
1640 | int ref = in_serv_bfqq->ref; | ||
1633 | bfq_put_queue(in_serv_bfqq); | 1641 | bfq_put_queue(in_serv_bfqq); |
1642 | if (ref == 1) | ||
1643 | return true; | ||
1644 | } | ||
1645 | |||
1646 | return false; | ||
1634 | } | 1647 | } |
1635 | 1648 | ||
1636 | void bfq_deactivate_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq, | 1649 | void bfq_deactivate_bfqq(struct bfq_data *bfqd, struct bfq_queue *bfqq, |