forked from mirrors/linux
		
	scsi: storvsc: Fix a race in sub-channel creation that can cause panic
We can concurrently try to open the same sub-channel from 2 paths: path #1: vmbus_onoffer() -> vmbus_process_offer() -> handle_sc_creation(). path #2: storvsc_probe() -> storvsc_connect_to_vsp() -> -> storvsc_channel_init() -> handle_multichannel_storage() -> -> vmbus_are_subchannels_present() -> handle_sc_creation(). They conflict with each other, but it was not an issue before the recent commitae6935ed7d("vmbus: split ring buffer allocation from open"), because at the beginning of vmbus_open() we checked newchannel->state so only one path could succeed, and the other would return with -EINVAL. Afterae6935ed7d, the failing path frees the channel's ringbuffer by vmbus_free_ring(), and this causes a panic later. Commitae6935ed7ditself is good, and it just reveals the longstanding race. We can resolve the issue by removing path #2, i.e. removing the second vmbus_are_subchannels_present() in handle_multichannel_storage(). BTW, the comment "Check to see if sub-channels have already been created" in handle_multichannel_storage() is incorrect: when we unload the driver, we first close the sub-channel(s) and then close the primary channel, next the host sends rescind-offer message(s) so primary->sc_list will become empty. This means the first vmbus_are_subchannels_present() in handle_multichannel_storage() is never useful. Fixes:ae6935ed7d("vmbus: split ring buffer allocation from open") Cc: stable@vger.kernel.org Cc: Long Li <longli@microsoft.com> Cc: Stephen Hemminger <sthemmin@microsoft.com> Cc: K. Y. Srinivasan <kys@microsoft.com> Cc: Haiyang Zhang <haiyangz@microsoft.com> Signed-off-by: Dexuan Cui <decui@microsoft.com> Signed-off-by: K. Y. Srinivasan <kys@microsoft.com> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
This commit is contained in:
		
							parent
							
								
									02f425f811
								
							
						
					
					
						commit
						c967590457
					
				
					 1 changed files with 30 additions and 31 deletions
				
			
		|  | @ -446,7 +446,6 @@ struct storvsc_device { | ||||||
| 
 | 
 | ||||||
| 	bool	 destroy; | 	bool	 destroy; | ||||||
| 	bool	 drain_notify; | 	bool	 drain_notify; | ||||||
| 	bool	 open_sub_channel; |  | ||||||
| 	atomic_t num_outstanding_req; | 	atomic_t num_outstanding_req; | ||||||
| 	struct Scsi_Host *host; | 	struct Scsi_Host *host; | ||||||
| 
 | 
 | ||||||
|  | @ -636,33 +635,38 @@ static inline struct storvsc_device *get_in_stor_device( | ||||||
| static void handle_sc_creation(struct vmbus_channel *new_sc) | static void handle_sc_creation(struct vmbus_channel *new_sc) | ||||||
| { | { | ||||||
| 	struct hv_device *device = new_sc->primary_channel->device_obj; | 	struct hv_device *device = new_sc->primary_channel->device_obj; | ||||||
|  | 	struct device *dev = &device->device; | ||||||
| 	struct storvsc_device *stor_device; | 	struct storvsc_device *stor_device; | ||||||
| 	struct vmstorage_channel_properties props; | 	struct vmstorage_channel_properties props; | ||||||
|  | 	int ret; | ||||||
| 
 | 
 | ||||||
| 	stor_device = get_out_stor_device(device); | 	stor_device = get_out_stor_device(device); | ||||||
| 	if (!stor_device) | 	if (!stor_device) | ||||||
| 		return; | 		return; | ||||||
| 
 | 
 | ||||||
| 	if (stor_device->open_sub_channel == false) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	memset(&props, 0, sizeof(struct vmstorage_channel_properties)); | 	memset(&props, 0, sizeof(struct vmstorage_channel_properties)); | ||||||
| 
 | 
 | ||||||
| 	vmbus_open(new_sc, | 	ret = vmbus_open(new_sc, | ||||||
| 			 storvsc_ringbuffer_size, | 			 storvsc_ringbuffer_size, | ||||||
| 			 storvsc_ringbuffer_size, | 			 storvsc_ringbuffer_size, | ||||||
| 			 (void *)&props, | 			 (void *)&props, | ||||||
| 			 sizeof(struct vmstorage_channel_properties), | 			 sizeof(struct vmstorage_channel_properties), | ||||||
| 			 storvsc_on_channel_callback, new_sc); | 			 storvsc_on_channel_callback, new_sc); | ||||||
| 
 | 
 | ||||||
| 	if (new_sc->state == CHANNEL_OPENED_STATE) { | 	/* In case vmbus_open() fails, we don't use the sub-channel. */ | ||||||
|  | 	if (ret != 0) { | ||||||
|  | 		dev_err(dev, "Failed to open sub-channel: err=%d\n", ret); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* Add the sub-channel to the array of available channels. */ | ||||||
| 	stor_device->stor_chns[new_sc->target_cpu] = new_sc; | 	stor_device->stor_chns[new_sc->target_cpu] = new_sc; | ||||||
| 	cpumask_set_cpu(new_sc->target_cpu, &stor_device->alloced_cpus); | 	cpumask_set_cpu(new_sc->target_cpu, &stor_device->alloced_cpus); | ||||||
| } | } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| static void  handle_multichannel_storage(struct hv_device *device, int max_chns) | static void  handle_multichannel_storage(struct hv_device *device, int max_chns) | ||||||
| { | { | ||||||
|  | 	struct device *dev = &device->device; | ||||||
| 	struct storvsc_device *stor_device; | 	struct storvsc_device *stor_device; | ||||||
| 	int num_cpus = num_online_cpus(); | 	int num_cpus = num_online_cpus(); | ||||||
| 	int num_sc; | 	int num_sc; | ||||||
|  | @ -679,21 +683,11 @@ static void  handle_multichannel_storage(struct hv_device *device, int max_chns) | ||||||
| 	request = &stor_device->init_request; | 	request = &stor_device->init_request; | ||||||
| 	vstor_packet = &request->vstor_packet; | 	vstor_packet = &request->vstor_packet; | ||||||
| 
 | 
 | ||||||
| 	stor_device->open_sub_channel = true; |  | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Establish a handler for dealing with subchannels. | 	 * Establish a handler for dealing with subchannels. | ||||||
| 	 */ | 	 */ | ||||||
| 	vmbus_set_sc_create_callback(device->channel, handle_sc_creation); | 	vmbus_set_sc_create_callback(device->channel, handle_sc_creation); | ||||||
| 
 | 
 | ||||||
| 	/*
 |  | ||||||
| 	 * Check to see if sub-channels have already been created. This |  | ||||||
| 	 * can happen when this driver is re-loaded after unloading. |  | ||||||
| 	 */ |  | ||||||
| 
 |  | ||||||
| 	if (vmbus_are_subchannels_present(device->channel)) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	stor_device->open_sub_channel = false; |  | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Request the host to create sub-channels. | 	 * Request the host to create sub-channels. | ||||||
| 	 */ | 	 */ | ||||||
|  | @ -710,23 +704,29 @@ static void  handle_multichannel_storage(struct hv_device *device, int max_chns) | ||||||
| 			       VM_PKT_DATA_INBAND, | 			       VM_PKT_DATA_INBAND, | ||||||
| 			       VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); | 			       VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); | ||||||
| 
 | 
 | ||||||
| 	if (ret != 0) | 	if (ret != 0) { | ||||||
|  | 		dev_err(dev, "Failed to create sub-channel: err=%d\n", ret); | ||||||
| 		return; | 		return; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	t = wait_for_completion_timeout(&request->wait_event, 10*HZ); | 	t = wait_for_completion_timeout(&request->wait_event, 10*HZ); | ||||||
| 	if (t == 0) | 	if (t == 0) { | ||||||
|  | 		dev_err(dev, "Failed to create sub-channel: timed out\n"); | ||||||
| 		return; | 		return; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if (vstor_packet->operation != VSTOR_OPERATION_COMPLETE_IO || | 	if (vstor_packet->operation != VSTOR_OPERATION_COMPLETE_IO || | ||||||
| 	    vstor_packet->status != 0) | 	    vstor_packet->status != 0) { | ||||||
|  | 		dev_err(dev, "Failed to create sub-channel: op=%d, sts=%d\n", | ||||||
|  | 			vstor_packet->operation, vstor_packet->status); | ||||||
| 		return; | 		return; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Now that we created the sub-channels, invoke the check; this | 	 * We need to do nothing here, because vmbus_process_offer() | ||||||
| 	 * may trigger the callback. | 	 * invokes channel->sc_creation_callback, which will open and use | ||||||
|  | 	 * the sub-channel(s). | ||||||
| 	 */ | 	 */ | ||||||
| 	stor_device->open_sub_channel = true; |  | ||||||
| 	vmbus_are_subchannels_present(device->channel); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void cache_wwn(struct storvsc_device *stor_device, | static void cache_wwn(struct storvsc_device *stor_device, | ||||||
|  | @ -1794,7 +1794,6 @@ static int storvsc_probe(struct hv_device *device, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	stor_device->destroy = false; | 	stor_device->destroy = false; | ||||||
| 	stor_device->open_sub_channel = false; |  | ||||||
| 	init_waitqueue_head(&stor_device->waiting_to_drain); | 	init_waitqueue_head(&stor_device->waiting_to_drain); | ||||||
| 	stor_device->device = device; | 	stor_device->device = device; | ||||||
| 	stor_device->host = host; | 	stor_device->host = host; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Dexuan Cui
						Dexuan Cui