Skip to content

Commit b02176f

Browse files
htejunaxboe
authored andcommitted
block: don't release bdi while request_queue has live references
bdi's are initialized in two steps, bdi_init() and bdi_register(), but destroyed in a single step by bdi_destroy() which, for a bdi embedded in a request_queue, is called during blk_cleanup_queue() which makes the queue invisible and starts the draining of remaining usages. A request_queue's user can access the congestion state of the embedded bdi as long as it holds a reference to the queue. As such, it may access the congested state of a queue which finished blk_cleanup_queue() but hasn't reached blk_release_queue() yet. Because the congested state was embedded in backing_dev_info which in turn is embedded in request_queue, accessing the congested state after bdi_destroy() was called was fine. The bdi was destroyed but the memory region for the congested state remained accessible till the queue got released. a13f35e ("writeback: don't embed root bdi_writeback_congested in bdi_writeback") changed the situation. Now, the root congested state which is expected to be pinned while request_queue remains accessible is separately reference counted and the base ref is put during bdi_destroy(). This means that the root congested state may go away prematurely while the queue is between bdi_dstroy() and blk_cleanup_queue(), which was detected by Andrey's KASAN tests. The root cause of this problem is that bdi doesn't distinguish the two steps of destruction, unregistration and release, and now the root congested state actually requires a separate release step. To fix the issue, this patch separates out bdi_unregister() and bdi_exit() from bdi_destroy(). bdi_unregister() is called from blk_cleanup_queue() and bdi_exit() from blk_release_queue(). bdi_destroy() is now just a simple wrapper calling the two steps back-to-back. While at it, the prototype of bdi_destroy() is moved right below bdi_setup_and_register() so that the counterpart operations are located together. Signed-off-by: Tejun Heo <[email protected]> Fixes: a13f35e ("writeback: don't embed root bdi_writeback_congested in bdi_writeback") Cc: [email protected] # v4.2+ Reported-and-tested-by: Andrey Konovalov <[email protected]> Link: http://lkml.kernel.org/g/CAAeHK+zUJ74Zn17=rOyxacHU18SgCfC6bsYW=6kCY5GXJBwGfQ@mail.gmail.com Reviewed-by: Jan Kara <[email protected]> Reviewed-by: Jeff Moyer <[email protected]> Signed-off-by: Jens Axboe <[email protected]>
1 parent 81c04b9 commit b02176f

File tree

4 files changed

+18
-3
lines changed

4 files changed

+18
-3
lines changed

block/blk-core.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ void blk_cleanup_queue(struct request_queue *q)
576576
q->queue_lock = &q->__queue_lock;
577577
spin_unlock_irq(lock);
578578

579-
bdi_destroy(&q->backing_dev_info);
579+
bdi_unregister(&q->backing_dev_info);
580580

581581
/* @q is and will stay empty, shutdown and put */
582582
blk_put_queue(q);

block/blk-sysfs.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ static void blk_release_queue(struct kobject *kobj)
540540
struct request_queue *q =
541541
container_of(kobj, struct request_queue, kobj);
542542

543+
bdi_exit(&q->backing_dev_info);
543544
blkcg_exit_queue(q);
544545

545546
if (q->elevator) {

include/linux/backing-dev.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
#include <linux/slab.h>
2020

2121
int __must_check bdi_init(struct backing_dev_info *bdi);
22-
void bdi_destroy(struct backing_dev_info *bdi);
22+
void bdi_exit(struct backing_dev_info *bdi);
2323

2424
__printf(3, 4)
2525
int bdi_register(struct backing_dev_info *bdi, struct device *parent,
2626
const char *fmt, ...);
2727
int bdi_register_dev(struct backing_dev_info *bdi, dev_t dev);
28+
void bdi_unregister(struct backing_dev_info *bdi);
29+
2830
int __must_check bdi_setup_and_register(struct backing_dev_info *, char *);
31+
void bdi_destroy(struct backing_dev_info *bdi);
32+
2933
void wb_start_writeback(struct bdi_writeback *wb, long nr_pages,
3034
bool range_cyclic, enum wb_reason reason);
3135
void wb_start_background_writeback(struct bdi_writeback *wb);

mm/backing-dev.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ static void bdi_remove_from_list(struct backing_dev_info *bdi)
835835
synchronize_rcu_expedited();
836836
}
837837

838-
void bdi_destroy(struct backing_dev_info *bdi)
838+
void bdi_unregister(struct backing_dev_info *bdi)
839839
{
840840
/* make sure nobody finds us on the bdi_list anymore */
841841
bdi_remove_from_list(bdi);
@@ -847,9 +847,19 @@ void bdi_destroy(struct backing_dev_info *bdi)
847847
device_unregister(bdi->dev);
848848
bdi->dev = NULL;
849849
}
850+
}
850851

852+
void bdi_exit(struct backing_dev_info *bdi)
853+
{
854+
WARN_ON_ONCE(bdi->dev);
851855
wb_exit(&bdi->wb);
852856
}
857+
858+
void bdi_destroy(struct backing_dev_info *bdi)
859+
{
860+
bdi_unregister(bdi);
861+
bdi_exit(bdi);
862+
}
853863
EXPORT_SYMBOL(bdi_destroy);
854864

855865
/*

0 commit comments

Comments
 (0)