Commit fe316bf2 authored by Jun'ichi Nomura's avatar Jun'ichi Nomura Committed by Jens Axboe

block: Fix NULL pointer dereference in sd_revalidate_disk

Since 2.6.39 (1196f8b8), when a driver returns -ENOMEDIUM for open(),
__blkdev_get() calls rescan_partitions() to remove
in-kernel partition structures and raise KOBJ_CHANGE uevent.

However it ends up calling driver's revalidate_disk without open
and could cause oops.

In the case of SCSI:

  process A                  process B
  ----------------------------------------------
  sys_open
    __blkdev_get
      sd_open
        returns -ENOMEDIUM
                             scsi_remove_device
                               <scsi_device torn down>
      rescan_partitions
        sd_revalidate_disk
          <oops>
Oopses are reported here:
http://marc.info/?l=linux-scsi&m=132388619710052

This patch separates the partition invalidation from rescan_partitions()
and use it for -ENOMEDIUM case.
Reported-by: default avatarHuajun Li <huajun.li.lee@gmail.com>
Signed-off-by: default avatarJun'ichi Nomura <j-nomura@ce.jp.nec.com>
Acked-by: default avatarTejun Heo <tj@kernel.org>
Cc: stable@kernel.org
Signed-off-by: default avatarJens Axboe <axboe@kernel.dk>
parent 621032ad
...@@ -389,17 +389,11 @@ static bool disk_unlock_native_capacity(struct gendisk *disk) ...@@ -389,17 +389,11 @@ static bool disk_unlock_native_capacity(struct gendisk *disk)
} }
} }
int rescan_partitions(struct gendisk *disk, struct block_device *bdev) static int drop_partitions(struct gendisk *disk, struct block_device *bdev)
{ {
struct parsed_partitions *state = NULL;
struct disk_part_iter piter; struct disk_part_iter piter;
struct hd_struct *part; struct hd_struct *part;
int p, highest, res; int res;
rescan:
if (state && !IS_ERR(state)) {
kfree(state);
state = NULL;
}
if (bdev->bd_part_count) if (bdev->bd_part_count)
return -EBUSY; return -EBUSY;
...@@ -412,6 +406,24 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev) ...@@ -412,6 +406,24 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev)
delete_partition(disk, part->partno); delete_partition(disk, part->partno);
disk_part_iter_exit(&piter); disk_part_iter_exit(&piter);
return 0;
}
int rescan_partitions(struct gendisk *disk, struct block_device *bdev)
{
struct parsed_partitions *state = NULL;
struct hd_struct *part;
int p, highest, res;
rescan:
if (state && !IS_ERR(state)) {
kfree(state);
state = NULL;
}
res = drop_partitions(disk, bdev);
if (res)
return res;
if (disk->fops->revalidate_disk) if (disk->fops->revalidate_disk)
disk->fops->revalidate_disk(disk); disk->fops->revalidate_disk(disk);
check_disk_size_change(disk, bdev); check_disk_size_change(disk, bdev);
...@@ -515,6 +527,26 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev) ...@@ -515,6 +527,26 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev)
return 0; return 0;
} }
int invalidate_partitions(struct gendisk *disk, struct block_device *bdev)
{
int res;
if (!bdev->bd_invalidated)
return 0;
res = drop_partitions(disk, bdev);
if (res)
return res;
set_capacity(disk, 0);
check_disk_size_change(disk, bdev);
bdev->bd_invalidated = 0;
/* tell userspace that the media / partition table may have changed */
kobject_uevent(&disk_to_dev(disk)->kobj, KOBJ_CHANGE);
return 0;
}
unsigned char *read_dev_sector(struct block_device *bdev, sector_t n, Sector *p) unsigned char *read_dev_sector(struct block_device *bdev, sector_t n, Sector *p)
{ {
struct address_space *mapping = bdev->bd_inode->i_mapping; struct address_space *mapping = bdev->bd_inode->i_mapping;
......
...@@ -1183,8 +1183,12 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part) ...@@ -1183,8 +1183,12 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
* The latter is necessary to prevent ghost * The latter is necessary to prevent ghost
* partitions on a removed medium. * partitions on a removed medium.
*/ */
if (bdev->bd_invalidated && (!ret || ret == -ENOMEDIUM)) if (bdev->bd_invalidated) {
rescan_partitions(disk, bdev); if (!ret)
rescan_partitions(disk, bdev);
else if (ret == -ENOMEDIUM)
invalidate_partitions(disk, bdev);
}
if (ret) if (ret)
goto out_clear; goto out_clear;
} else { } else {
...@@ -1214,8 +1218,12 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part) ...@@ -1214,8 +1218,12 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
if (bdev->bd_disk->fops->open) if (bdev->bd_disk->fops->open)
ret = bdev->bd_disk->fops->open(bdev, mode); ret = bdev->bd_disk->fops->open(bdev, mode);
/* the same as first opener case, read comment there */ /* the same as first opener case, read comment there */
if (bdev->bd_invalidated && (!ret || ret == -ENOMEDIUM)) if (bdev->bd_invalidated) {
rescan_partitions(bdev->bd_disk, bdev); if (!ret)
rescan_partitions(bdev->bd_disk, bdev);
else if (ret == -ENOMEDIUM)
invalidate_partitions(bdev->bd_disk, bdev);
}
if (ret) if (ret)
goto out_unlock_bdev; goto out_unlock_bdev;
} }
......
...@@ -596,6 +596,7 @@ extern char *disk_name (struct gendisk *hd, int partno, char *buf); ...@@ -596,6 +596,7 @@ extern char *disk_name (struct gendisk *hd, int partno, char *buf);
extern int disk_expand_part_tbl(struct gendisk *disk, int target); extern int disk_expand_part_tbl(struct gendisk *disk, int target);
extern int rescan_partitions(struct gendisk *disk, struct block_device *bdev); extern int rescan_partitions(struct gendisk *disk, struct block_device *bdev);
extern int invalidate_partitions(struct gendisk *disk, struct block_device *bdev);
extern struct hd_struct * __must_check add_partition(struct gendisk *disk, extern struct hd_struct * __must_check add_partition(struct gendisk *disk,
int partno, sector_t start, int partno, sector_t start,
sector_t len, int flags, sector_t len, int flags,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment