Skip to content

Commit

Permalink
block: Fix NULL pointer dereference in sd_revalidate_disk
Browse files Browse the repository at this point in the history
Since 2.6.39 (1196f8b), 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: Huajun Li <[email protected]>
Signed-off-by: Jun'ichi Nomura <[email protected]>
Acked-by: Tejun Heo <[email protected]>
Cc: [email protected]
Signed-off-by: Jens Axboe <[email protected]>
  • Loading branch information
nomuranec authored and axboe committed Mar 2, 2012
1 parent 621032a commit fe316bf
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 12 deletions.
48 changes: 40 additions & 8 deletions block/partition-generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 hd_struct *part;
int p, highest, res;
rescan:
if (state && !IS_ERR(state)) {
kfree(state);
state = NULL;
}
int res;

if (bdev->bd_part_count)
return -EBUSY;
Expand All @@ -412,6 +406,24 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev)
delete_partition(disk, part->partno);
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)
disk->fops->revalidate_disk(disk);
check_disk_size_change(disk, bdev);
Expand Down Expand Up @@ -515,6 +527,26 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev)
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)
{
struct address_space *mapping = bdev->bd_inode->i_mapping;
Expand Down
16 changes: 12 additions & 4 deletions fs/block_dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
* partitions on a removed medium.
*/
if (bdev->bd_invalidated && (!ret || ret == -ENOMEDIUM))
rescan_partitions(disk, bdev);
if (bdev->bd_invalidated) {
if (!ret)
rescan_partitions(disk, bdev);
else if (ret == -ENOMEDIUM)
invalidate_partitions(disk, bdev);
}
if (ret)
goto out_clear;
} else {
Expand Down Expand Up @@ -1214,8 +1218,12 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
if (bdev->bd_disk->fops->open)
ret = bdev->bd_disk->fops->open(bdev, mode);
/* the same as first opener case, read comment there */
if (bdev->bd_invalidated && (!ret || ret == -ENOMEDIUM))
rescan_partitions(bdev->bd_disk, bdev);
if (bdev->bd_invalidated) {
if (!ret)
rescan_partitions(bdev->bd_disk, bdev);
else if (ret == -ENOMEDIUM)
invalidate_partitions(bdev->bd_disk, bdev);
}
if (ret)
goto out_unlock_bdev;
}
Expand Down
1 change: 1 addition & 0 deletions include/linux/genhd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 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,
int partno, sector_t start,
sector_t len, int flags,
Expand Down

0 comments on commit fe316bf

Please sign in to comment.