diff mbox

[2/3] ser_gigaset: fix deallocation of platform device structure

Message ID 83c4ab9bbca911aad62343154eabfa1af077b021.1449570042.git.tilman@imap.cc
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Tilman Schmidt Dec. 8, 2015, 11 a.m. UTC
When shutting down the device, the struct ser_cardstate must not be
kfree()d immediately after the call to platform_device_unregister()
since the embedded struct platform_device is still in use.
Move the kfree() call to the release method instead.

Signed-off-by: Tilman Schmidt <tilman@imap.cc>
Fixes: 2869b23e4b95 ("drivers/isdn/gigaset: new M101 driver (v2)")
Reported-by: Sasha Levin <sasha.levin@oracle.com>
---
 drivers/isdn/gigaset/ser-gigaset.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

Comments

Paul Bolle Dec. 8, 2015, 11:12 p.m. UTC | #1
Hi Tilman,

On di, 2015-12-08 at 12:00 +0100, Tilman Schmidt wrote:
> When shutting down the device, the struct ser_cardstate must not be
> kfree()d immediately after the call to platform_device_unregister()
> since the embedded struct platform_device is still in use.
> Move the kfree() call to the release method instead.
> 
> Signed-off-by: Tilman Schmidt <tilman@imap.cc>
> Fixes: 2869b23e4b95 ("drivers/isdn/gigaset: new M101 driver (v2)")
> Reported-by: Sasha Levin <sasha.levin@oracle.com>
> ---
>  drivers/isdn/gigaset/ser-gigaset.c | 10 +++++++---
>  1 file changed, 7 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/isdn/gigaset/ser-gigaset.c
> b/drivers/isdn/gigaset/ser-gigaset.c
> index d8771b5..2693cb2 100644
> --- a/drivers/isdn/gigaset/ser-gigaset.c
> +++ b/drivers/isdn/gigaset/ser-gigaset.c
> @@ -370,19 +370,23 @@ static void gigaset_freecshw(struct cardstate
> *cs)
>  	tasklet_kill(&cs->write_tasklet);
>  	if (!cs->hw.ser)
>  		return;
> -	dev_set_drvdata(&cs->hw.ser->dev.dev, NULL);
>  	platform_device_unregister(&cs->hw.ser->dev);
> -	kfree(cs->hw.ser);
> -	cs->hw.ser = NULL;
>  }
>  
>  static void gigaset_device_release(struct device *dev)
>  {
>  	struct platform_device *pdev = to_platform_device(dev);
> +	struct cardstate *cs = dev_get_drvdata(dev);
>  
>  	/* adapted from platform_device_release() in
> drivers/base/platform.c */
>  	kfree(dev->platform_data);
>  	kfree(pdev->resource);
> +
> +	if (!cs)
> +		return;
> +	dev_set_drvdata(dev, NULL);

dev equals cs->hw.ser->dev.dev, doesn't it? So what does setting
cs->hw.ser->dev.dev.driver_data to NULL just before freeing it buy us?

> +	kfree(cs->hw.ser);
> +	cs->hw.ser = NULL;

I might be missing something, but what does setting this to NULL buy us
here?

(I realize that I'm asking questions to code that isn't actually new but
only moved around, but I think that's still an opportunity to have
another look at that code.)

>  }
>  
>  /*

Thanks,


Paul Bolle
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Tilman Schmidt Dec. 9, 2015, 11:10 a.m. UTC | #2
Am 09.12.2015 um 00:12 schrieb Paul Bolle:

>> --- a/drivers/isdn/gigaset/ser-gigaset.c
>> +++ b/drivers/isdn/gigaset/ser-gigaset.c
>> @@ -370,19 +370,23 @@ static void gigaset_freecshw(struct cardstate
>> *cs)
>>  	tasklet_kill(&cs->write_tasklet);
>>  	if (!cs->hw.ser)
>>  		return;
>> -	dev_set_drvdata(&cs->hw.ser->dev.dev, NULL);
>>  	platform_device_unregister(&cs->hw.ser->dev);
>> -	kfree(cs->hw.ser);
>> -	cs->hw.ser = NULL;
>>  }
>>  
>>  static void gigaset_device_release(struct device *dev)
>>  {
>>  	struct platform_device *pdev = to_platform_device(dev);
>> +	struct cardstate *cs = dev_get_drvdata(dev);
>>  
>>  	/* adapted from platform_device_release() in
>> drivers/base/platform.c */
>>  	kfree(dev->platform_data);
>>  	kfree(pdev->resource);
>> +
>> +	if (!cs)
>> +		return;
>> +	dev_set_drvdata(dev, NULL);
> 
> dev equals cs->hw.ser->dev.dev, doesn't it?

Correct.

> So what does setting
> cs->hw.ser->dev.dev.driver_data to NULL just before freeing it buy us?

We're freeing cs->hw.ser, not cs->hw.ser->dev.
Clearing the reference to cs from the device structure before freeing cs
guards against possible use-after-free.

>> +	kfree(cs->hw.ser);
>> +	cs->hw.ser = NULL;
> 
> I might be missing something, but what does setting this to NULL buy us
> here?

Just defensive programming. Guarding against possible use-after-free or
double-free.

> 
> (I realize that I'm asking questions to code that isn't actually new but
> only moved around, but I think that's still an opportunity to have
> another look at that code.)

I'm a big fan of one change per patch. If we also want to modify the
moved code then that should be done in a separate patch. It makes
bisecting so much easier. Same reason why I separated out patch 3/3. And
btw same reason why I think patch 1/3 should go in as-is, as an obvious
fix to commit f34d7a5b, and any concerns about whether those tests are
useful should be addressed by a separate patch.

Regards,
Tilman
Paul Bolle Dec. 10, 2015, 11:20 a.m. UTC | #3
On wo, 2015-12-09 at 12:10 +0100, Tilman Schmidt wrote:
> Am 09.12.2015 um 00:12 schrieb Paul Bolle:

> > So what does setting
> > cs->hw.ser->dev.dev.driver_data to NULL just before freeing it buy
> > us?
> 
> We're freeing cs->hw.ser, not cs->hw.ser->dev.
> Clearing the reference to cs from the device structure before freeing 
> cs guards against possible use-after-free.
> 
> > > +	kfree(cs->hw.ser);
> > > +	cs->hw.ser = NULL;
> > 
> > I might be missing something, but what does setting this to NULL buy 
> > us here?
> 
> Just defensive programming. Guarding against possible use-after-free 
> or double-free.

I'm inclined to think this is not the best way to guard against such
nasty bugs. But then again, I'm only a few months into my shift of
looking after the gigaset drivers and haven't had to track down such
bugs yet. But I'd be surprised if many other drivers do it that way and
think this is a job for (tree wide) debugging tools. But, whatever the
merits of our views, we can defer this discussion to some future date.
See below.

> I'm a big fan of one change per patch. If we also want to modify the
> moved code then that should be done in a separate patch. It makes
> bisecting so much easier. Same reason why I separated out patch 3/3.


Fair enough.


Paul Bolle
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Peter Hurley Dec. 10, 2015, 2:04 p.m. UTC | #4
Hi Tilman,

On 12/09/2015 03:10 AM, Tilman Schmidt wrote:
> Am 09.12.2015 um 00:12 schrieb Paul Bolle:
> 
>>> --- a/drivers/isdn/gigaset/ser-gigaset.c
>>> +++ b/drivers/isdn/gigaset/ser-gigaset.c
>>> @@ -370,19 +370,23 @@ static void gigaset_freecshw(struct cardstate
>>> *cs)
>>>  	tasklet_kill(&cs->write_tasklet);
>>>  	if (!cs->hw.ser)
>>>  		return;
>>> -	dev_set_drvdata(&cs->hw.ser->dev.dev, NULL);
>>>  	platform_device_unregister(&cs->hw.ser->dev);
>>> -	kfree(cs->hw.ser);
>>> -	cs->hw.ser = NULL;
>>>  }
>>>  
>>>  static void gigaset_device_release(struct device *dev)
>>>  {
>>>  	struct platform_device *pdev = to_platform_device(dev);
>>> +	struct cardstate *cs = dev_get_drvdata(dev);
>>>  
>>>  	/* adapted from platform_device_release() in
>>> drivers/base/platform.c */
>>>  	kfree(dev->platform_data);
>>>  	kfree(pdev->resource);
>>> +
>>> +	if (!cs)
>>> +		return;
>>> +	dev_set_drvdata(dev, NULL);

This is of marginal value and (I think) unnecessary; it implies
the core will use the device after release, which would trigger
many problems if true.


>> dev equals cs->hw.ser->dev.dev, doesn't it?
> 
> Correct.
> 
>> So what does setting
>> cs->hw.ser->dev.dev.driver_data to NULL just before freeing it buy us?
> 
> We're freeing cs->hw.ser, not cs->hw.ser->dev.
> Clearing the reference to cs from the device structure before freeing cs
> guards against possible use-after-free.
> 
>>> +	kfree(cs->hw.ser);
>>> +	cs->hw.ser = NULL;

This pattern is common, and defends against much more common
driver bugs.

Unfortunately, much of the good this pattern is intended to do in finding
use-after-free bugs is undone by explicit tests for null everywhere else.
Not saying that's the case here; rather, generally speaking.

Like the
	if (!tty && !tty->ops && ....)

code.

Better just to let it crash.

Regards,
Peter Hurley


>> I might be missing something, but what does setting this to NULL buy us
>> here?
> 
> Just defensive programming. Guarding against possible use-after-free or
> double-free.
> 
>>
>> (I realize that I'm asking questions to code that isn't actually new but
>> only moved around, but I think that's still an opportunity to have
>> another look at that code.)
> 
> I'm a big fan of one change per patch. If we also want to modify the
> moved code then that should be done in a separate patch. It makes
> bisecting so much easier. Same reason why I separated out patch 3/3. And
> btw same reason why I think patch 1/3 should go in as-is, as an obvious
> fix to commit f34d7a5b, and any concerns about whether those tests are
> useful should be addressed by a separate patch.
> 
> Regards,
> Tilman
> 

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Tilman Schmidt Dec. 12, 2015, 5:52 p.m. UTC | #5
Hi Peter,

Am 10.12.2015 um 15:04 schrieb Peter Hurley:

>>>> --- a/drivers/isdn/gigaset/ser-gigaset.c
>>>> +++ b/drivers/isdn/gigaset/ser-gigaset.c
>>>> @@ -370,19 +370,23 @@ static void gigaset_freecshw(struct cardstate
>>>> *cs)
>>>>  	tasklet_kill(&cs->write_tasklet);
>>>>  	if (!cs->hw.ser)
>>>>  		return;
>>>> -	dev_set_drvdata(&cs->hw.ser->dev.dev, NULL);
>>>>  	platform_device_unregister(&cs->hw.ser->dev);
>>>> -	kfree(cs->hw.ser);
>>>> -	cs->hw.ser = NULL;
>>>>  }
>>>>  
>>>>  static void gigaset_device_release(struct device *dev)
>>>>  {
>>>>  	struct platform_device *pdev = to_platform_device(dev);
>>>> +	struct cardstate *cs = dev_get_drvdata(dev);
>>>>  
>>>>  	/* adapted from platform_device_release() in
>>>> drivers/base/platform.c */
>>>>  	kfree(dev->platform_data);
>>>>  	kfree(pdev->resource);
>>>> +
>>>> +	if (!cs)
>>>> +		return;
>>>> +	dev_set_drvdata(dev, NULL);
> 
> This is of marginal value and (I think) unnecessary; it implies
> the core will use the device after release, which would trigger
> many problems if true.

Agreed, but I'm just moving existing code here. Dropping the
dev_set_drvdata() call would be an unrelated change which should be done
in a separate patch if I understand the rules correctly.

Regards,
Tilman
diff mbox

Patch

diff --git a/drivers/isdn/gigaset/ser-gigaset.c b/drivers/isdn/gigaset/ser-gigaset.c
index d8771b5..2693cb2 100644
--- a/drivers/isdn/gigaset/ser-gigaset.c
+++ b/drivers/isdn/gigaset/ser-gigaset.c
@@ -370,19 +370,23 @@  static void gigaset_freecshw(struct cardstate *cs)
 	tasklet_kill(&cs->write_tasklet);
 	if (!cs->hw.ser)
 		return;
-	dev_set_drvdata(&cs->hw.ser->dev.dev, NULL);
 	platform_device_unregister(&cs->hw.ser->dev);
-	kfree(cs->hw.ser);
-	cs->hw.ser = NULL;
 }
 
 static void gigaset_device_release(struct device *dev)
 {
 	struct platform_device *pdev = to_platform_device(dev);
+	struct cardstate *cs = dev_get_drvdata(dev);
 
 	/* adapted from platform_device_release() in drivers/base/platform.c */
 	kfree(dev->platform_data);
 	kfree(pdev->resource);
+
+	if (!cs)
+		return;
+	dev_set_drvdata(dev, NULL);
+	kfree(cs->hw.ser);
+	cs->hw.ser = NULL;
 }
 
 /*