diff mbox series

[1/4] scsi: core: fix error handling of scsi_host_alloc

Message ID 20210602133029.2864069-2-ming.lei@redhat.com
State New
Headers show
Series scsi: fix failure handling of alloc/add host | expand

Commit Message

Ming Lei June 2, 2021, 1:30 p.m. UTC
After device is initialized via device_initialize(), or its name is
set via dev_set_name(), the device has to be freed via put_device(),
otherwise device name will be leaked because it is allocated
dynamically in dev_set_name().

Fixes the issue by replacing kfree(shost) via put_device(&shost->shost_gendev)
which can help to free dev_name(&shost->shost_dev) when host state is
in SHOST_CREATED. Meantime needn't to remove IDA and stop the kthread of
shost->ehandler in the error handling code.

Cc: Bart Van Assche <bvanassche@acm.org>
Cc: John Garry <john.garry@huawei.com>
Cc: Hannes Reinecke <hare@suse.de>
Signed-off-by: Ming Lei <ming.lei@redhat.com>
---
 drivers/scsi/hosts.c | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

Comments

Bart Van Assche June 3, 2021, 2:26 a.m. UTC | #1
On 6/2/21 6:30 AM, Ming Lei wrote:
> After device is initialized via device_initialize(), or its name is

> set via dev_set_name(), the device has to be freed via put_device(),

> otherwise device name will be leaked because it is allocated

> dynamically in dev_set_name().


dev_set_name() must be called after device_initialize() so I think the
reference to dev_set_name() can be left out from the above sentence.

>  	return shost;

> + fail:


Please leave a blank line above labels. Otherwise this patch looks good
to me hence:

Reviewed-by: Bart Van Assche <bvanassche@acm.org>
John Garry June 3, 2021, 3:40 p.m. UTC | #2
On 02/06/2021 14:30, Ming Lei wrote:
> After device is initialized via device_initialize(), or its name is

> set via dev_set_name(), the device has to be freed via put_device(),

> otherwise device name will be leaked because it is allocated

> dynamically in dev_set_name().

> 

> Fixes the issue by replacing kfree(shost) via put_device(&shost->shost_gendev)

> which can help to free dev_name(&shost->shost_dev) when host state is

> in SHOST_CREATED. Meantime needn't to remove IDA and stop the kthread of

> shost->ehandler in the error handling code.

> 

> Cc: Bart Van Assche<bvanassche@acm.org>

> Cc: John Garry<john.garry@huawei.com>

> Cc: Hannes Reinecke<hare@suse.de>

> Signed-off-by: Ming Lei<ming.lei@redhat.com>


Reviewed-by: John Garry <john.garry@huawei.com>
Hannes Reinecke June 7, 2021, 11:39 a.m. UTC | #3
On 6/2/21 3:30 PM, Ming Lei wrote:
> After device is initialized via device_initialize(), or its name is

> set via dev_set_name(), the device has to be freed via put_device(),

> otherwise device name will be leaked because it is allocated

> dynamically in dev_set_name().

> 

> Fixes the issue by replacing kfree(shost) via put_device(&shost->shost_gendev)

> which can help to free dev_name(&shost->shost_dev) when host state is

> in SHOST_CREATED. Meantime needn't to remove IDA and stop the kthread of

> shost->ehandler in the error handling code.

> 

> Cc: Bart Van Assche <bvanassche@acm.org>

> Cc: John Garry <john.garry@huawei.com>

> Cc: Hannes Reinecke <hare@suse.de>

> Signed-off-by: Ming Lei <ming.lei@redhat.com>

> ---

>  drivers/scsi/hosts.c | 23 +++++++++++++----------

>  1 file changed, 13 insertions(+), 10 deletions(-)

> 

Reviewed-by: Hannes Reinecke <hare@suse.de>


Cheers,

Hannes
-- 
Dr. Hannes Reinecke		        Kernel Storage Architect
hare@suse.de			               +49 911 74053 688
SUSE Software Solutions Germany GmbH, 90409 Nürnberg
GF: F. Imendörffer, HRB 36809 (AG Nürnberg)
Tyrel Datwyler June 29, 2021, 7:23 p.m. UTC | #4
On 6/2/21 6:30 AM, Ming Lei wrote:
> After device is initialized via device_initialize(), or its name is

> set via dev_set_name(), the device has to be freed via put_device(),

> otherwise device name will be leaked because it is allocated

> dynamically in dev_set_name().

> 

> Fixes the issue by replacing kfree(shost) via put_device(&shost->shost_gendev)

> which can help to free dev_name(&shost->shost_dev) when host state is

> in SHOST_CREATED. Meantime needn't to remove IDA and stop the kthread of

> shost->ehandler in the error handling code.


This statement is incorrect for kthread. If error handler thread failed to spawn
the value of shost->ehandler will be ERR_PTR(-ENOMEM) which will pass the "if
(shost->ehandler)" check in scsi_host_dev_release() resulting in a
kthread_stop() call for a non-existant kthread which triggers a bad pointer
dereference. Here is an example splat:

scsi host11: error handler thread failed to spawn, error = -4
Kernel attempted to read user page (10c) - exploit attempt? (uid: 0)
BUG: Kernel NULL pointer dereference on read at 0x0000010c
Faulting instruction address: 0xc00000000818e9a8
Oops: Kernel access of bad area, sig: 11 [#1]
LE PAGE_SIZE=64K MMU=Hash SMP NR_CPUS=2048 NUMA pSeries
Modules linked in: ibmvscsi(+) scsi_transport_srp dm_multipath dm_mirror dm_region
 hash dm_log dm_mod fuse overlay squashfs loop
CPU: 12 PID: 274 Comm: systemd-udevd Not tainted 5.13.0-rc7 #1
NIP:  c00000000818e9a8 LR: c0000000089846e8 CTR: 0000000000007ee8
REGS: c000000037d12ea0 TRAP: 0300   Not tainted  (5.13.0-rc7)
MSR:  800000000280b033 &lt;SF,VEC,VSX,EE,FP,ME,IR,DR,RI,LE&gt;  CR: 28228228
XER: 20040001
CFAR: c0000000089846e4 DAR: 000000000000010c DSISR: 40000000 IRQMASK: 0
GPR00: c0000000089846e8 c000000037d13140 c000000009cc1100 fffffffffffffffc
GPR04: 0000000000000001 0000000000000000 0000000000000000 c000000037dc0000
GPR08: 0000000000000000 c000000037dc0000 0000000000000001 00000000fffff7ff
GPR12: 0000000000008000 c00000000a049000 c000000037d13d00 000000011134d5a0
GPR16: 0000000000001740 c0080000190d0000 c0080000190d1740 c000000009129288
GPR20: c000000037d13bc0 0000000000000001 c000000037d13bc0 c0080000190b7898
GPR24: c0080000190b7708 0000000000000000 c000000033bb2c48 0000000000000000
GPR28: c000000046b28280 0000000000000000 000000000000010c fffffffffffffffc
NIP [c00000000818e9a8] kthread_stop+0x38/0x230
LR [c0000000089846e8] scsi_host_dev_release+0x98/0x160
Call Trace:
[c000000033bb2c48] 0xc000000033bb2c48 (unreliable)
[c0000000089846e8] scsi_host_dev_release+0x98/0x160
[c00000000891e960] device_release+0x60/0x100
[c0000000087e55c4] kobject_release+0x84/0x210
[c00000000891ec78] put_device+0x28/0x40
[c000000008984ea4] scsi_host_alloc+0x314/0x430
[c0080000190b38bc] ibmvscsi_probe+0x54/0xad0 [ibmvscsi]
[c000000008110104] vio_bus_probe+0xa4/0x4b0
[c00000000892a860] really_probe+0x140/0x680
[c00000000892aefc] driver_probe_device+0x15c/0x200
[c00000000892b63c] device_driver_attach+0xcc/0xe0
[c00000000892b740] __driver_attach+0xf0/0x200
[c000000008926f28] bus_for_each_dev+0xa8/0x130
[c000000008929ce4] driver_attach+0x34/0x50
[c000000008928fc0] bus_add_driver+0x1b0/0x300
[c00000000892c798] driver_register+0x98/0x1a0
[c00000000810eb60] __vio_register_driver+0x80/0xe0
[c0080000190b4a30] ibmvscsi_module_init+0x9c/0xdc [ibmvscsi]
[c0000000080121d0] do_one_initcall+0x60/0x2d0
[c000000008261abc] do_init_module+0x7c/0x320
[c000000008265700] load_module+0x2350/0x25b0
[c000000008265cb4] __do_sys_finit_module+0xd4/0x160
[c000000008031110] system_call_exception+0x150/0x2d0
[c00000000800d35c] system_call_common+0xec/0x278


I'm happy to send a fix, but I see two possible approaches.

1.) Set shost->ehandler = NULL if kthread_run() fails in scsi_host_alloc()

or

2.) Test that (shost->ehandler && !IS_ERR(shost->ehandler)) before calling
kthread_stop in scsi_host_dev_release()

-Tyrel

> 

> Cc: Bart Van Assche <bvanassche@acm.org>

> Cc: John Garry <john.garry@huawei.com>

> Cc: Hannes Reinecke <hare@suse.de>

> Signed-off-by: Ming Lei <ming.lei@redhat.com>

> ---

>  drivers/scsi/hosts.c | 23 +++++++++++++----------

>  1 file changed, 13 insertions(+), 10 deletions(-)

> 

> diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c

> index 624e2582c3df..25cf76e73595 100644

> --- a/drivers/scsi/hosts.c

> +++ b/drivers/scsi/hosts.c

> @@ -391,8 +391,10 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)

>  	mutex_init(&shost->scan_mutex);

>  

>  	index = ida_simple_get(&host_index_ida, 0, 0, GFP_KERNEL);

> -	if (index < 0)

> -		goto fail_kfree;

> +	if (index < 0) {

> +		kfree(shost);

> +		return NULL;

> +	}

>  	shost->host_no = index;

>  

>  	shost->dma_channel = 0xff;

> @@ -484,7 +486,7 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)

>  		shost_printk(KERN_WARNING, shost,

>  			"error handler thread failed to spawn, error = %ld\n",

>  			PTR_ERR(shost->ehandler));

> -		goto fail_index_remove;

> +		goto fail;

>  	}

>  

>  	shost->tmf_work_q = alloc_workqueue("scsi_tmf_%d",

> @@ -493,17 +495,18 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)

>  	if (!shost->tmf_work_q) {

>  		shost_printk(KERN_WARNING, shost,

>  			     "failed to create tmf workq\n");

> -		goto fail_kthread;

> +		goto fail;

>  	}

>  	scsi_proc_hostdir_add(shost->hostt);

>  	return shost;

> + fail:

> +	/*

> +	 * host state is still SHOST_CREATED, and it is enough to release

> +	 * ->shost_gendev since scsi_host_dev_release() can help to free

> +	 * dev_name(&shost->shost_dev)

> +	 */

> +	put_device(&shost->shost_gendev);

>  

> - fail_kthread:

> -	kthread_stop(shost->ehandler);

> - fail_index_remove:

> -	ida_simple_remove(&host_index_ida, shost->host_no);

> - fail_kfree:

> -	kfree(shost);

>  	return NULL;

>  }

>  EXPORT_SYMBOL(scsi_host_alloc);

>
Ming Lei June 30, 2021, 12:11 a.m. UTC | #5
On Tue, Jun 29, 2021 at 12:23:04PM -0700, Tyrel Datwyler wrote:
> On 6/2/21 6:30 AM, Ming Lei wrote:

> > After device is initialized via device_initialize(), or its name is

> > set via dev_set_name(), the device has to be freed via put_device(),

> > otherwise device name will be leaked because it is allocated

> > dynamically in dev_set_name().

> > 

> > Fixes the issue by replacing kfree(shost) via put_device(&shost->shost_gendev)

> > which can help to free dev_name(&shost->shost_dev) when host state is

> > in SHOST_CREATED. Meantime needn't to remove IDA and stop the kthread of

> > shost->ehandler in the error handling code.

> 

> This statement is incorrect for kthread. If error handler thread failed to spawn

> the value of shost->ehandler will be ERR_PTR(-ENOMEM) which will pass the "if

> (shost->ehandler)" check in scsi_host_dev_release() resulting in a

> kthread_stop() call for a non-existant kthread which triggers a bad pointer

> dereference. Here is an example splat:

> 

> scsi host11: error handler thread failed to spawn, error = -4

> Kernel attempted to read user page (10c) - exploit attempt? (uid: 0)

> BUG: Kernel NULL pointer dereference on read at 0x0000010c

> Faulting instruction address: 0xc00000000818e9a8

> Oops: Kernel access of bad area, sig: 11 [#1]

> LE PAGE_SIZE=64K MMU=Hash SMP NR_CPUS=2048 NUMA pSeries

> Modules linked in: ibmvscsi(+) scsi_transport_srp dm_multipath dm_mirror dm_region

>  hash dm_log dm_mod fuse overlay squashfs loop

> CPU: 12 PID: 274 Comm: systemd-udevd Not tainted 5.13.0-rc7 #1

> NIP:  c00000000818e9a8 LR: c0000000089846e8 CTR: 0000000000007ee8

> REGS: c000000037d12ea0 TRAP: 0300   Not tainted  (5.13.0-rc7)

> MSR:  800000000280b033 &lt;SF,VEC,VSX,EE,FP,ME,IR,DR,RI,LE&gt;  CR: 28228228

> XER: 20040001

> CFAR: c0000000089846e4 DAR: 000000000000010c DSISR: 40000000 IRQMASK: 0

> GPR00: c0000000089846e8 c000000037d13140 c000000009cc1100 fffffffffffffffc

> GPR04: 0000000000000001 0000000000000000 0000000000000000 c000000037dc0000

> GPR08: 0000000000000000 c000000037dc0000 0000000000000001 00000000fffff7ff

> GPR12: 0000000000008000 c00000000a049000 c000000037d13d00 000000011134d5a0

> GPR16: 0000000000001740 c0080000190d0000 c0080000190d1740 c000000009129288

> GPR20: c000000037d13bc0 0000000000000001 c000000037d13bc0 c0080000190b7898

> GPR24: c0080000190b7708 0000000000000000 c000000033bb2c48 0000000000000000

> GPR28: c000000046b28280 0000000000000000 000000000000010c fffffffffffffffc

> NIP [c00000000818e9a8] kthread_stop+0x38/0x230

> LR [c0000000089846e8] scsi_host_dev_release+0x98/0x160

> Call Trace:

> [c000000033bb2c48] 0xc000000033bb2c48 (unreliable)

> [c0000000089846e8] scsi_host_dev_release+0x98/0x160

> [c00000000891e960] device_release+0x60/0x100

> [c0000000087e55c4] kobject_release+0x84/0x210

> [c00000000891ec78] put_device+0x28/0x40

> [c000000008984ea4] scsi_host_alloc+0x314/0x430

> [c0080000190b38bc] ibmvscsi_probe+0x54/0xad0 [ibmvscsi]

> [c000000008110104] vio_bus_probe+0xa4/0x4b0

> [c00000000892a860] really_probe+0x140/0x680

> [c00000000892aefc] driver_probe_device+0x15c/0x200

> [c00000000892b63c] device_driver_attach+0xcc/0xe0

> [c00000000892b740] __driver_attach+0xf0/0x200

> [c000000008926f28] bus_for_each_dev+0xa8/0x130

> [c000000008929ce4] driver_attach+0x34/0x50

> [c000000008928fc0] bus_add_driver+0x1b0/0x300

> [c00000000892c798] driver_register+0x98/0x1a0

> [c00000000810eb60] __vio_register_driver+0x80/0xe0

> [c0080000190b4a30] ibmvscsi_module_init+0x9c/0xdc [ibmvscsi]

> [c0000000080121d0] do_one_initcall+0x60/0x2d0

> [c000000008261abc] do_init_module+0x7c/0x320

> [c000000008265700] load_module+0x2350/0x25b0

> [c000000008265cb4] __do_sys_finit_module+0xd4/0x160

> [c000000008031110] system_call_exception+0x150/0x2d0

> [c00000000800d35c] system_call_common+0xec/0x278

> 

> 

> I'm happy to send a fix, but I see two possible approaches.

> 

> 1.) Set shost->ehandler = NULL if kthread_run() fails in scsi_host_alloc()

> 

> or

> 

> 2.) Test that (shost->ehandler && !IS_ERR(shost->ehandler)) before calling

> kthread_stop in scsi_host_dev_release()


Either one looks fine for me, please go ahead to make a patch, and thanks for
the catch!

-- 
Ming
diff mbox series

Patch

diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index 624e2582c3df..25cf76e73595 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -391,8 +391,10 @@  struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
 	mutex_init(&shost->scan_mutex);
 
 	index = ida_simple_get(&host_index_ida, 0, 0, GFP_KERNEL);
-	if (index < 0)
-		goto fail_kfree;
+	if (index < 0) {
+		kfree(shost);
+		return NULL;
+	}
 	shost->host_no = index;
 
 	shost->dma_channel = 0xff;
@@ -484,7 +486,7 @@  struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
 		shost_printk(KERN_WARNING, shost,
 			"error handler thread failed to spawn, error = %ld\n",
 			PTR_ERR(shost->ehandler));
-		goto fail_index_remove;
+		goto fail;
 	}
 
 	shost->tmf_work_q = alloc_workqueue("scsi_tmf_%d",
@@ -493,17 +495,18 @@  struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
 	if (!shost->tmf_work_q) {
 		shost_printk(KERN_WARNING, shost,
 			     "failed to create tmf workq\n");
-		goto fail_kthread;
+		goto fail;
 	}
 	scsi_proc_hostdir_add(shost->hostt);
 	return shost;
+ fail:
+	/*
+	 * host state is still SHOST_CREATED, and it is enough to release
+	 * ->shost_gendev since scsi_host_dev_release() can help to free
+	 * dev_name(&shost->shost_dev)
+	 */
+	put_device(&shost->shost_gendev);
 
- fail_kthread:
-	kthread_stop(shost->ehandler);
- fail_index_remove:
-	ida_simple_remove(&host_index_ida, shost->host_no);
- fail_kfree:
-	kfree(shost);
 	return NULL;
 }
 EXPORT_SYMBOL(scsi_host_alloc);