USB: keyspan: fix NULL-pointer dereferences and memory leaks
Fix NULL-pointer dereference at release by moving port data allocation
and deallocation to port_probe and port_remove.
Fix NULL-pointer dereference at disconnect by stopping port urbs at
port_remove.
Since commit 0998d06310 (device-core: Ensure drvdata = NULL when no
driver is bound) the port private data is no longer accessible at
disconnect or release.
Note that this patch also fixes port and interface-data memory leaks in
the error path of attach should port initialisation fail for any port.
Compile-only tested.
Cc: <stable@vger.kernel.org>
Signed-off-by: Johan Hovold <jhovold@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
	
	
This commit is contained in:
		
					parent
					
						
							
								5260e458f5
							
						
					
				
			
			
				commit
				
					
						f79b2d0fe8
					
				
			
		
					 2 changed files with 99 additions and 96 deletions
				
			
		|  | @ -1374,13 +1374,9 @@ static struct callbacks { | ||||||
| 	   data in device_details */ | 	   data in device_details */ | ||||||
| static void keyspan_setup_urbs(struct usb_serial *serial) | static void keyspan_setup_urbs(struct usb_serial *serial) | ||||||
| { | { | ||||||
| 	int				i, j; |  | ||||||
| 	struct keyspan_serial_private 	*s_priv; | 	struct keyspan_serial_private 	*s_priv; | ||||||
| 	const struct keyspan_device_details	*d_details; | 	const struct keyspan_device_details	*d_details; | ||||||
| 	struct usb_serial_port		*port; |  | ||||||
| 	struct keyspan_port_private	*p_priv; |  | ||||||
| 	struct callbacks		*cback; | 	struct callbacks		*cback; | ||||||
| 	int				endp; |  | ||||||
| 
 | 
 | ||||||
| 	s_priv = usb_get_serial_data(serial); | 	s_priv = usb_get_serial_data(serial); | ||||||
| 	d_details = s_priv->device_details; | 	d_details = s_priv->device_details; | ||||||
|  | @ -1404,45 +1400,6 @@ static void keyspan_setup_urbs(struct usb_serial *serial) | ||||||
| 		(serial, d_details->glocont_endpoint, USB_DIR_OUT, | 		(serial, d_details->glocont_endpoint, USB_DIR_OUT, | ||||||
| 		 serial, s_priv->glocont_buf, GLOCONT_BUFLEN, | 		 serial, s_priv->glocont_buf, GLOCONT_BUFLEN, | ||||||
| 		 cback->glocont_callback); | 		 cback->glocont_callback); | ||||||
| 
 |  | ||||||
| 	/* Setup endpoints for each port specific thing */ |  | ||||||
| 	for (i = 0; i < d_details->num_ports; i++) { |  | ||||||
| 		port = serial->port[i]; |  | ||||||
| 		p_priv = usb_get_serial_port_data(port); |  | ||||||
| 
 |  | ||||||
| 		/* Do indat endpoints first, once for each flip */ |  | ||||||
| 		endp = d_details->indat_endpoints[i]; |  | ||||||
| 		for (j = 0; j <= d_details->indat_endp_flip; ++j, ++endp) { |  | ||||||
| 			p_priv->in_urbs[j] = keyspan_setup_urb |  | ||||||
| 				(serial, endp, USB_DIR_IN, port, |  | ||||||
| 				 p_priv->in_buffer[j], 64, |  | ||||||
| 				 cback->indat_callback); |  | ||||||
| 		} |  | ||||||
| 		for (; j < 2; ++j) |  | ||||||
| 			p_priv->in_urbs[j] = NULL; |  | ||||||
| 
 |  | ||||||
| 		/* outdat endpoints also have flip */ |  | ||||||
| 		endp = d_details->outdat_endpoints[i]; |  | ||||||
| 		for (j = 0; j <= d_details->outdat_endp_flip; ++j, ++endp) { |  | ||||||
| 			p_priv->out_urbs[j] = keyspan_setup_urb |  | ||||||
| 				(serial, endp, USB_DIR_OUT, port, |  | ||||||
| 				 p_priv->out_buffer[j], 64, |  | ||||||
| 				 cback->outdat_callback); |  | ||||||
| 		} |  | ||||||
| 		for (; j < 2; ++j) |  | ||||||
| 			p_priv->out_urbs[j] = NULL; |  | ||||||
| 
 |  | ||||||
| 		/* inack endpoint */ |  | ||||||
| 		p_priv->inack_urb = keyspan_setup_urb |  | ||||||
| 			(serial, d_details->inack_endpoints[i], USB_DIR_IN, |  | ||||||
| 			 port, p_priv->inack_buffer, 1, cback->inack_callback); |  | ||||||
| 
 |  | ||||||
| 		/* outcont endpoint */ |  | ||||||
| 		p_priv->outcont_urb = keyspan_setup_urb |  | ||||||
| 			(serial, d_details->outcont_endpoints[i], USB_DIR_OUT, |  | ||||||
| 			 port, p_priv->outcont_buffer, 64, |  | ||||||
| 			 cback->outcont_callback); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* usa19 function doesn't require prescaler */ | /* usa19 function doesn't require prescaler */ | ||||||
|  | @ -2407,9 +2364,7 @@ static void keyspan_send_setup(struct usb_serial_port *port, int reset_port) | ||||||
| static int keyspan_startup(struct usb_serial *serial) | static int keyspan_startup(struct usb_serial *serial) | ||||||
| { | { | ||||||
| 	int				i, err; | 	int				i, err; | ||||||
| 	struct usb_serial_port		*port; |  | ||||||
| 	struct keyspan_serial_private 	*s_priv; | 	struct keyspan_serial_private 	*s_priv; | ||||||
| 	struct keyspan_port_private	*p_priv; |  | ||||||
| 	const struct keyspan_device_details	*d_details; | 	const struct keyspan_device_details	*d_details; | ||||||
| 
 | 
 | ||||||
| 	for (i = 0; (d_details = keyspan_devices[i]) != NULL; ++i) | 	for (i = 0; (d_details = keyspan_devices[i]) != NULL; ++i) | ||||||
|  | @ -2432,19 +2387,6 @@ static int keyspan_startup(struct usb_serial *serial) | ||||||
| 	s_priv->device_details = d_details; | 	s_priv->device_details = d_details; | ||||||
| 	usb_set_serial_data(serial, s_priv); | 	usb_set_serial_data(serial, s_priv); | ||||||
| 
 | 
 | ||||||
| 	/* Now setup per port private data */ |  | ||||||
| 	for (i = 0; i < serial->num_ports; i++) { |  | ||||||
| 		port = serial->port[i]; |  | ||||||
| 		p_priv = kzalloc(sizeof(struct keyspan_port_private), |  | ||||||
| 								GFP_KERNEL); |  | ||||||
| 		if (!p_priv) { |  | ||||||
| 			dev_dbg(&port->dev, "%s - kmalloc for keyspan_port_private (%d) failed!.\n", __func__, i); |  | ||||||
| 			return 1; |  | ||||||
| 		} |  | ||||||
| 		p_priv->device_details = d_details; |  | ||||||
| 		usb_set_serial_port_data(port, p_priv); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	keyspan_setup_urbs(serial); | 	keyspan_setup_urbs(serial); | ||||||
| 
 | 
 | ||||||
| 	if (s_priv->instat_urb != NULL) { | 	if (s_priv->instat_urb != NULL) { | ||||||
|  | @ -2463,59 +2405,112 @@ static int keyspan_startup(struct usb_serial *serial) | ||||||
| 
 | 
 | ||||||
| static void keyspan_disconnect(struct usb_serial *serial) | static void keyspan_disconnect(struct usb_serial *serial) | ||||||
| { | { | ||||||
| 	int				i, j; | 	struct keyspan_serial_private *s_priv; | ||||||
| 	struct usb_serial_port		*port; |  | ||||||
| 	struct keyspan_serial_private 	*s_priv; |  | ||||||
| 	struct keyspan_port_private	*p_priv; |  | ||||||
| 
 | 
 | ||||||
| 	s_priv = usb_get_serial_data(serial); | 	s_priv = usb_get_serial_data(serial); | ||||||
| 
 | 
 | ||||||
| 	/* Stop reading/writing urbs */ |  | ||||||
| 	stop_urb(s_priv->instat_urb); | 	stop_urb(s_priv->instat_urb); | ||||||
| 	stop_urb(s_priv->glocont_urb); | 	stop_urb(s_priv->glocont_urb); | ||||||
| 	stop_urb(s_priv->indat_urb); | 	stop_urb(s_priv->indat_urb); | ||||||
| 	for (i = 0; i < serial->num_ports; ++i) { |  | ||||||
| 		port = serial->port[i]; |  | ||||||
| 		p_priv = usb_get_serial_port_data(port); |  | ||||||
| 		stop_urb(p_priv->inack_urb); |  | ||||||
| 		stop_urb(p_priv->outcont_urb); |  | ||||||
| 		for (j = 0; j < 2; j++) { |  | ||||||
| 			stop_urb(p_priv->in_urbs[j]); |  | ||||||
| 			stop_urb(p_priv->out_urbs[j]); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/* Now free them */ |  | ||||||
| 	usb_free_urb(s_priv->instat_urb); |  | ||||||
| 	usb_free_urb(s_priv->indat_urb); |  | ||||||
| 	usb_free_urb(s_priv->glocont_urb); |  | ||||||
| 	for (i = 0; i < serial->num_ports; ++i) { |  | ||||||
| 		port = serial->port[i]; |  | ||||||
| 		p_priv = usb_get_serial_port_data(port); |  | ||||||
| 		usb_free_urb(p_priv->inack_urb); |  | ||||||
| 		usb_free_urb(p_priv->outcont_urb); |  | ||||||
| 		for (j = 0; j < 2; j++) { |  | ||||||
| 			usb_free_urb(p_priv->in_urbs[j]); |  | ||||||
| 			usb_free_urb(p_priv->out_urbs[j]); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void keyspan_release(struct usb_serial *serial) | static void keyspan_release(struct usb_serial *serial) | ||||||
| { | { | ||||||
| 	int				i; | 	struct keyspan_serial_private *s_priv; | ||||||
| 	struct usb_serial_port		*port; |  | ||||||
| 	struct keyspan_serial_private 	*s_priv; |  | ||||||
| 
 | 
 | ||||||
| 	s_priv = usb_get_serial_data(serial); | 	s_priv = usb_get_serial_data(serial); | ||||||
| 
 | 
 | ||||||
| 	kfree(s_priv); | 	usb_free_urb(s_priv->instat_urb); | ||||||
|  | 	usb_free_urb(s_priv->indat_urb); | ||||||
|  | 	usb_free_urb(s_priv->glocont_urb); | ||||||
| 
 | 
 | ||||||
| 	/* Now free per port private data */ | 	kfree(s_priv); | ||||||
| 	for (i = 0; i < serial->num_ports; i++) { | } | ||||||
| 		port = serial->port[i]; | 
 | ||||||
| 		kfree(usb_get_serial_port_data(port)); | static int keyspan_port_probe(struct usb_serial_port *port) | ||||||
|  | { | ||||||
|  | 	struct usb_serial *serial = port->serial; | ||||||
|  | 	struct keyspan_port_private *s_priv; | ||||||
|  | 	struct keyspan_port_private *p_priv; | ||||||
|  | 	const struct keyspan_device_details *d_details; | ||||||
|  | 	struct callbacks *cback; | ||||||
|  | 	int endp; | ||||||
|  | 	int port_num; | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	s_priv = usb_get_serial_data(serial); | ||||||
|  | 	d_details = s_priv->device_details; | ||||||
|  | 
 | ||||||
|  | 	p_priv = kzalloc(sizeof(*p_priv), GFP_KERNEL); | ||||||
|  | 	if (!p_priv) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	s_priv = usb_get_serial_data(port->serial); | ||||||
|  | 	p_priv->device_details = d_details; | ||||||
|  | 
 | ||||||
|  | 	/* Setup values for the various callback routines */ | ||||||
|  | 	cback = &keyspan_callbacks[d_details->msg_format]; | ||||||
|  | 
 | ||||||
|  | 	port_num = port->number - port->serial->minor; | ||||||
|  | 
 | ||||||
|  | 	/* Do indat endpoints first, once for each flip */ | ||||||
|  | 	endp = d_details->indat_endpoints[port_num]; | ||||||
|  | 	for (i = 0; i <= d_details->indat_endp_flip; ++i, ++endp) { | ||||||
|  | 		p_priv->in_urbs[i] = keyspan_setup_urb(serial, endp, | ||||||
|  | 						USB_DIR_IN, port, | ||||||
|  | 						p_priv->in_buffer[i], 64, | ||||||
|  | 						cback->indat_callback); | ||||||
| 	} | 	} | ||||||
|  | 	/* outdat endpoints also have flip */ | ||||||
|  | 	endp = d_details->outdat_endpoints[port_num]; | ||||||
|  | 	for (i = 0; i <= d_details->outdat_endp_flip; ++i, ++endp) { | ||||||
|  | 		p_priv->out_urbs[i] = keyspan_setup_urb(serial, endp, | ||||||
|  | 						USB_DIR_OUT, port, | ||||||
|  | 						p_priv->out_buffer[i], 64, | ||||||
|  | 						cback->outdat_callback); | ||||||
|  | 	} | ||||||
|  | 	/* inack endpoint */ | ||||||
|  | 	p_priv->inack_urb = keyspan_setup_urb(serial, | ||||||
|  | 					d_details->inack_endpoints[port_num], | ||||||
|  | 					USB_DIR_IN, port, | ||||||
|  | 					p_priv->inack_buffer, 1, | ||||||
|  | 					cback->inack_callback); | ||||||
|  | 	/* outcont endpoint */ | ||||||
|  | 	p_priv->outcont_urb = keyspan_setup_urb(serial, | ||||||
|  | 					d_details->outcont_endpoints[port_num], | ||||||
|  | 					USB_DIR_OUT, port, | ||||||
|  | 					p_priv->outcont_buffer, 64, | ||||||
|  | 					 cback->outcont_callback); | ||||||
|  | 
 | ||||||
|  | 	usb_set_serial_port_data(port, p_priv); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int keyspan_port_remove(struct usb_serial_port *port) | ||||||
|  | { | ||||||
|  | 	struct keyspan_port_private *p_priv; | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	p_priv = usb_get_serial_port_data(port); | ||||||
|  | 
 | ||||||
|  | 	stop_urb(p_priv->inack_urb); | ||||||
|  | 	stop_urb(p_priv->outcont_urb); | ||||||
|  | 	for (i = 0; i < 2; i++) { | ||||||
|  | 		stop_urb(p_priv->in_urbs[i]); | ||||||
|  | 		stop_urb(p_priv->out_urbs[i]); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	usb_free_urb(p_priv->inack_urb); | ||||||
|  | 	usb_free_urb(p_priv->outcont_urb); | ||||||
|  | 	for (i = 0; i < 2; i++) { | ||||||
|  | 		usb_free_urb(p_priv->in_urbs[i]); | ||||||
|  | 		usb_free_urb(p_priv->out_urbs[i]); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	kfree(p_priv); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MODULE_AUTHOR(DRIVER_AUTHOR); | MODULE_AUTHOR(DRIVER_AUTHOR); | ||||||
|  |  | ||||||
|  | @ -42,6 +42,8 @@ static void keyspan_dtr_rts		(struct usb_serial_port *port, int on); | ||||||
| static int  keyspan_startup		(struct usb_serial *serial); | static int  keyspan_startup		(struct usb_serial *serial); | ||||||
| static void keyspan_disconnect		(struct usb_serial *serial); | static void keyspan_disconnect		(struct usb_serial *serial); | ||||||
| static void keyspan_release		(struct usb_serial *serial); | static void keyspan_release		(struct usb_serial *serial); | ||||||
|  | static int keyspan_port_probe(struct usb_serial_port *port); | ||||||
|  | static int keyspan_port_remove(struct usb_serial_port *port); | ||||||
| static int  keyspan_write_room		(struct tty_struct *tty); | static int  keyspan_write_room		(struct tty_struct *tty); | ||||||
| 
 | 
 | ||||||
| static int  keyspan_write		(struct tty_struct *tty, | static int  keyspan_write		(struct tty_struct *tty, | ||||||
|  | @ -567,6 +569,8 @@ static struct usb_serial_driver keyspan_1port_device = { | ||||||
| 	.attach			= keyspan_startup, | 	.attach			= keyspan_startup, | ||||||
| 	.disconnect		= keyspan_disconnect, | 	.disconnect		= keyspan_disconnect, | ||||||
| 	.release		= keyspan_release, | 	.release		= keyspan_release, | ||||||
|  | 	.port_probe		= keyspan_port_probe, | ||||||
|  | 	.port_remove		= keyspan_port_remove, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static struct usb_serial_driver keyspan_2port_device = { | static struct usb_serial_driver keyspan_2port_device = { | ||||||
|  | @ -589,6 +593,8 @@ static struct usb_serial_driver keyspan_2port_device = { | ||||||
| 	.attach			= keyspan_startup, | 	.attach			= keyspan_startup, | ||||||
| 	.disconnect		= keyspan_disconnect, | 	.disconnect		= keyspan_disconnect, | ||||||
| 	.release		= keyspan_release, | 	.release		= keyspan_release, | ||||||
|  | 	.port_probe		= keyspan_port_probe, | ||||||
|  | 	.port_remove		= keyspan_port_remove, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static struct usb_serial_driver keyspan_4port_device = { | static struct usb_serial_driver keyspan_4port_device = { | ||||||
|  | @ -611,6 +617,8 @@ static struct usb_serial_driver keyspan_4port_device = { | ||||||
| 	.attach			= keyspan_startup, | 	.attach			= keyspan_startup, | ||||||
| 	.disconnect		= keyspan_disconnect, | 	.disconnect		= keyspan_disconnect, | ||||||
| 	.release		= keyspan_release, | 	.release		= keyspan_release, | ||||||
|  | 	.port_probe		= keyspan_port_probe, | ||||||
|  | 	.port_remove		= keyspan_port_remove, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static struct usb_serial_driver * const serial_drivers[] = { | static struct usb_serial_driver * const serial_drivers[] = { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Johan Hovold
				Johan Hovold