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 /block/bfq-iosched.c | |
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>
Diffstat (limited to 'block/bfq-iosched.c')
-rw-r--r-- | block/bfq-iosched.c | 15 |
1 files changed, 7 insertions, 8 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; |