USB: serial: fix null-pointer dereferences on disconnect
authorJohan Hovold <jhovold@gmail.com>
Wed, 13 Feb 2013 16:53:28 +0000 (17:53 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 27 Feb 2013 17:21:06 +0000 (09:21 -0800)
commit b2ca699076573c94fee9a73cb0d8645383b602a0 upstream.

Make sure serial-driver dtr_rts is called with disc_mutex held after
checking the disconnected flag.

Due to a bug in the tty layer, dtr_rts may get called after a device has
been disconnected and the tty-device unregistered. Some drivers have had
individual checks for disconnect to make sure the disconnected interface
was not accessed, but this should really be handled in usb-serial core
(at least until the long-standing tty-bug has been fixed).

Note that the problem has been made more acute with commit 0998d0631001
("device-core: Ensure drvdata = NULL when no driver is bound") as the
port data is now also NULL when dtr_rts is called resulting in further
oopses.

Reported-by: Chris Ruehl <chris.ruehl@gtsys.com.hk>
Signed-off-by: Johan Hovold <jhovold@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/serial/ftdi_sio.c
drivers/usb/serial/mct_u232.c
drivers/usb/serial/quatech2.c
drivers/usb/serial/sierra.c
drivers/usb/serial/ssu100.c
drivers/usb/serial/usb-serial.c
drivers/usb/serial/usb_wwan.c

index b786b7d2a1785247fd9946fd03d9bb73f49cb563..50fbc30652ea468974114400922c7886722eaba8 100644 (file)
@@ -1884,24 +1884,22 @@ static void ftdi_dtr_rts(struct usb_serial_port *port, int on)
 {
        struct ftdi_private *priv = usb_get_serial_port_data(port);
 
-       mutex_lock(&port->serial->disc_mutex);
-       if (!port->serial->disconnected) {
-               /* Disable flow control */
-               if (!on && usb_control_msg(port->serial->dev,
+       /* Disable flow control */
+       if (!on) {
+               if (usb_control_msg(port->serial->dev,
                            usb_sndctrlpipe(port->serial->dev, 0),
                            FTDI_SIO_SET_FLOW_CTRL_REQUEST,
                            FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE,
                            0, priv->interface, NULL, 0,
                            WDR_TIMEOUT) < 0) {
-                           dev_err(&port->dev, "error from flowcontrol urb\n");
+                       dev_err(&port->dev, "error from flowcontrol urb\n");
                }
-               /* drop RTS and DTR */
-               if (on)
-                       set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
-               else
-                       clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
        }
-       mutex_unlock(&port->serial->disc_mutex);
+       /* drop RTS and DTR */
+       if (on)
+               set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+       else
+               clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
 }
 
 /*
index 8a2081004107977dc929d586ffda41fdf34abf06..6602059037b0e1960c499738b8dc39853f7424b3 100644 (file)
@@ -503,19 +503,15 @@ static void mct_u232_dtr_rts(struct usb_serial_port *port, int on)
        unsigned int control_state;
        struct mct_u232_private *priv = usb_get_serial_port_data(port);
 
-       mutex_lock(&port->serial->disc_mutex);
-       if (!port->serial->disconnected) {
-               /* drop DTR and RTS */
-               spin_lock_irq(&priv->lock);
-               if (on)
-                       priv->control_state |= TIOCM_DTR | TIOCM_RTS;
-               else
-                       priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS);
-               control_state = priv->control_state;
-               spin_unlock_irq(&priv->lock);
-               mct_u232_set_modem_ctrl(port, control_state);
-       }
-       mutex_unlock(&port->serial->disc_mutex);
+       spin_lock_irq(&priv->lock);
+       if (on)
+               priv->control_state |= TIOCM_DTR | TIOCM_RTS;
+       else
+               priv->control_state &= ~(TIOCM_DTR | TIOCM_RTS);
+       control_state = priv->control_state;
+       spin_unlock_irq(&priv->lock);
+
+       mct_u232_set_modem_ctrl(port, control_state);
 }
 
 static void mct_u232_close(struct usb_serial_port *port)
index ffcfc962ab10b5a4043cd900f8ca6e90094ff417..0cba503fda5d8bd68e349aa087432842250c6d67 100644 (file)
@@ -947,19 +947,17 @@ static void qt2_dtr_rts(struct usb_serial_port *port, int on)
        struct usb_device *dev = port->serial->dev;
        struct qt2_port_private *port_priv = usb_get_serial_port_data(port);
 
-       mutex_lock(&port->serial->disc_mutex);
-       if (!port->serial->disconnected) {
-               /* Disable flow control */
-               if (!on && qt2_setregister(dev, port_priv->device_port,
+       /* Disable flow control */
+       if (!on) {
+               if (qt2_setregister(dev, port_priv->device_port,
                                           UART_MCR, 0) < 0)
                        dev_warn(&port->dev, "error from flowcontrol urb\n");
-               /* drop RTS and DTR */
-               if (on)
-                       update_mctrl(port_priv, TIOCM_DTR | TIOCM_RTS, 0);
-               else
-                       update_mctrl(port_priv, 0, TIOCM_DTR | TIOCM_RTS);
        }
-       mutex_unlock(&port->serial->disc_mutex);
+       /* drop RTS and DTR */
+       if (on)
+               update_mctrl(port_priv, TIOCM_DTR | TIOCM_RTS, 0);
+       else
+               update_mctrl(port_priv, 0, TIOCM_DTR | TIOCM_RTS);
 }
 
 static void qt2_update_msr(struct usb_serial_port *port, unsigned char *ch)
index 270860f6bb2aee0a97f2091c7c33041332cccc1e..4eed702d27bd531edd60d942222db1ab4c402ad6 100644 (file)
@@ -861,19 +861,13 @@ static int sierra_open(struct tty_struct *tty, struct usb_serial_port *port)
 
 static void sierra_dtr_rts(struct usb_serial_port *port, int on)
 {
-       struct usb_serial *serial = port->serial;
        struct sierra_port_private *portdata;
 
        portdata = usb_get_serial_port_data(port);
        portdata->rts_state = on;
        portdata->dtr_state = on;
 
-       if (serial->dev) {
-               mutex_lock(&serial->disc_mutex);
-               if (!serial->disconnected)
-                       sierra_send_setup(port);
-               mutex_unlock(&serial->disc_mutex);
-       }
+       sierra_send_setup(port);
 }
 
 static int sierra_startup(struct usb_serial *serial)
index 868d1e6852e2ab6086a354cd62a352a241cb51c7..5238bf870f8bdb2bd473519519c56569598ebcff 100644 (file)
@@ -508,19 +508,16 @@ static void ssu100_dtr_rts(struct usb_serial_port *port, int on)
 {
        struct usb_device *dev = port->serial->dev;
 
-       mutex_lock(&port->serial->disc_mutex);
-       if (!port->serial->disconnected) {
-               /* Disable flow control */
-               if (!on &&
-                   ssu100_setregister(dev, 0, UART_MCR, 0) < 0)
+       /* Disable flow control */
+       if (!on) {
+               if (ssu100_setregister(dev, 0, UART_MCR, 0) < 0)
                        dev_err(&port->dev, "error from flowcontrol urb\n");
-               /* drop RTS and DTR */
-               if (on)
-                       set_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
-               else
-                       clear_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
        }
-       mutex_unlock(&port->serial->disc_mutex);
+       /* drop RTS and DTR */
+       if (on)
+               set_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
+       else
+               clear_mctrl(dev, TIOCM_DTR | TIOCM_RTS);
 }
 
 static void ssu100_update_msr(struct usb_serial_port *port, u8 msr)
index 73b8e05691644bc9830883e0b8fb2ebe3546908b..f057f4daab52daf082a71041aec88465db06da12 100644 (file)
@@ -687,10 +687,20 @@ static int serial_carrier_raised(struct tty_port *port)
 static void serial_dtr_rts(struct tty_port *port, int on)
 {
        struct usb_serial_port *p = container_of(port, struct usb_serial_port, port);
-       struct usb_serial_driver *drv = p->serial->type;
+       struct usb_serial *serial = p->serial;
+       struct usb_serial_driver *drv = serial->type;
 
-       if (drv->dtr_rts)
+       if (!drv->dtr_rts)
+               return;
+       /*
+        * Work-around bug in the tty-layer which can result in dtr_rts
+        * being called after a disconnect (and tty_unregister_device
+        * has returned). Remove once bug has been squashed.
+        */
+       mutex_lock(&serial->disc_mutex);
+       if (!serial->disconnected)
                drv->dtr_rts(p, on);
+       mutex_unlock(&serial->disc_mutex);
 }
 
 static const struct tty_port_operations serial_port_ops = {
index a3e9c095f0d83a39738ef74b9e3a9ef7b41a2d6e..2897859122bc0fde0596e897be59904ef24c9263 100644 (file)
@@ -39,7 +39,6 @@
 
 void usb_wwan_dtr_rts(struct usb_serial_port *port, int on)
 {
-       struct usb_serial *serial = port->serial;
        struct usb_wwan_port_private *portdata;
        struct usb_wwan_intf_private *intfdata;
 
@@ -49,12 +48,11 @@ void usb_wwan_dtr_rts(struct usb_serial_port *port, int on)
                return;
 
        portdata = usb_get_serial_port_data(port);
-       mutex_lock(&serial->disc_mutex);
+       /* FIXME: locking */
        portdata->rts_state = on;
        portdata->dtr_state = on;
-       if (serial->dev)
-               intfdata->send_setup(port);
-       mutex_unlock(&serial->disc_mutex);
+
+       intfdata->send_setup(port);
 }
 EXPORT_SYMBOL(usb_wwan_dtr_rts);