Re: [Fwd: Re: [Fwd: Re: [Bug 20703] HAL sometimes doesn't emit udi-removed signals]]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Oliver:

Can you please check out this patch?  You're much more familiar with 
usb-serial than I am.

The main problem it tries to solve is that the port devices aren't
being handled properly.  They aren't unregistered during disconnection
and they aren't properly refcounted.

There are a few smaller problems it fixes too: serial->disc_mutex needs 
to make open mutually exclusive with disconnect, and 
flush_scheduled_work() is deadlock-prone.  And given this patch, it 
looks like port->port.count may not be needed -- but I don't know 
enough about it to be able to tell for sure.

Dan, you can try this one out in place of the earlier patch.  Hopefully 
it will resolve the use-after-free BUG.

Thanks,

Alan Stern


Index: usb-2.6/drivers/usb/serial/usb-serial.c
===================================================================
--- usb-2.6.orig/drivers/usb/serial/usb-serial.c
+++ usb-2.6/drivers/usb/serial/usb-serial.c
@@ -142,16 +142,6 @@ static void destroy_serial(struct kref *
 	if (serial->minor != SERIAL_TTY_NO_MINOR)
 		return_serial(serial);
 
-	for (i = 0; i < serial->num_ports; ++i)
-		serial->port[i]->port.count = 0;
-
-	/* the ports are cleaned up and released in port_release() */
-	for (i = 0; i < serial->num_ports; ++i)
-		if (serial->port[i]->dev.parent != NULL) {
-			device_unregister(&serial->port[i]->dev);
-			serial->port[i] = NULL;
-		}
-
 	/* If this is a "fake" port, we have to clean it up here, as it will
 	 * not get cleaned up in port_release() as it was never registered with
 	 * the driver core */
@@ -186,7 +176,7 @@ static int serial_open (struct tty_struc
 	struct usb_serial *serial;
 	struct usb_serial_port *port;
 	unsigned int portNumber;
-	int retval;
+	int retval = 0;
 
 	dbg("%s", __func__);
 
@@ -197,21 +187,22 @@ static int serial_open (struct tty_struc
 		return -ENODEV;
 	}
 
+	mutex_lock(&serial->disc_mutex);
 	portNumber = tty->index - serial->minor;
 	port = serial->port[portNumber];
-	if (!port) {
+	if (!port)
 		retval = -ENODEV;
-		goto bailout_kref_put;
-	}
-
-	if (port->serial->disconnected) {
+	else if (port->serial->disconnected)
 		retval = -ENODEV;
-		goto bailout_kref_put;
-	}
+	else
+		get_device(&port->dev);
+	mutex_unlock(&serial->disc_mutex);
+	if (retval)
+		goto bailout_serial_put;
 
 	if (mutex_lock_interruptible(&port->mutex)) {
 		retval = -ERESTARTSYS;
-		goto bailout_kref_put;
+		goto bailout_port_put;
 	}
 
 	++port->port.count;
@@ -253,7 +244,9 @@ bailout_mutex_unlock:
 	tty->driver_data = NULL;
 	tty_port_tty_set(&port->port, NULL);
 	mutex_unlock(&port->mutex);
-bailout_kref_put:
+bailout_port_put:
+	put_device(&port->dev);
+bailout_serial_put:
 	usb_serial_put(serial);
 	return retval;
 }
@@ -261,6 +254,8 @@ bailout_kref_put:
 static void serial_close(struct tty_struct *tty, struct file *filp)
 {
 	struct usb_serial_port *port = tty->driver_data;
+	struct usb_serial *serial;
+	int count;
 
 	if (!port)
 		return;
@@ -268,6 +263,7 @@ static void serial_close(struct tty_stru
 	dbg("%s - port %d", __func__, port->number);
 
 	mutex_lock(&port->mutex);
+	serial = port->serial;
 
 	if (port->port.count == 0) {
 		mutex_unlock(&port->mutex);
@@ -280,7 +276,7 @@ static void serial_close(struct tty_stru
 		 * this before we drop the port count. The call is protected
 		 * by the port mutex
 		 */
-		port->serial->type->close(tty, port, filp);
+		serial->type->close(tty, port, filp);
 
 	if (port->port.count == (port->console ? 2 : 1)) {
 		struct tty_struct *tty = tty_port_tty_get(&port->port);
@@ -294,17 +290,19 @@ static void serial_close(struct tty_stru
 		}
 	}
 
-	if (port->port.count == 1) {
-		mutex_lock(&port->serial->disc_mutex);
-		if (!port->serial->disconnected)
-			usb_autopm_put_interface(port->serial->interface);
-		mutex_unlock(&port->serial->disc_mutex);
-		module_put(port->serial->type->driver.owner);
-	}
 	--port->port.count;
-
+	count = port->port.count;
 	mutex_unlock(&port->mutex);
-	usb_serial_put(port->serial);
+	put_device(&port->dev);
+
+	if (count == 0) {
+		mutex_lock(&serial->disc_mutex);
+		if (!serial->disconnected)
+			usb_autopm_put_interface(serial->interface);
+		mutex_unlock(&serial->disc_mutex);
+		module_put(serial->type->driver.owner);
+	}
+	usb_serial_put(serial);
 }
 
 static int serial_write(struct tty_struct *tty, const unsigned char *buf,
@@ -554,6 +552,7 @@ static void kill_traffic(struct usb_seri
 static void port_free(struct usb_serial_port *port)
 {
 	kill_traffic(port);
+	cancel_work_sync(&port->work);
 	usb_free_urb(port->read_urb);
 	usb_free_urb(port->write_urb);
 	usb_free_urb(port->interrupt_in_urb);
@@ -562,7 +561,6 @@ static void port_free(struct usb_serial_
 	kfree(port->bulk_out_buffer);
 	kfree(port->interrupt_in_buffer);
 	kfree(port->interrupt_out_buffer);
-	flush_scheduled_work();		/* port->work */
 	kfree(port);
 }
 
@@ -1047,6 +1045,8 @@ void usb_serial_disconnect(struct usb_in
 	usb_set_intfdata(interface, NULL);
 	/* must set a flag, to signal subdrivers */
 	serial->disconnected = 1;
+	mutex_unlock(&serial->disc_mutex);
+
 	for (i = 0; i < serial->num_ports; ++i) {
 		port = serial->port[i];
 		if (port) {
@@ -1056,11 +1056,12 @@ void usb_serial_disconnect(struct usb_in
 				tty_kref_put(tty);
 			}
 			kill_traffic(port);
+			device_unregister(&port->dev);
+			serial->port[i] = NULL;
 		}
 	}
 	/* let the last holder of this object
 	 * cause it to be cleaned up */
-	mutex_unlock(&serial->disc_mutex);
 	usb_serial_put(serial);
 	dev_info(dev, "device disconnected\n");
 }


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux