bcache: fix refcount underflow in bcache_device_free()
authorColy Li <colyli@suse.de>
Wed, 27 May 2020 04:01:53 +0000 (12:01 +0800)
committerJens Axboe <axboe@kernel.dk>
Wed, 27 May 2020 11:19:36 +0000 (05:19 -0600)
The problematic code piece in bcache_device_free() is,

 785 static void bcache_device_free(struct bcache_device *d)
 786 {
 787     struct gendisk *disk = d->disk;
 [snipped]
 799     if (disk) {
 800             if (disk->flags & GENHD_FL_UP)
 801                     del_gendisk(disk);
 802
 803             if (disk->queue)
 804                     blk_cleanup_queue(disk->queue);
 805
 806             ida_simple_remove(&bcache_device_idx,
 807                               first_minor_to_idx(disk->first_minor));
 808             put_disk(disk);
 809         }
 [snipped]
 816 }

At line 808, put_disk(disk) may encounter kobject refcount of 'disk'
being underflow.

Here is how to reproduce the issue,
- Attche the backing device to a cache device and do random write to
  make the cache being dirty.
- Stop the bcache device while the cache device has dirty data of the
  backing device.
- Only register the backing device back, NOT register cache device.
- The bcache device node /dev/bcache0 won't show up, because backing
  device waits for the cache device shows up for the missing dirty
  data.
- Now echo 1 into /sys/fs/bcache/pendings_cleanup, to stop the pending
  backing device.
- After the pending backing device stopped, use 'dmesg' to check kernel
  message, a use-after-free warning from KASA reported the refcount of
  kobject linked to the 'disk' is underflow.

The dropping refcount at line 808 in the above code piece is added by
add_disk(d->disk) in bch_cached_dev_run(). But in the above condition
the cache device is not registered, bch_cached_dev_run() has no chance
to be called and the refcount is not added. The put_disk() for a non-
added refcount of gendisk kobject triggers a underflow warning.

This patch checks whether GENHD_FL_UP is set in disk->flags, if it is
not set then the bcache device was not added, don't call put_disk()
and the the underflow issue can be avoided.

Signed-off-by: Coly Li <colyli@suse.de>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
drivers/md/bcache/super.c

index 467149f..a10a3c7 100644 (file)
@@ -797,7 +797,9 @@ static void bcache_device_free(struct bcache_device *d)
                bcache_device_detach(d);
 
        if (disk) {
-               if (disk->flags & GENHD_FL_UP)
+               bool disk_added = (disk->flags & GENHD_FL_UP) != 0;
+
+               if (disk_added)
                        del_gendisk(disk);
 
                if (disk->queue)
@@ -805,7 +807,8 @@ static void bcache_device_free(struct bcache_device *d)
 
                ida_simple_remove(&bcache_device_idx,
                                  first_minor_to_idx(disk->first_minor));
-               put_disk(disk);
+               if (disk_added)
+                       put_disk(disk);
        }
 
        bioset_exit(&d->bio_split);