mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 16:54:21 +00:00 
			
		
		
		
	block: Fix NULL pointer dereference in sd_revalidate_disk
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 <huajun.li.lee@gmail.com>
Signed-off-by: Jun'ichi Nomura <j-nomura@ce.jp.nec.com>
Acked-by: Tejun Heo <tj@kernel.org>
Cc: stable@kernel.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
			
			
This commit is contained in:
		
							parent
							
								
									621032ad6e
								
							
						
					
					
						commit
						fe316bf2d5
					
				
					 3 changed files with 53 additions and 12 deletions
				
			
		|  | @ -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; | ||||
|  | @ -412,6 +406,24 @@ rescan: | |||
| 		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); | ||||
|  | @ -515,6 +527,26 @@ rescan: | |||
| 	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; | ||||
|  |  | |||
|  | @ -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 { | ||||
|  | @ -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; | ||||
| 		} | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Jun'ichi Nomura
						Jun'ichi Nomura