serial: core: Prevent unsafe uart port access, part 3
authorPeter Hurley <peter@hurleysoftware.com>
Sun, 10 Apr 2016 01:56:35 +0000 (18:56 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 30 Apr 2016 16:26:55 +0000 (09:26 -0700)
For tty operations which may expect uart port to have been removed
but still have other necessary work to accomplish, check for NULL
uart port; specifically uart_close(), uart_hangup() and sub-functions
(uart_shutdown() and uart_port_shutdown()).

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/serial_core.c

index 1887f9c71f859f89388aabe54850b887533bfa91..d529b5ce66c711ad38be6d502dc2938e1a3f351d 100644 (file)
@@ -257,6 +257,8 @@ static int uart_startup(struct tty_struct *tty, struct uart_state *state,
  * This routine will shutdown a serial port; interrupts are disabled, and
  * DTR is dropped if the hangup on close termio flag is on.  Calls to
  * uart_shutdown are serialised by the per-port semaphore.
+ *
+ * uport == NULL if uart_port has already been removed
  */
 static void uart_shutdown(struct tty_struct *tty, struct uart_state *state)
 {
@@ -275,7 +277,7 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state)
                /*
                 * Turn off DTR and RTS early.
                 */
-               if (uart_console(uport) && tty)
+               if (uport && uart_console(uport) && tty)
                        uport->cons->cflag = tty->termios.c_cflag;
 
                if (!tty || C_HUPCL(tty))
@@ -1460,7 +1462,6 @@ out:
  * Calls to uart_close() are serialised via the tty_lock in
  *   drivers/tty/tty_io.c:tty_release()
  *   drivers/tty/tty_io.c:do_tty_hangup()
- * This runs from a workqueue and can sleep for a _short_ time only.
  */
 static void uart_close(struct tty_struct *tty, struct file *filp)
 {
@@ -1479,18 +1480,21 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
                return;
        }
 
-       uport = state->uart_port;
        port = &state->port;
        pr_debug("uart_close(%d) called\n", tty->index);
 
-       if (!port->count || tty_port_close_start(port, tty, filp) == 0)
+       if (tty_port_close_start(port, tty, filp) == 0)
                return;
 
+       mutex_lock(&port->mutex);
+       uport = uart_port_check(state);
+
        /*
         * At this point, we stop accepting input.  To do this, we
         * disable the receive line status interrupts.
         */
-       if (tty_port_initialized(port)) {
+       if (tty_port_initialized(port) &&
+           !WARN(!uport, "detached port still initialized!\n")) {
                spin_lock_irq(&uport->lock);
                uport->ops->stop_rx(uport);
                spin_unlock_irq(&uport->lock);
@@ -1502,7 +1506,6 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
                uart_wait_until_sent(tty, uport->timeout);
        }
 
-       mutex_lock(&port->mutex);
        uart_shutdown(tty, state);
        tty_port_tty_set(port, NULL);
 
@@ -1513,7 +1516,7 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
                if (port->close_delay)
                        msleep_interruptible(jiffies_to_msecs(port->close_delay));
                spin_lock_irq(&port->lock);
-       } else if (!uart_console(uport)) {
+       } else if (uport && !uart_console(uport)) {
                spin_unlock_irq(&port->lock);
                uart_change_pm(state, UART_PM_STATE_OFF);
                spin_lock_irq(&port->lock);
@@ -1600,11 +1603,15 @@ static void uart_hangup(struct tty_struct *tty)
 {
        struct uart_state *state = tty->driver_data;
        struct tty_port *port = &state->port;
+       struct uart_port *uport;
        unsigned long flags;
 
        pr_debug("uart_hangup(%d)\n", tty->index);
 
        mutex_lock(&port->mutex);
+       uport = uart_port_check(state);
+       WARN(!uport, "hangup of detached port!\n");
+
        if (tty_port_active(port)) {
                uart_flush_buffer(tty);
                uart_shutdown(tty, state);
@@ -1613,7 +1620,7 @@ static void uart_hangup(struct tty_struct *tty)
                spin_unlock_irqrestore(&port->lock, flags);
                tty_port_set_active(port, 0);
                tty_port_tty_set(port, NULL);
-               if (!uart_console(state->uart_port))
+               if (uport && !uart_console(uport))
                        uart_change_pm(state, UART_PM_STATE_OFF);
                wake_up_interruptible(&port->open_wait);
                wake_up_interruptible(&port->delta_msr_wait);
@@ -1621,6 +1628,7 @@ static void uart_hangup(struct tty_struct *tty)
        mutex_unlock(&port->mutex);
 }
 
+/* uport == NULL if uart_port has already been removed */
 static void uart_port_shutdown(struct tty_port *port)
 {
        struct uart_state *state = container_of(port, struct uart_state, port);
@@ -1638,12 +1646,14 @@ static void uart_port_shutdown(struct tty_port *port)
        /*
         * Free the IRQ and disable the port.
         */
-       uport->ops->shutdown(uport);
+       if (uport)
+               uport->ops->shutdown(uport);
 
        /*
         * Ensure that the IRQ handler isn't running on another CPU.
         */
-       synchronize_irq(uport->irq);
+       if (uport)
+               synchronize_irq(uport->irq);
 }
 
 static int uart_carrier_raised(struct tty_port *port)