diff mbox series

[c/azure] scsi: storvsc: Fix a race in sub-channel creation that can cause panic

Message ID 20190409135527.8075-1-marcelo.cerri@canonical.com
State New
Headers show
Series [c/azure] scsi: storvsc: Fix a race in sub-channel creation that can cause panic | expand

Commit Message

Marcelo Henrique Cerri April 9, 2019, 1:55 p.m. UTC
From: Dexuan Cui <decui@microsoft.com>

BugLink: http://bugs.launchpad.net/bugs/1823805

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
commit ae6935ed7d42 ("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.

After ae6935ed7d42, the failing path frees the channel's ringbuffer by
vmbus_free_ring(), and this causes a panic later.

Commit ae6935ed7d42 itself 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: ae6935ed7d42 ("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>
(cherry picked from commit c967590457cae5ba4f018704c341641bdcecfdcf)
Signed-off-by: Marcelo Henrique Cerri <marcelo.cerri@canonical.com>
---
 drivers/scsi/storvsc_drv.c | 61 +++++++++++++++++++-------------------
 1 file changed, 30 insertions(+), 31 deletions(-)

Comments

Seth Forshee April 9, 2019, 2:08 p.m. UTC | #1
On Tue, Apr 09, 2019 at 10:55:27AM -0300, Marcelo Henrique Cerri wrote:
> From: Dexuan Cui <decui@microsoft.com>
> 
> BugLink: http://bugs.launchpad.net/bugs/1823805
> 
> 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
> commit ae6935ed7d42 ("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.
> 
> After ae6935ed7d42, the failing path frees the channel's ringbuffer by
> vmbus_free_ring(), and this causes a panic later.
> 
> Commit ae6935ed7d42 itself 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: ae6935ed7d42 ("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>
> (cherry picked from commit c967590457cae5ba4f018704c341641bdcecfdcf)
> Signed-off-by: Marcelo Henrique Cerri <marcelo.cerri@canonical.com>

Upstream cherry pick which has been included in stable releases, fixes a
serious issue.

Acked-by: Seth Forshee <seth.forshee@canonical.com>
Stefan Bader April 17, 2019, 3:56 p.m. UTC | #2
On 09.04.19 15:55, Marcelo Henrique Cerri wrote:
> From: Dexuan Cui <decui@microsoft.com>
> 
> BugLink: http://bugs.launchpad.net/bugs/1823805
> 
> 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
> commit ae6935ed7d42 ("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.
> 
> After ae6935ed7d42, the failing path frees the channel's ringbuffer by
> vmbus_free_ring(), and this causes a panic later.
> 
> Commit ae6935ed7d42 itself 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: ae6935ed7d42 ("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>
> (cherry picked from commit c967590457cae5ba4f018704c341641bdcecfdcf)
> Signed-off-by: Marcelo Henrique Cerri <marcelo.cerri@canonical.com>
Acked-by: Stefan Bader <stefan.bader@canonical.com>
> ---

If that is not already done

>  drivers/scsi/storvsc_drv.c | 61 +++++++++++++++++++-------------------
>  1 file changed, 30 insertions(+), 31 deletions(-)
> 
> diff --git a/drivers/scsi/storvsc_drv.c b/drivers/scsi/storvsc_drv.c
> index 883bff0ba3ce..57fb1b067b33 100644
> --- a/drivers/scsi/storvsc_drv.c
> +++ b/drivers/scsi/storvsc_drv.c
> @@ -446,7 +446,6 @@ struct storvsc_device {
>  
>  	bool	 destroy;
>  	bool	 drain_notify;
> -	bool	 open_sub_channel;
>  	atomic_t num_outstanding_req;
>  	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)
>  {
>  	struct hv_device *device = new_sc->primary_channel->device_obj;
> +	struct device *dev = &device->device;
>  	struct storvsc_device *stor_device;
>  	struct vmstorage_channel_properties props;
> +	int ret;
>  
>  	stor_device = get_out_stor_device(device);
>  	if (!stor_device)
>  		return;
>  
> -	if (stor_device->open_sub_channel == false)
> -		return;
> -
>  	memset(&props, 0, sizeof(struct vmstorage_channel_properties));
>  
> -	vmbus_open(new_sc,
> -		   storvsc_ringbuffer_size,
> -		   storvsc_ringbuffer_size,
> -		   (void *)&props,
> -		   sizeof(struct vmstorage_channel_properties),
> -		   storvsc_on_channel_callback, new_sc);
> +	ret = vmbus_open(new_sc,
> +			 storvsc_ringbuffer_size,
> +			 storvsc_ringbuffer_size,
> +			 (void *)&props,
> +			 sizeof(struct vmstorage_channel_properties),
> +			 storvsc_on_channel_callback, new_sc);
>  
> -	if (new_sc->state == CHANNEL_OPENED_STATE) {
> -		stor_device->stor_chns[new_sc->target_cpu] = new_sc;
> -		cpumask_set_cpu(new_sc->target_cpu, &stor_device->alloced_cpus);
> +	/* 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;
> +	cpumask_set_cpu(new_sc->target_cpu, &stor_device->alloced_cpus);
>  }
>  
>  static void  handle_multichannel_storage(struct hv_device *device, int max_chns)
>  {
> +	struct device *dev = &device->device;
>  	struct storvsc_device *stor_device;
>  	int num_cpus = num_online_cpus();
>  	int num_sc;
> @@ -679,21 +683,11 @@ static void  handle_multichannel_storage(struct hv_device *device, int max_chns)
>  	request = &stor_device->init_request;
>  	vstor_packet = &request->vstor_packet;
>  
> -	stor_device->open_sub_channel = true;
>  	/*
>  	 * Establish a handler for dealing with subchannels.
>  	 */
>  	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.
>  	 */
> @@ -710,23 +704,29 @@ static void  handle_multichannel_storage(struct hv_device *device, int max_chns)
>  			       VM_PKT_DATA_INBAND,
>  			       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;
> +	}
>  
>  	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;
> +	}
>  
>  	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;
> +	}
>  
>  	/*
> -	 * Now that we created the sub-channels, invoke the check; this
> -	 * may trigger the callback.
> +	 * We need to do nothing here, because vmbus_process_offer()
> +	 * 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,
> @@ -1794,7 +1794,6 @@ static int storvsc_probe(struct hv_device *device,
>  	}
>  
>  	stor_device->destroy = false;
> -	stor_device->open_sub_channel = false;
>  	init_waitqueue_head(&stor_device->waiting_to_drain);
>  	stor_device->device = device;
>  	stor_device->host = host;
>
diff mbox series

Patch

diff --git a/drivers/scsi/storvsc_drv.c b/drivers/scsi/storvsc_drv.c
index 883bff0ba3ce..57fb1b067b33 100644
--- a/drivers/scsi/storvsc_drv.c
+++ b/drivers/scsi/storvsc_drv.c
@@ -446,7 +446,6 @@  struct storvsc_device {
 
 	bool	 destroy;
 	bool	 drain_notify;
-	bool	 open_sub_channel;
 	atomic_t num_outstanding_req;
 	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)
 {
 	struct hv_device *device = new_sc->primary_channel->device_obj;
+	struct device *dev = &device->device;
 	struct storvsc_device *stor_device;
 	struct vmstorage_channel_properties props;
+	int ret;
 
 	stor_device = get_out_stor_device(device);
 	if (!stor_device)
 		return;
 
-	if (stor_device->open_sub_channel == false)
-		return;
-
 	memset(&props, 0, sizeof(struct vmstorage_channel_properties));
 
-	vmbus_open(new_sc,
-		   storvsc_ringbuffer_size,
-		   storvsc_ringbuffer_size,
-		   (void *)&props,
-		   sizeof(struct vmstorage_channel_properties),
-		   storvsc_on_channel_callback, new_sc);
+	ret = vmbus_open(new_sc,
+			 storvsc_ringbuffer_size,
+			 storvsc_ringbuffer_size,
+			 (void *)&props,
+			 sizeof(struct vmstorage_channel_properties),
+			 storvsc_on_channel_callback, new_sc);
 
-	if (new_sc->state == CHANNEL_OPENED_STATE) {
-		stor_device->stor_chns[new_sc->target_cpu] = new_sc;
-		cpumask_set_cpu(new_sc->target_cpu, &stor_device->alloced_cpus);
+	/* 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;
+	cpumask_set_cpu(new_sc->target_cpu, &stor_device->alloced_cpus);
 }
 
 static void  handle_multichannel_storage(struct hv_device *device, int max_chns)
 {
+	struct device *dev = &device->device;
 	struct storvsc_device *stor_device;
 	int num_cpus = num_online_cpus();
 	int num_sc;
@@ -679,21 +683,11 @@  static void  handle_multichannel_storage(struct hv_device *device, int max_chns)
 	request = &stor_device->init_request;
 	vstor_packet = &request->vstor_packet;
 
-	stor_device->open_sub_channel = true;
 	/*
 	 * Establish a handler for dealing with subchannels.
 	 */
 	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.
 	 */
@@ -710,23 +704,29 @@  static void  handle_multichannel_storage(struct hv_device *device, int max_chns)
 			       VM_PKT_DATA_INBAND,
 			       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;
+	}
 
 	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;
+	}
 
 	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;
+	}
 
 	/*
-	 * Now that we created the sub-channels, invoke the check; this
-	 * may trigger the callback.
+	 * We need to do nothing here, because vmbus_process_offer()
+	 * 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,
@@ -1794,7 +1794,6 @@  static int storvsc_probe(struct hv_device *device,
 	}
 
 	stor_device->destroy = false;
-	stor_device->open_sub_channel = false;
 	init_waitqueue_head(&stor_device->waiting_to_drain);
 	stor_device->device = device;
 	stor_device->host = host;