Fix memory leak in qcserial driver
Subsystems (2)
PATCH 1/1 Fix memory leak in qcserial driver
Hi,
I've been experimenting with kmemleak and noticed a recurring leak warning whenever I load & unload the qcserial driver.
Inspection of the code seems to indicate the leak is the serial->private data allocated in the qcprobe() function, which is never freed (apart from in some of the qcprobe error paths) as far as I can tell.
The patch below fixes the following problems:
1 - Always free the serial->private data allocated in qcprobe, added a new function qc_release which frees the memory rather than relying on the usb_wwan_release function which does not. Without this cleanup, we leak the memory allocated in qcprobe when the kfree(serial) happens in usb-serial.c::destroy_serial()
2 - Ensure that the serial->private data is freed in the event of the probe failing and returning -ENODEV. This error-path leak is less likely but still possible, it was discussed previously but not actually fixed AFAICS:
http://kerneltrap.org/mailarchive/linux-kernel/2010/6/14/4582980/thread,
http://lkml.org/lkml/2010/6/20/234
3 - Don't assign serial->private when doing the kzalloc or serial->private ends up pointing to freed memory in the event we return -ENODEV, instead call usb_set_serial_data(serial, data) at the end of the function, and only have one return.
Please keep me on CC for any responses or review comments, since I'm not currently subscribed to LKML
drivers/usb/serial/qcserial.c +23 -7
@@ -111,7 +111,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) ifnum = intf->desc.bInterfaceNumber;dbg ("This Interface = %d" , ifnum); - data = serial->private =kzalloc (sizeof (struct usb_wwan_intf_private ), + data =kzalloc (sizeof (struct usb_wwan_intf_private ),GFP_KERNEL );if (!data)return -ENOMEM ;
@@ -134,8 +134,10 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {dbg ("QDL port found" ); -if (serial->interface->num_altsetting ==1 ) -return 0 ; +if (serial->interface->num_altsetting ==1 ) { + retval =0 ;/* Success */ +break ; + } retval =usb_set_interface (serial->dev, ifnum,1 );if (retval <0 ) {
@@ -145,7 +147,6 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) retval = -ENODEV ;kfree (data); } -return retval; }break ;
@@ -166,6 +167,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)"Could not set interface, error %d\n" , retval); retval = -ENODEV ; +kfree (data); } }else if (ifnum ==2 ) {dbg ("Modem port found" );
@@ -177,7 +179,6 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) retval = -ENODEV ;kfree (data); } -return retval; }else if (ifnum==3 ) {/* * NMEA (serial line 9600 8N1)
@@ -191,6 +192,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)"Could not set interface, error %d\n" , retval); retval = -ENODEV ; +kfree (data); } }break ;
@@ -198,13 +200,27 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)default :dev_err (&serial->dev->dev,"unknown number of interfaces: %d\n" , nintf); + retval = -ENODEV ;kfree (data); -return -ENODEV ; } +/* Set serial->private may be null if -ENODEV */ +usb_set_serial_data (serial, data);return retval; } +static void qc_release (struct usb_serial *serial) +{ +struct usb_wwan_intf_private *priv =usb_get_serial_data (serial); + +dbg ("%s" , __func__); + +/* Call usb_wwan release & free the private data allocated in qcprobe */ +usb_wwan_release (serial); +usb_set_serial_data (serial,NULL ); +kfree (priv); +} +static struct usb_serial_driver qcdevice = { .driver = { .owner =THIS_MODULE ,
@@ -222,7 +238,7 @@ static struct usb_serial_driver qcdevice = { .chars_in_buffer = usb_wwan_chars_in_buffer, .attach = usb_wwan_startup, .disconnect = usb_wwan_disconnect, - .release = usb_wwan_release, + .release = qc_release, #ifdef CONFIG_PM .suspend = usb_wwan_suspend, .resume = usb_wwan_resume,
Quoted 8 lines · Steven Hardy
Hi,
I've been experimenting with kmemleak and noticed a recurring leak warning whenever I load & unload the qcserial driver.
Inspection of the code seems to indicate the leak is the
serial->privatedata allocated in theqcprobe()function, which is never freed (apart from in some of the qcprobe error paths) as far as I can tell.The patch below fixes the following problems:
<snip>
Can you fix your email client to properly wrap your lines?
Also, as you do 3 different things here, can you break this into 3 patches and resend them?
thanks,
greg k-h
PATCH 1/3 Resend : Fix memory leak in qcserial driver
Quoted 5 lines · Greg KH
Quoted 4 lines · Steven Hardy
Hi,
I've been experimenting with kmemleak and noticed a recurring leak warning whenever I load & unload the qcserial driver.
Quoted 8 lines
Quoted 4 lines
Inspection of the code seems to indicate the leak is the
serial->privatedata allocated in theqcprobe()function, which is never freed (apart from in some of the qcprobe error paths) as far as I can tell.The patch below fixes the following problems:
<snip>
Can you fix your email client to properly wrap your lines?
Apologies, was trying to avoid mangling the patch & ended up mangling the preamble instead, back to mutt now which will hopefully do the right thing! :)
Quoted 2 lines
Also, as you do 3 different things here, can you break this into 3 patches and resend them?
Done, please find patch 1 of 3 below:
The patch below fixes the following problem:
1 - Always free the serial->private data allocated in qcprobe, added a new function qc_release which frees the memory rather than relying on the usb_wwan_release function which does not. Without this cleanup, we leak the memory allocated in qcprobe when the kfree(serial) happens in usb-serial.c::destroy_serial()
Please keep me on CC for any responses or review comments, since I'm not currently subscribed to LKML
drivers/usb/serial/qcserial.c +13 -1
@@ -205,6 +205,18 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)return retval; } +static void qc_release (struct usb_serial *serial) +{ +struct usb_wwan_intf_private *priv =usb_get_serial_data (serial); + +dbg ("%s" , __func__); + +/* Call usb_wwan release & free the private data allocated in qcprobe */ +usb_wwan_release (serial); +usb_set_serial_data (serial,NULL ); +kfree (priv); +} +static struct usb_serial_driver qcdevice = { .driver = { .owner =THIS_MODULE ,
@@ -222,7 +234,7 @@ static struct usb_serial_driver qcdevice = { .chars_in_buffer = usb_wwan_chars_in_buffer, .attach = usb_wwan_startup, .disconnect = usb_wwan_disconnect, - .release = usb_wwan_release, + .release = qc_release, #ifdef CONFIG_PM .suspend = usb_wwan_suspend, .resume = usb_wwan_resume,
PATCH 2/3 Resend : Fix memory leak in qcserial driver
In reply to Greg KHPatch 2 of 3, addresses the problem where serial->private can end up pointing to freed memory in the event qcprobe fails & returns -ENODEV
Original description:
3 - Don't assign serial->private when doing the kzalloc or serial->private ends up pointing to freed memory in the event we return -ENODEV, instead call usb_set_serial_data(serial, data) at the end of the function, and only have one return-point.
drivers/usb/serial/qcserial.c +8 -6
@@ -111,7 +111,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) ifnum = intf->desc.bInterfaceNumber;dbg ("This Interface = %d" , ifnum); - data = serial->private =kzalloc (sizeof (struct usb_wwan_intf_private ), + data =kzalloc (sizeof (struct usb_wwan_intf_private ),GFP_KERNEL );if (!data)return -ENOMEM ;
@@ -134,8 +134,10 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {dbg ("QDL port found" ); -if (serial->interface->num_altsetting ==1 ) -return 0 ; +if (serial->interface->num_altsetting ==1 ) { + retval =0 ;/* Success */ +break ; + } retval =usb_set_interface (serial->dev, ifnum,1 );if (retval <0 ) {
@@ -145,7 +147,6 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) retval = -ENODEV ;kfree (data); } -return retval; }break ;
@@ -177,7 +178,6 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) retval = -ENODEV ;kfree (data); } -return retval; }else if (ifnum==3 ) {/* * NMEA (serial line 9600 8N1)
@@ -199,9 +199,11 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)dev_err (&serial->dev->dev,"unknown number of interfaces: %d\n" , nintf);kfree (data); -return -ENODEV ; + retval = -ENODEV ; } +/* Set serial->private may be null if -ENODEV */ +usb_set_serial_data (serial, data);return retval; }
Re: [GIT PATCH 2/3] Resend : Fix memory leak in qcserial driver
Hello.
On 28-03-2011 21:38, Steven Hardy wrote:
Posting all 3 patches having the same sucject is not a good idea.
Quoted 1 line
Patch 2 of 3, addresses the problem where
serial->privatecan end up pointing to freed memory in the event qcprobe fails & returns-ENODEV
You still didn't wrap the changelog text properly.
Quoted 2 lines
Original description:
3 - Don't assignserial->privatewhen doing the kzalloc orserial->privateends up pointing to freed memory in the event we return-ENODEV, instead callusb_set_serial_data(serial, data)at the end of the function, and only have one return-point.
WBR, Sergei
PATCH 2/3 Resend : Fix memory leak in qcserial driver
In reply to Greg KHSecond draft of patch #2, this addresses the review comments made by Aristeu Rozanski, now usb_set_serial_data() is only called when we are not returning -ENODEV
Original description:
Don't assign serial->private when doing the kzalloc or serial->private ends up pointing to freed memory in the event we return -ENODEV, instead call usb_set_serial_data(serial, data) at the end of the function, and only have one return-point.
drivers/usb/serial/qcserial.c +9 -6
@@ -111,7 +111,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) ifnum = intf->desc.bInterfaceNumber;dbg ("This Interface = %d" , ifnum); - data = serial->private =kzalloc (sizeof (struct usb_wwan_intf_private ), + data =kzalloc (sizeof (struct usb_wwan_intf_private ),GFP_KERNEL );if (!data)return -ENOMEM ;
@@ -134,8 +134,10 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {dbg ("QDL port found" ); -if (serial->interface->num_altsetting ==1 ) -return 0 ; +if (serial->interface->num_altsetting ==1 ) { + retval =0 ;/* Success */ +break ; + } retval =usb_set_interface (serial->dev, ifnum,1 );if (retval <0 ) {
@@ -145,7 +147,6 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) retval = -ENODEV ;kfree (data); } -return retval; }break ;
@@ -177,7 +178,6 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) retval = -ENODEV ;kfree (data); } -return retval; }else if (ifnum==3 ) {/* * NMEA (serial line 9600 8N1)
@@ -199,9 +199,12 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)dev_err (&serial->dev->dev,"unknown number of interfaces: %d\n" , nintf);kfree (data); -return -ENODEV ; + retval = -ENODEV ; } +/* Set serial->private if not returning -ENODEV */ +if ( retval != -ENODEV ) +usb_set_serial_data (serial, data);return retval; }
Re: [GIT PATCH 2/3 (2nd draft)] Resend : Fix memory leak in qcserial driver
Hello.
Quoted 1 line · Steven Hardy
Second draft of patch #2, this addresses the review comments made by Aristeu Rozanski, now
usb_set_serial_data()is only called when we are not returning-ENODEV
Such comments should be put under --- tearline (which should appear under
your signoff). Otherwise Greg will have to hand edit them out of your patch.
Quoted 1 line
Original description:
Omit that please.
Quoted 1 line
Don't assign
serial->privatewhen doing the kzalloc orserial->privateends up pointing to freed memory in the event we return-ENODEV, instead callusb_set_serial_data(serial, data)at the end of the function, and only have one return-point.
Still not properly wrapped.
Quoted 7 lines
Signed-off-by Steve Hardy<[email protected]>drivers/usb/serial/qcserial.c +0
-0
[...]
drivers/usb/serial/qcserial.c static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) · line 206
Quoted 10 lines
drivers/usb/serial/qcserial.c +3
-1@@ -199,9 +199,12 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)dev_err (&serial->dev->dev,"unknown number of interfaces: %d\n" , nintf);kfree (data); -return -ENODEV ; + retval = -ENODEV ; } +/* Set serial->private if not returning -ENODEV */ +if ( retval != -ENODEV )
No spaces after ( and before ) allowed.
WBR, Sergei
PATCH 1/3 usb: Fix qcserial memory leak on rmmod
qcprobe function allocates serial->private but this is never freed, this patch adds a new function qc_release() which frees serial->private, after calling usb_wwan_release
---
Repost of qcserial patches posted last week, hopefully addresses review comments and email-patch-format issues
drivers/usb/serial/qcserial.c | 14 +++++++++++++- 1 files changed, 13 insertions(+), 1 deletions(-)
drivers/usb/serial/qcserial.c +13 -1
@@ -205,6 +205,18 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)return retval; } +static void qc_release (struct usb_serial *serial) +{ +struct usb_wwan_intf_private *priv =usb_get_serial_data (serial); + +dbg ("%s" , __func__); + +/* Call usb_wwan release & free the private data allocated in qcprobe */ +usb_wwan_release (serial); +usb_set_serial_data (serial,NULL ); +kfree (priv); +} +static struct usb_serial_driver qcdevice = { .driver = { .owner =THIS_MODULE ,
@@ -222,7 +234,7 @@ static struct usb_serial_driver qcdevice = { .chars_in_buffer = usb_wwan_chars_in_buffer, .attach = usb_wwan_startup, .disconnect = usb_wwan_disconnect, - .release = usb_wwan_release, + .release = qc_release, #ifdef CONFIG_PM .suspend = usb_wwan_suspend, .resume = usb_wwan_resume,
Signature · 1 line
1.7.1
PATCH 2/3 usb: qcserial avoid pointing to freed memory
Rework the qcprobe logic such that serial->private is not set when qcprobe exits with -ENODEV, otherwise serial->private will point to freed memory on -ENODEV
---
Repost of qcserial patches posted last week, hopefully addresses review comments and email-patch-format issues
drivers/usb/serial/qcserial.c | 15 +++++++++------ 1 files changed, 9 insertions(+), 6 deletions(-)
drivers/usb/serial/qcserial.c +9 -6
@@ -111,7 +111,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) ifnum = intf->desc.bInterfaceNumber;dbg ("This Interface = %d" , ifnum); - data = serial->private =kzalloc (sizeof (struct usb_wwan_intf_private ), + data =kzalloc (sizeof (struct usb_wwan_intf_private ),GFP_KERNEL );if (!data)return -ENOMEM ;
@@ -134,8 +134,10 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {dbg ("QDL port found" ); -if (serial->interface->num_altsetting ==1 ) -return 0 ; +if (serial->interface->num_altsetting ==1 ) { + retval =0 ;/* Success */ +break ; + } retval =usb_set_interface (serial->dev, ifnum,1 );if (retval <0 ) {
@@ -145,7 +147,6 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) retval = -ENODEV ;kfree (data); } -return retval; }break ;
@@ -177,7 +178,6 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) retval = -ENODEV ;kfree (data); } -return retval; }else if (ifnum==3 ) {/* * NMEA (serial line 9600 8N1)
@@ -199,9 +199,12 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)dev_err (&serial->dev->dev,"unknown number of interfaces: %d\n" , nintf);kfree (data); -return -ENODEV ; + retval = -ENODEV ; } +/* Set serial->private if not returning -ENODEV */ +if (retval != -ENODEV ) +usb_set_serial_data (serial, data);return retval; }
Signature · 1 line
1.7.1
PATCH 3/3 usb: qcserial add missing errorpath kfrees
In reply to Steven HardyThere are two -ENODEV error paths in qcprobe where the allocated private data is not freed, this patch adds the two missing kfrees to avoid leaking memory on the error path
---
Repost of qcserial patches posted last week, hopefully addresses review comments and email-patch-format issues
drivers/usb/serial/qcserial.c | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)
drivers/usb/serial/qcserial.c +2 -0
@@ -167,6 +167,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)"Could not set interface, error %d\n" , retval); retval = -ENODEV ; +kfree (data); } }else if (ifnum ==2 ) {dbg ("Modem port found" );
@@ -191,6 +192,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)"Could not set interface, error %d\n" , retval); retval = -ENODEV ; +kfree (data); } }break ;
Signature · 1 line
1.7.1
Re: [GIT PATCH 2/3 (2nd draft)] Resend : Fix memory leak in qcserial driver
In reply to Steven Hardydrivers/usb/serial/qcserial.c static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) · line 206
Quoted 2 lines · Steven Hardy
drivers/usb/serial/qcserial.c +2
-0+/* Set serial->private if not returning -ENODEV */ +if ( retval != -ENODEV )
^ ^
coding style
drivers/usb/serial/qcserial.c static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) · line 209
Quoted 3 lines
drivers/usb/serial/qcserial.c +1
-0+usb_set_serial_data (serial, data);return retval; }
the rest looks good to me.
Signature · 1 line
Aristeu
PATCH 3/3 Resend : Fix memory leak in qcserial driver
In reply to Greg KHPatch 3 of 3, this simply adds a couple of missing free's in the error path:
Original description:
2 - Ensure that the serial->private data is freed in the event of the probe failing and returning -ENODEV. This error-path leak is less likely but still possible, it was discussed previously but not actually fixed AFAICS: http://kerneltrap.org/mailarchive/linux-kernel/2010/6/14/4582980/thread, http://lkml.org/lkml/2010/6/20/234
drivers/usb/serial/qcserial.c +2 -0
@@ -167,6 +167,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)"Could not set interface, error %d\n" , retval); retval = -ENODEV ; +kfree (data); } }else if (ifnum ==2 ) {dbg ("Modem port found" );
@@ -191,6 +192,7 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)"Could not set interface, error %d\n" , retval); retval = -ENODEV ; +kfree (data); } }break ;
drivers/usb/serial/qcserial.c static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) · line 210
Quoted 13 lines · Steven Hardy
drivers/usb/serial/qcserial.c +3
-1@@ -198,13 +200,27 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)default :dev_err (&serial->dev->dev,"unknown number of interfaces: %d\n" , nintf); + retval = -ENODEV ;kfree (data); -return -ENODEV ; } +/* Set serial->private may be null if -ENODEV */ +usb_set_serial_data (serial, data);return retval; }
when there's a -ENODEV, kfree() is called but the now invalid address is set by usb_set_serial_data() anyway. am I missing something here?
Signature · 1 line
Aristeu
Quoted 5 lines · Aristeu Rozanski
drivers/usb/serial/qcserial.c static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) · line 210
Quoted 3 lines
drivers/usb/serial/qcserial.c +1
-0+usb_set_serial_data (serial, data);return retval; }when there's a
-ENODEV,kfree()is called but the now invalid address is set byusb_set_serial_data()anyway. am I missing something here?
The intention here is that since data should be NULL after the kfree, we're simply setting serial->private to NULL.
I can add a conditional to avoid calling usb_set_serial_data() for -ENODEV if it's deemed cleaner or more readable.
Thanks for the comments,
Steve
Quoted 8 lines · Steven Hardy
Quoted 5 lines · Aristeu Rozanski
drivers/usb/serial/qcserial.c static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) · line 210
Quoted 3 lines
drivers/usb/serial/qcserial.c +1
-0+usb_set_serial_data (serial, data);return retval; }when there's a
-ENODEV,kfree()is called but the now invalid address is set byusb_set_serial_data()anyway. am I missing something here?The intention here is that since data should be NULL after the kfree, we're simply setting
serial->privateto NULL.
kfree(data) won't make data == NULL. it'll free whatever address data has. data will still have the same (now stale) address. you need to explicitely make data = NULL if you want that behavior.
Signature · 1 line
Aristeu
Quoted 3 lines · Aristeu Rozanski
kfree(data) won't make data == NULL. it'll free whatever address data has. data will still have the same (now stale) address. you need to explicitely make data = NULL if you want that behavior.
Of course, sorry I should have remembered that, too much scripting lately's caused some brain-rot :)
I will resend a revised patch.
Thanks,
Steve