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