__blkg_prfill_rwstat() tries to get the device name by 'bdi->dev', while the 'dev' has been freed by bdi_unregister(). Then, blkg_dev_name() will return an invalid name pointer, resulting in crash on string(). The race as following: CPU1 CPU2 blkg_print_stat_bytes __scsi_remove_device del_gendisk bdi_unregister put_device(bdi->dev) kfree(bdi->dev) __blkg_prfill_rwstat blkg_dev_name //use the freed bdi->dev dev_name(blkg->q->backing_dev_info->dev) bdi->dev = NULL
There is another simple way to fix the problem. Since blkg_dev_name() have been coverd by rcu_read_lock/unlock(), we can wait all rcu reader to finish before free 'bdi->dev' to avoid use-after-free. But I am not sure if this solution will introduce new problems. diff --git a/mm/backing-dev.c b/mm/backing-dev.c index 62f05f605fb5..6f322473ca4d 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -1007,15 +1007,19 @@ static void bdi_remove_from_list(struct backing_dev_info *bdi) void bdi_unregister(struct backing_dev_info *bdi) { + struct device *dev = bdi->dev; /* make sure nobody finds us on the bdi_list anymore */ bdi_remove_from_list(bdi); wb_shutdown(&bdi->wb); cgwb_bdi_unregister(bdi); - if (bdi->dev) { + if (dev) { bdi_debug_unregister(bdi); - device_unregister(bdi->dev); bdi->dev = NULL; + device_del(dev); + /* wait all rcu reader of bdi->dev before free dev */ + synchronize_rcu(); + put_device(dev); } if (bdi->owner) {