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]

 



On Fri, 2009-03-20 at 11:42 -0400, Alan Stern wrote:
> 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.

This patch (slightly modified to apply to 2.6.29-rc8 sources; the
bailout_serial_put stuff didn't apply exactly) appears to work correctly
over a few plug/unplug sessions while PPP is up and running on the tty.
Modified patch below.

Thanks!
Dan

--- usb-serial.c.ORIG	2009-03-24 11:22:32.000000000 -0400
+++ usb-serial.c	2009-03-24 11:32:03.000000000 -0400
@@ -142,16 +142,6 @@
 	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 @@
 	struct usb_serial *serial;
 	struct usb_serial_port *port;
 	unsigned int portNumber;
-	int retval;
+	int retval = 0;
 
 	dbg("%s", __func__);
 
@@ -197,16 +187,22 @@
 		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;
-	}
+	else if (port->serial->disconnected)
+		retval = -ENODEV;
+	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;
@@ -248,7 +244,9 @@
 	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;
 }
@@ -256,6 +254,8 @@
 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;
@@ -263,6 +263,7 @@
 	dbg("%s - port %d", __func__, port->number);
 
 	mutex_lock(&port->mutex);
+	serial = port->serial;
 
 	if (port->port.count == 0) {
 		mutex_unlock(&port->mutex);
@@ -275,7 +276,7 @@
 		 * 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);
@@ -289,17 +290,20 @@
 		}
 	}
 
-	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,
@@ -549,6 +553,7 @@
 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);
@@ -557,7 +562,6 @@
 	kfree(port->bulk_out_buffer);
 	kfree(port->interrupt_in_buffer);
 	kfree(port->interrupt_out_buffer);
-	flush_scheduled_work();		/* port->work */
 	kfree(port);
 }
 
@@ -1042,6 +1046,8 @@
 	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) {
@@ -1051,11 +1057,12 @@
 				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