All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/8] Fix unsafe uart port access
@ 2015-12-13  0:36 Peter Hurley
  2015-12-13  0:36 ` [PATCH 1/8] serial: core: Fold __uart_put_char() into caller Peter Hurley
                   ` (8 more replies)
  0 siblings, 9 replies; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

Hi Greg,

The serial core has always intended to allow uart drivers to detach and
unload, even while ttys are open and running. Since the serial core is
the actual tty driver, it acts as a proxy for the uart driver, which
allows the uart driver to remove the port (which hangs up the tty) and
then unload.

However, all of this is broken.

Firstly, there is no mechanism for blocking the uart port removal until
current operations are complete. The tty core does not, and never has,
serialized ioctl() or any other tty operation other than open/close
with hangup.

Secondly, uart_register_driver() directly binds data from the uart driver
with the tty core, rather than providing proxy copies _without taking
module references to the uart driver_. This produces some spectacular
results if the tty is in-use when the uart driver unloads.

Thirdly, uart_unregister_driver() immediately destroys the tty port
despite that it may be in use, and will continue to be for the
lifetime of the tty, which is unbounded (userspace may retain open
ttys forever).

This series addresses the first problem of ensuring safe uart port
dereferencing with concurrent uart port removal. Two distinct
mechanisms are used to provide the necessary safe dereference: the
tty_port mutex and RCU.

By serializing the uart port detach (ie, reset to NULL) with the
tty_port mutex, existing sections of the serial core that already
hold the port mutex are guaranteed the uart port detach will not
be concurrent, and so can simply test for NULL value before first
dereference.

Most of the existing serial core that holds the port mutex is
ioctl-related and so can return -EIO as if the tty has been hungup
(which it has been).

Other portions of the serial core that sleep (eg. uart_wait_until_sent())
also now claim the port mutex to prevent concurrent uart port detach,
and thus protect the reference. The port mutex is dropped for the
actual sleep, retaken on wakeup and the uart port is re-checked
for NULL.

For portions of the serial core that don't sleep, RCU is used to
defer actual uart port teardown after detach (with synchronize_rcu())
until the current holders of rcu_read_lock() are complete.

The xmit buffer operations that don't modify the buffer indexes --
uart_write_room() and uart_chars_in_buffer() -- just report the state
of the xmit buffer anyway if the uart port has been removed, despite
that the xmit buffer is no longer lockable (the lock is in the
uart_port that is now gone).

Xmit buffer operations that do modify the buffer indexes are aborted.

Extra care is taken with the close/hangup/shutdown paths since this
must continue to perform other work even if the uart port has been
removed (for example, if the uart port was /dev/console and so will
only be closed when the file descriptor is released).

I do have a plan for the second and third problems but this series
does not implement those.

Regards,

Peter Hurley (8):
  serial: core: Fold __uart_put_char() into caller
  serial: core: Fold do_uart_get_info() into caller
  serial: core: Use tty->index for port # in debug messages
  serial: core: Take port mutex for send_xchar() tty method
  serial: core: Expand port mutex section in uart_poll_init()
  serial: core: Prevent unsafe uart port access, part 1
  serial: core: Prevent unsafe uart port access, part 2
  serial: core: Prevent unsafe uart port access, part 3

 drivers/tty/serial/8250/8250_port.c |   6 +-
 drivers/tty/serial/serial_core.c    | 547 +++++++++++++++++++++++-------------
 2 files changed, 355 insertions(+), 198 deletions(-)

-- 
2.6.3


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/8] serial: core: Fold __uart_put_char() into caller
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
@ 2015-12-13  0:36 ` Peter Hurley
  2015-12-13  0:36 ` [PATCH 2/8] serial: core: Fold do_uart_get_info() " Peter Hurley
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

uart_put_char() is the required interface; manually inline
__uart_put_char().

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/serial/serial_core.c | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index cbf36df..321b45f 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -485,12 +485,15 @@ static void uart_change_speed(struct tty_struct *tty, struct uart_state *state,
 	spin_unlock_irq(&uport->lock);
 }
 
-static inline int __uart_put_char(struct uart_port *port,
-				struct circ_buf *circ, unsigned char c)
+static int uart_put_char(struct tty_struct *tty, unsigned char c)
 {
+	struct uart_state *state = tty->driver_data;
+	struct uart_port *port = state->uart_port;
+	struct circ_buf *circ;
 	unsigned long flags;
 	int ret = 0;
 
+	circ = &state->xmit;
 	if (!circ->buf)
 		return 0;
 
@@ -504,13 +507,6 @@ static inline int __uart_put_char(struct uart_port *port,
 	return ret;
 }
 
-static int uart_put_char(struct tty_struct *tty, unsigned char ch)
-{
-	struct uart_state *state = tty->driver_data;
-
-	return __uart_put_char(state->uart_port, &state->xmit, ch);
-}
-
 static void uart_flush_chars(struct tty_struct *tty)
 {
 	uart_start(tty);
-- 
2.6.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/8] serial: core: Fold do_uart_get_info() into caller
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
  2015-12-13  0:36 ` [PATCH 1/8] serial: core: Fold __uart_put_char() into caller Peter Hurley
@ 2015-12-13  0:36 ` Peter Hurley
  2015-12-13  0:36 ` [PATCH 3/8] serial: core: Use tty->index for port # in debug messages Peter Hurley
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

do_uart_get_info() has a single caller: uart_get_info().
Manually inline do_uart_get_info().

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/serial/serial_core.c | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 321b45f..89b5ddd 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -673,14 +673,18 @@ static void uart_unthrottle(struct tty_struct *tty)
 		uart_set_mctrl(port, TIOCM_RTS);
 }
 
-static void do_uart_get_info(struct tty_port *port,
-			struct serial_struct *retinfo)
+static void uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
 {
 	struct uart_state *state = container_of(port, struct uart_state, port);
 	struct uart_port *uport = state->uart_port;
 
 	memset(retinfo, 0, sizeof(*retinfo));
 
+	/*
+	 * Ensure the state we copy is consistent and no hardware changes
+	 * occur as we go
+	 */
+	mutex_lock(&port->mutex);
 	retinfo->type	    = uport->type;
 	retinfo->line	    = uport->line;
 	retinfo->port	    = uport->iobase;
@@ -699,15 +703,6 @@ static void do_uart_get_info(struct tty_port *port,
 	retinfo->io_type         = uport->iotype;
 	retinfo->iomem_reg_shift = uport->regshift;
 	retinfo->iomem_base      = (void *)(unsigned long)uport->mapbase;
-}
-
-static void uart_get_info(struct tty_port *port,
-			struct serial_struct *retinfo)
-{
-	/* Ensure the state we copy is consistent and no hardware changes
-	   occur as we go */
-	mutex_lock(&port->mutex);
-	do_uart_get_info(port, retinfo);
 	mutex_unlock(&port->mutex);
 }
 
@@ -715,6 +710,7 @@ static int uart_get_info_user(struct tty_port *port,
 			 struct serial_struct __user *retinfo)
 {
 	struct serial_struct tmp;
+
 	uart_get_info(port, &tmp);
 
 	if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
-- 
2.6.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/8] serial: core: Use tty->index for port # in debug messages
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
  2015-12-13  0:36 ` [PATCH 1/8] serial: core: Fold __uart_put_char() into caller Peter Hurley
  2015-12-13  0:36 ` [PATCH 2/8] serial: core: Fold do_uart_get_info() " Peter Hurley
@ 2015-12-13  0:36 ` Peter Hurley
  2015-12-13  0:36 ` [PATCH 4/8] serial: core: Take port mutex for send_xchar() tty method Peter Hurley
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

The uart port may have already been removed by uart_remove_one_port();
use equivalent tty->index (which is always valid in these contexts)
instead.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/serial/serial_core.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 89b5ddd..4430518 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -1383,8 +1383,7 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
 
 	uport = state->uart_port;
 	port = &state->port;
-
-	pr_debug("uart_close(%d) called\n", uport ? uport->line : -1);
+	pr_debug("uart_close(%d) called\n", tty->index);
 
 	if (!port->count || tty_port_close_start(port, tty, filp) == 0)
 		return;
@@ -1498,7 +1497,7 @@ static void uart_hangup(struct tty_struct *tty)
 	struct tty_port *port = &state->port;
 	unsigned long flags;
 
-	pr_debug("uart_hangup(%d)\n", state->uart_port->line);
+	pr_debug("uart_hangup(%d)\n", tty->index);
 
 	mutex_lock(&port->mutex);
 	if (port->flags & ASYNC_NORMAL_ACTIVE) {
-- 
2.6.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/8] serial: core: Take port mutex for send_xchar() tty method
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
                   ` (2 preceding siblings ...)
  2015-12-13  0:36 ` [PATCH 3/8] serial: core: Use tty->index for port # in debug messages Peter Hurley
@ 2015-12-13  0:36 ` Peter Hurley
  2016-01-11  5:23   ` Peter Hurley
  2015-12-13  0:36 ` [PATCH 5/8] serial: core: Expand port mutex section in uart_poll_init() Peter Hurley
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

Prepare for separate methods of preventing unsafe uart port access
when sending xchar; claim port mutex for the tty ops method (which
might sleep) and rcu for the throttle/unthrottle uses.

The implied limitation is that uart drivers which support
AUTOXOFF flow control cannot define a send_xchar() method that sleeps.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/serial/serial_core.c | 30 +++++++++++++++++++-----------
 1 file changed, 19 insertions(+), 11 deletions(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 4430518..806eba81 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -610,23 +610,31 @@ static void uart_flush_buffer(struct tty_struct *tty)
  * This function is used to send a high-priority XON/XOFF character to
  * the device
  */
-static void uart_send_xchar(struct tty_struct *tty, char ch)
+static void __uart_send_xchar(struct uart_port *uport, char ch)
 {
-	struct uart_state *state = tty->driver_data;
-	struct uart_port *port = state->uart_port;
 	unsigned long flags;
 
-	if (port->ops->send_xchar)
-		port->ops->send_xchar(port, ch);
+	if (uport->ops->send_xchar)
+		uport->ops->send_xchar(uport, ch);
 	else {
-		spin_lock_irqsave(&port->lock, flags);
-		port->x_char = ch;
+		spin_lock_irqsave(&uport->lock, flags);
+		uport->x_char = ch;
 		if (ch)
-			port->ops->start_tx(port);
-		spin_unlock_irqrestore(&port->lock, flags);
+			uport->ops->start_tx(port);
+		spin_unlock_irqrestore(&uport->lock, flags);
 	}
 }
 
+static void uart_send_xchar(struct tty_struct *tty, char ch)
+{
+	struct uart_state *state = tty->driver_data;
+	struct uart_port *uport = state->uart_port;
+
+	mutex_lock(&state->port.mutex);
+	__uart_send_xchar(uport, ch);
+	mutex_unlock(&state->port.mutex);
+}
+
 static void uart_throttle(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
@@ -644,7 +652,7 @@ static void uart_throttle(struct tty_struct *tty)
 	}
 
 	if (mask & UPSTAT_AUTOXOFF)
-		uart_send_xchar(tty, STOP_CHAR(tty));
+		__uart_send_xchar(port, STOP_CHAR(tty));
 
 	if (mask & UPSTAT_AUTORTS)
 		uart_clear_mctrl(port, TIOCM_RTS);
@@ -667,7 +675,7 @@ static void uart_unthrottle(struct tty_struct *tty)
 	}
 
 	if (mask & UPSTAT_AUTOXOFF)
-		uart_send_xchar(tty, START_CHAR(tty));
+		__uart_send_xchar(port, START_CHAR(tty));
 
 	if (mask & UPSTAT_AUTORTS)
 		uart_set_mctrl(port, TIOCM_RTS);
-- 
2.6.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 5/8] serial: core: Expand port mutex section in uart_poll_init()
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
                   ` (3 preceding siblings ...)
  2015-12-13  0:36 ` [PATCH 4/8] serial: core: Take port mutex for send_xchar() tty method Peter Hurley
@ 2015-12-13  0:36 ` Peter Hurley
  2015-12-13  0:36 ` [PATCH 6/8] serial: core: Prevent unsafe uart port access, part 1 Peter Hurley
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

Prepare uart_poll_init() to safely dereference uart port; expand the
port mutex section to guarantee uart port remains valid until
uart_poll_init() completes.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/serial/serial_core.c | 37 ++++++++++++++++++-------------------
 1 file changed, 18 insertions(+), 19 deletions(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 806eba81..16c4c48 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -2268,42 +2268,41 @@ static int uart_poll_init(struct tty_driver *driver, int line, char *options)
 {
 	struct uart_driver *drv = driver->driver_state;
 	struct uart_state *state = drv->state + line;
-	struct uart_port *port;
+	struct tty_port *port;
+	struct uart_port *uport;
 	int baud = 9600;
 	int bits = 8;
 	int parity = 'n';
 	int flow = 'n';
-	int ret;
+	int ret = -1;
 
-	if (!state || !state->uart_port)
+	if (!state)
 		return -1;
 
-	port = state->uart_port;
-	if (!(port->ops->poll_get_char && port->ops->poll_put_char))
-		return -1;
+	port = &state->port;
+	mutex_lock(&port->mutex);
 
-	if (port->ops->poll_init) {
-		struct tty_port *tport = &state->port;
+	uport = state->uart_port;
+	if (!(uport->ops->poll_get_char && uport->ops->poll_put_char))
+		goto out;
 
-		ret = 0;
-		mutex_lock(&tport->mutex);
+	ret = 0;
+	if (uport->ops->poll_init) {
 		/*
 		 * We don't set ASYNCB_INITIALIZED as we only initialized the
 		 * hw, e.g. state->xmit is still uninitialized.
 		 */
-		if (!test_bit(ASYNCB_INITIALIZED, &tport->flags))
-			ret = port->ops->poll_init(port);
-		mutex_unlock(&tport->mutex);
-		if (ret)
-			return ret;
+		if (!test_bit(ASYNCB_INITIALIZED, &port->flags))
+			ret = uport->ops->poll_init(uport);
 	}
 
-	if (options) {
+	if (!ret && options) {
 		uart_parse_options(options, &baud, &parity, &bits, &flow);
-		return uart_set_options(port, NULL, baud, parity, bits, flow);
+		ret = uart_set_options(uport, NULL, baud, parity, bits, flow);
 	}
-
-	return 0;
+out:
+	mutex_unlock(&port->mutex);
+	return ret;
 }
 
 static int uart_poll_get_char(struct tty_driver *driver, int line)
-- 
2.6.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 6/8] serial: core: Prevent unsafe uart port access, part 1
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
                   ` (4 preceding siblings ...)
  2015-12-13  0:36 ` [PATCH 5/8] serial: core: Expand port mutex section in uart_poll_init() Peter Hurley
@ 2015-12-13  0:36 ` Peter Hurley
  2015-12-13  0:36 ` [PATCH 7/8] serial: core: Prevent unsafe uart port access, part 2 Peter Hurley
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

uart_remove_one_port() may race with every serial core operation
requiring a valid dereference of state->uart_port. In particular,
uart_remove_one_port() may unlink the uart port concurrently with
any serial core operation that may dereference same.

Ensure safe dereference for those operations that already claim
the port->mutex, and extend that guarantee for trivial cases,
such as the ioctl handlers.

For ioctls, return -EIO as if the port has been hung up (since it
has).

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/serial/serial_core.c | 115 ++++++++++++++++++++++++++-------------
 1 file changed, 78 insertions(+), 37 deletions(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 16c4c48..2806f52 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -681,10 +681,11 @@ static void uart_unthrottle(struct tty_struct *tty)
 		uart_set_mctrl(port, TIOCM_RTS);
 }
 
-static void uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
+static int uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
 {
 	struct uart_state *state = container_of(port, struct uart_state, port);
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
+	int ret = -ENODEV;
 
 	memset(retinfo, 0, sizeof(*retinfo));
 
@@ -693,6 +694,10 @@ static void uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
 	 * occur as we go
 	 */
 	mutex_lock(&port->mutex);
+	uport = state->uart_port;
+	if (!uport)
+		goto out;
+
 	retinfo->type	    = uport->type;
 	retinfo->line	    = uport->line;
 	retinfo->port	    = uport->iobase;
@@ -711,7 +716,11 @@ static void uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
 	retinfo->io_type         = uport->iotype;
 	retinfo->iomem_reg_shift = uport->regshift;
 	retinfo->iomem_base      = (void *)(unsigned long)uport->mapbase;
+
+	ret = 0;
+out:
 	mutex_unlock(&port->mutex);
+	return ret;
 }
 
 static int uart_get_info_user(struct tty_port *port,
@@ -719,7 +728,8 @@ static int uart_get_info_user(struct tty_port *port,
 {
 	struct serial_struct tmp;
 
-	uart_get_info(port, &tmp);
+	if (uart_get_info(port, &tmp) < 0)
+		return -EIO;
 
 	if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
 		return -EFAULT;
@@ -737,6 +747,9 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port,
 	upf_t old_flags, new_flags;
 	int retval = 0;
 
+	if (!uport)
+		return -EIO;
+
 	new_port = new_info->port;
 	if (HIGH_BITS_OFFSET)
 		new_port += (unsigned long) new_info->port_high << HIGH_BITS_OFFSET;
@@ -946,8 +959,6 @@ static int uart_set_info_user(struct tty_struct *tty, struct uart_state *state,
  *	@tty: tty associated with the UART
  *	@state: UART being queried
  *	@value: returned modem value
- *
- *	Note: uart_ioctl protects us against hangups.
  */
 static int uart_get_lsr_info(struct tty_struct *tty,
 			struct uart_state *state, unsigned int __user *value)
@@ -975,18 +986,22 @@ static int uart_tiocmget(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
 	struct tty_port *port = &state->port;
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
 	int result = -EIO;
 
 	mutex_lock(&port->mutex);
+	uport = state->uart_port;
+	if (!uport)
+		goto out;
+
 	if (!(tty->flags & (1 << TTY_IO_ERROR))) {
 		result = uport->mctrl;
 		spin_lock_irq(&uport->lock);
 		result |= uport->ops->get_mctrl(uport);
 		spin_unlock_irq(&uport->lock);
 	}
+out:
 	mutex_unlock(&port->mutex);
-
 	return result;
 }
 
@@ -994,15 +1009,20 @@ static int
 uart_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *uport = state->uart_port;
 	struct tty_port *port = &state->port;
+	struct uart_port *uport;
 	int ret = -EIO;
 
 	mutex_lock(&port->mutex);
+	uport = state->uart_port;
+	if (!uport)
+		goto out;
+
 	if (!(tty->flags & (1 << TTY_IO_ERROR))) {
 		uart_update_mctrl(uport, set, clear);
 		ret = 0;
 	}
+out:
 	mutex_unlock(&port->mutex);
 	return ret;
 }
@@ -1011,21 +1031,26 @@ static int uart_break_ctl(struct tty_struct *tty, int break_state)
 {
 	struct uart_state *state = tty->driver_data;
 	struct tty_port *port = &state->port;
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
+	int ret = -EIO;
 
 	mutex_lock(&port->mutex);
+	uport = state->uart_port;
+	if (!uport)
+		goto out;
 
 	if (uport->type != PORT_UNKNOWN)
 		uport->ops->break_ctl(uport, break_state);
-
+	ret = 0;
+out:
 	mutex_unlock(&port->mutex);
-	return 0;
+	return ret;
 }
 
 static int uart_do_autoconfig(struct tty_struct *tty,struct uart_state *state)
 {
-	struct uart_port *uport = state->uart_port;
 	struct tty_port *port = &state->port;
+	struct uart_port *uport;
 	int flags, ret;
 
 	if (!capable(CAP_SYS_ADMIN))
@@ -1039,6 +1064,12 @@ static int uart_do_autoconfig(struct tty_struct *tty,struct uart_state *state)
 	if (mutex_lock_interruptible(&port->mutex))
 		return -ERESTARTSYS;
 
+	uport = state->uart_port;
+	if (!uport) {
+		ret = -EIO;
+		goto out;
+	}
+
 	ret = -EBUSY;
 	if (tty_port_users(port) == 1) {
 		uart_shutdown(tty, state);
@@ -1062,6 +1093,7 @@ static int uart_do_autoconfig(struct tty_struct *tty,struct uart_state *state)
 
 		ret = uart_startup(tty, state, 1);
 	}
+out:
 	mutex_unlock(&port->mutex);
 	return ret;
 }
@@ -1210,11 +1242,11 @@ static int uart_set_rs485_config(struct uart_port *port,
  * Called via sys_ioctl.  We can use spin_lock_irq() here.
  */
 static int
-uart_ioctl(struct tty_struct *tty, unsigned int cmd,
-	   unsigned long arg)
+uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
 {
 	struct uart_state *state = tty->driver_data;
 	struct tty_port *port = &state->port;
+	struct uart_port *uport;
 	void __user *uarg = (void __user *)arg;
 	int ret = -ENOIOCTLCMD;
 
@@ -1266,8 +1298,9 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd,
 		goto out;
 
 	mutex_lock(&port->mutex);
+	uport = state->uart_port;
 
-	if (tty->flags & (1 << TTY_IO_ERROR)) {
+	if (!uport || test_bit(TTY_IO_ERROR, &tty->flags)) {
 		ret = -EIO;
 		goto out_up;
 	}
@@ -1283,19 +1316,17 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd,
 		break;
 
 	case TIOCGRS485:
-		ret = uart_get_rs485_config(state->uart_port, uarg);
+		ret = uart_get_rs485_config(uport, uarg);
 		break;
 
 	case TIOCSRS485:
-		ret = uart_set_rs485_config(state->uart_port, uarg);
+		ret = uart_set_rs485_config(uport, uarg);
 		break;
-	default: {
-		struct uart_port *uport = state->uart_port;
+	default:
 		if (uport->ops->ioctl)
 			ret = uport->ops->ioctl(uport, cmd, arg);
 		break;
 	}
-	}
 out_up:
 	mutex_unlock(&port->mutex);
 out:
@@ -1305,24 +1336,29 @@ out:
 static void uart_set_ldisc(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
 
-	if (uport->ops->set_ldisc) {
-		mutex_lock(&state->port.mutex);
+	mutex_lock(&state->port.mutex);
+	uport = state->uart_port;
+	if (uport && uport->ops->set_ldisc)
 		uport->ops->set_ldisc(uport, &tty->termios);
-		mutex_unlock(&state->port.mutex);
-	}
+	mutex_unlock(&state->port.mutex);
 }
 
 static void uart_set_termios(struct tty_struct *tty,
 						struct ktermios *old_termios)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
 	unsigned int cflag = tty->termios.c_cflag;
 	unsigned int iflag_mask = IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK;
 	bool sw_changed = false;
 
+	mutex_lock(&state->port.mutex);
+	uport = state->uart_port;
+	if (!uport)
+		goto out;
+
 	/*
 	 * Drivers doing software flow control also need to know
 	 * about changes to these input settings.
@@ -1345,12 +1381,10 @@ static void uart_set_termios(struct tty_struct *tty,
 	    tty->termios.c_ispeed == old_termios->c_ispeed &&
 	    ((tty->termios.c_iflag ^ old_termios->c_iflag) & iflag_mask) == 0 &&
 	    !sw_changed) {
-		return;
+		goto out;
 	}
 
-	mutex_lock(&state->port.mutex);
 	uart_change_speed(tty, state, old_termios);
-	mutex_unlock(&state->port.mutex);
 	/* reload cflag from termios; port driver may have overriden flags */
 	cflag = tty->termios.c_cflag;
 
@@ -1364,6 +1398,8 @@ static void uart_set_termios(struct tty_struct *tty,
 			mask |= TIOCM_RTS;
 		uart_set_mctrl(uport, mask);
 	}
+out:
+	mutex_unlock(&state->port.mutex);
 }
 
 /*
@@ -1659,13 +1695,15 @@ static void uart_line_info(struct seq_file *m, struct uart_driver *drv, int i)
 	struct uart_state *state = drv->state + i;
 	struct tty_port *port = &state->port;
 	enum uart_pm_state pm_state;
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
 	char stat_buf[32];
 	unsigned int status;
 	int mmio;
 
+	mutex_lock(&port->mutex);
+	uport = state->uart_port;
 	if (!uport)
-		return;
+		goto out;
 
 	mmio = uport->iotype >= UPIO_MEM;
 	seq_printf(m, "%d: uart:%s %s%08llX irq:%d",
@@ -1677,11 +1715,10 @@ static void uart_line_info(struct seq_file *m, struct uart_driver *drv, int i)
 
 	if (uport->type == PORT_UNKNOWN) {
 		seq_putc(m, '\n');
-		return;
+		goto out;
 	}
 
 	if (capable(CAP_SYS_ADMIN)) {
-		mutex_lock(&port->mutex);
 		pm_state = state->pm_state;
 		if (pm_state != UART_PM_STATE_ON)
 			uart_change_pm(state, UART_PM_STATE_ON);
@@ -1690,7 +1727,6 @@ static void uart_line_info(struct seq_file *m, struct uart_driver *drv, int i)
 		spin_unlock_irq(&uport->lock);
 		if (pm_state != UART_PM_STATE_ON)
 			uart_change_pm(state, pm_state);
-		mutex_unlock(&port->mutex);
 
 		seq_printf(m, " tx:%d rx:%d",
 				uport->icount.tx, uport->icount.rx);
@@ -1732,6 +1768,9 @@ static void uart_line_info(struct seq_file *m, struct uart_driver *drv, int i)
 	seq_putc(m, '\n');
 #undef STATBIT
 #undef INFOBIT
+out:
+	mutex_unlock(&port->mutex);
+	return;
 }
 
 static int uart_proc_show(struct seq_file *m, void *v)
@@ -1995,11 +2034,11 @@ EXPORT_SYMBOL_GPL(uart_set_options);
 static void uart_change_pm(struct uart_state *state,
 			   enum uart_pm_state pm_state)
 {
-	struct uart_port *port = state->uart_port;
+	struct uart_port *uport = state->uart_port;
 
 	if (state->pm_state != pm_state) {
-		if (port->ops->pm)
-			port->ops->pm(port, pm_state, state->pm_state);
+		if (uport && uport->ops->pm)
+			uport->ops->pm(uport, pm_state, state->pm_state);
 		state->pm_state = pm_state;
 	}
 }
@@ -2803,7 +2842,9 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
 	 */
 	uport->type = PORT_UNKNOWN;
 
+	mutex_lock(&port->mutex);
 	state->uart_port = NULL;
+	mutex_unlock(&port->mutex);
 out:
 	mutex_unlock(&port_mutex);
 
-- 
2.6.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 7/8] serial: core: Prevent unsafe uart port access, part 2
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
                   ` (5 preceding siblings ...)
  2015-12-13  0:36 ` [PATCH 6/8] serial: core: Prevent unsafe uart port access, part 1 Peter Hurley
@ 2015-12-13  0:36 ` Peter Hurley
  2015-12-13  0:36 ` [PATCH 8/8] serial: core: Prevent unsafe uart port access, part 3 Peter Hurley
  2016-01-11  5:29 ` [PATCH 0/8] Fix unsafe uart port access Peter Hurley
  8 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

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(), uart_port_shutdown() and uart_wait_until_sent()).

Split uart_wait_until_sent() into the original tty operation (which
now must claim the port->mutex) and __uart_wait_until_sent() (which
performs the timeout computations and waits); the port->mutex is
released during the sleep and the uart port ptr is reloaded after
wakeup.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/serial/serial_core.c | 66 ++++++++++++++++++++++++++++------------
 1 file changed, 46 insertions(+), 20 deletions(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 2806f52..47a3dc9 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -53,7 +53,7 @@ static struct lock_class_key port_lock_key;
 
 static void uart_change_speed(struct tty_struct *tty, struct uart_state *state,
 					struct ktermios *old_termios);
-static void uart_wait_until_sent(struct tty_struct *tty, int timeout);
+static void __uart_wait_until_sent(struct tty_struct *tty, int timeout);
 static void uart_change_pm(struct uart_state *state,
 			   enum uart_pm_state pm_state);
 
@@ -221,6 +221,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)
 {
@@ -237,7 +239,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 || (tty->termios.c_cflag & HUPCL))
@@ -1406,7 +1408,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)
 {
@@ -1425,18 +1426,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 = state->uart_port;
+
 	/*
 	 * At this point, we stop accepting input.  To do this, we
 	 * disable the receive line status interrupts.
 	 */
-	if (port->flags & ASYNC_INITIALIZED) {
+	if ((port->flags & ASYNC_INITIALIZED) &&
+	    !WARN(!uport, "detached port still initialized!\n")) {
 		spin_lock_irq(&uport->lock);
 		uport->ops->stop_rx(uport);
 		spin_unlock_irq(&uport->lock);
@@ -1445,10 +1449,10 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
 		 * has completely drained; this is especially
 		 * important if there is a transmit FIFO!
 		 */
-		uart_wait_until_sent(tty, uport->timeout);
+		__uart_wait_until_sent(tty, uport->timeout);
+		uport = state->uart_port;
 	}
 
-	mutex_lock(&port->mutex);
 	uart_shutdown(tty, state);
 	tty_port_tty_set(port, NULL);
 
@@ -1459,7 +1463,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);
@@ -1475,13 +1479,13 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
 	mutex_unlock(&port->mutex);
 }
 
-static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
+static void __uart_wait_until_sent(struct tty_struct *tty, int timeout)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port = state->uart_port;
+	struct uart_port *uport = state->uart_port;
 	unsigned long char_time, expire;
 
-	if (port->type == PORT_UNKNOWN || port->fifosize == 0)
+	if (uport->type == PORT_UNKNOWN || uport->fifosize == 0)
 		return;
 
 	/*
@@ -1492,7 +1496,7 @@ static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
 	 * Note: we have to use pretty tight timings here to satisfy
 	 * the NIST-PCTS.
 	 */
-	char_time = (port->timeout - HZ/50) / port->fifosize;
+	char_time = (uport->timeout - HZ/50) / uport->fifosize;
 	char_time = char_time / 5;
 	if (char_time == 0)
 		char_time = 1;
@@ -1508,21 +1512,26 @@ static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
 	 * UART bug of some kind.  So, we clamp the timeout parameter at
 	 * 2*port->timeout.
 	 */
-	if (timeout == 0 || timeout > 2 * port->timeout)
-		timeout = 2 * port->timeout;
+	if (timeout == 0 || timeout > 2 * uport->timeout)
+		timeout = 2 * uport->timeout;
 
 	expire = jiffies + timeout;
 
 	pr_debug("uart_wait_until_sent(%d), jiffies=%lu, expire=%lu...\n",
-		port->line, jiffies, expire);
+		uport->line, jiffies, expire);
 
 	/*
 	 * Check whether the transmitter is empty every 'char_time'.
 	 * 'timeout' / 'expire' give us the maximum amount of time
 	 * we wait.
 	 */
-	while (!port->ops->tx_empty(port)) {
+	while (!uport->ops->tx_empty(uport)) {
+		mutex_unlock(&state->port.mutex);
 		msleep_interruptible(jiffies_to_msecs(char_time));
+		mutex_lock(&state->port.mutex);
+		uport = state->uart_port;
+		if (!uport)
+			break;
 		if (signal_pending(current))
 			break;
 		if (time_after(jiffies, expire))
@@ -1530,6 +1539,16 @@ static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
 	}
 }
 
+static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct uart_state *state = tty->driver_data;
+
+	mutex_lock(&state->port.mutex);
+	if (state->uart_port)
+		__uart_wait_until_sent(tty, timeout);
+	mutex_unlock(&state->port.mutex);
+}
+
 /*
  * Calls to uart_hangup() are serialised by the tty_lock in
  *   drivers/tty/tty_io.c:do_tty_hangup()
@@ -1539,11 +1558,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 = state->uart_port;
+	WARN(!uport, "hangup of detached port!\n");
+
 	if (port->flags & ASYNC_NORMAL_ACTIVE) {
 		uart_flush_buffer(tty);
 		uart_shutdown(tty, state);
@@ -1552,7 +1575,7 @@ static void uart_hangup(struct tty_struct *tty)
 		clear_bit(ASYNCB_NORMAL_ACTIVE, &port->flags);
 		spin_unlock_irqrestore(&port->lock, flags);
 		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);
@@ -1560,6 +1583,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);
@@ -1577,12 +1601,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)
-- 
2.6.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 8/8] serial: core: Prevent unsafe uart port access, part 3
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
                   ` (6 preceding siblings ...)
  2015-12-13  0:36 ` [PATCH 7/8] serial: core: Prevent unsafe uart port access, part 2 Peter Hurley
@ 2015-12-13  0:36 ` Peter Hurley
  2016-01-11  5:29 ` [PATCH 0/8] Fix unsafe uart port access Peter Hurley
  8 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2015-12-13  0:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel, Peter Hurley

For serial core operations without sleeping locks and other waits
(and not already excluded by holding port->mutex), use RCU to
protect dereferencing the state->uart_port.

Introduce helper functions, uart_port_ref() and uart_port_deref(), to
wrap uart_port access in RCU read sections, and helper macros,
uart_port_lock_rcu() and uart_port_unlock_rcu(), to wrap combination
RCU read/uart port lock sections.

For functions only reading the tx circular buffer indexes (where the
uart port lock is claimed to prevent concurrent users), a NULL
uart port is simply ignored and the operation completes.

For functions changing the tx circular buffer indexes (where the
uart port lock is claimed to prevent concurrent users), the operation
is aborted if the uart port is NULL (ie, has been detached).

For functions already holding the port->mutex (and thus guaranteed
safe uart port dereference), introduce helper uart_port_check_rcu(),
which asserts port->mutex is held (if CONFIG_PROVE_RCU).

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
 drivers/tty/serial/8250/8250_port.c |   6 +-
 drivers/tty/serial/serial_core.c    | 312 +++++++++++++++++++++++-------------
 2 files changed, 205 insertions(+), 113 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index ea9d1ce..ced0674 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -2570,7 +2570,8 @@ static int bytes_to_fcr_rxtrig(struct uart_8250_port *up, unsigned char bytes)
 static int do_get_rxtrig(struct tty_port *port)
 {
 	struct uart_state *state = container_of(port, struct uart_state, port);
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = rcu_dereference_protected(state->uart_port,
+						lockdep_is_held(&port->mutex));
 	struct uart_8250_port *up =
 		container_of(uport, struct uart_8250_port, port);
 
@@ -2607,7 +2608,8 @@ static ssize_t serial8250_get_attr_rx_trig_bytes(struct device *dev,
 static int do_set_rxtrig(struct tty_port *port, unsigned char bytes)
 {
 	struct uart_state *state = container_of(port, struct uart_state, port);
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = rcu_dereference_protected(state->uart_port,
+						lockdep_is_held(&port->mutex));
 	struct uart_8250_port *up =
 		container_of(uport, struct uart_8250_port, port);
 	int rxtrig;
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 47a3dc9..e98b16f 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -64,6 +64,46 @@ static int uart_dcd_enabled(struct uart_port *uport)
 	return !!(uport->status & UPSTAT_DCD_ENABLE);
 }
 
+static inline struct uart_port *uart_port_ref(struct uart_state *state)
+{
+	struct uart_port *uport;
+
+	rcu_read_lock();
+	uport = rcu_dereference(state->uart_port);
+	if (!uport)
+		rcu_read_unlock();
+	return uport;
+}
+
+static inline void uart_port_deref(struct uart_port *uport)
+{
+	if (uport)
+		rcu_read_unlock();
+}
+
+static inline struct uart_port *uart_port_check_rcu(struct uart_state *state)
+{
+	return rcu_dereference_check(state->uart_port,
+				     lockdep_is_held(&state->port.mutex));
+}
+
+#define uart_port_lock_rcu(state, flags)				\
+	({								\
+		struct uart_port *__uport = uart_port_ref(state);	\
+		if (__uport)						\
+			spin_lock_irqsave(&__uport->lock, flags);	\
+		__uport;						\
+	})
+
+#define uart_port_unlock_rcu(uport, flags)				\
+	({								\
+		struct uart_port *__uport = uport;			\
+		if (__uport)						\
+			spin_unlock_irqrestore(&__uport->lock, flags);	\
+		uart_port_deref(__uport);				\
+	})
+
+
 /*
  * This routine is used by the interrupt handler to schedule processing in
  * the software interrupt portion of the driver.
@@ -82,32 +122,33 @@ void uart_write_wakeup(struct uart_port *port)
 static void uart_stop(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port = state->uart_port;
+	struct uart_port *uport;
 	unsigned long flags;
 
-	spin_lock_irqsave(&port->lock, flags);
-	port->ops->stop_tx(port);
-	spin_unlock_irqrestore(&port->lock, flags);
+	uport = uart_port_lock_rcu(state, flags);
+	if (uport)
+		uport->ops->stop_tx(uport);
+	uart_port_unlock_rcu(uport, flags);
 }
 
 static void __uart_start(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 
-	if (!uart_tx_stopped(port))
-		port->ops->start_tx(port);
+	if (uport && !uart_tx_stopped(uport))
+		uport->ops->start_tx(uport);
 }
 
 static void uart_start(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port = state->uart_port;
+	struct uart_port *uport;
 	unsigned long flags;
 
-	spin_lock_irqsave(&port->lock, flags);
+	uport = uart_port_lock_rcu(state, flags);
 	__uart_start(tty);
-	spin_unlock_irqrestore(&port->lock, flags);
+	uart_port_unlock_rcu(uport, flags);
 }
 
 static inline void
@@ -134,7 +175,7 @@ uart_update_mctrl(struct uart_port *port, unsigned int set, unsigned int clear)
 static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,
 		int init_hw)
 {
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 	unsigned long page;
 	int retval = 0;
 
@@ -226,7 +267,7 @@ static int uart_startup(struct tty_struct *tty, struct uart_state *state,
  */
 static void uart_shutdown(struct tty_struct *tty, struct uart_state *state)
 {
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 	struct tty_port *port = &state->port;
 
 	/*
@@ -445,7 +486,7 @@ EXPORT_SYMBOL(uart_get_divisor);
 static void uart_change_speed(struct tty_struct *tty, struct uart_state *state,
 					struct ktermios *old_termios)
 {
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 	struct ktermios *termios;
 	int hw_stopped;
 
@@ -490,7 +531,7 @@ static void uart_change_speed(struct tty_struct *tty, struct uart_state *state,
 static int uart_put_char(struct tty_struct *tty, unsigned char c)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port = state->uart_port;
+	struct uart_port *uport;
 	struct circ_buf *circ;
 	unsigned long flags;
 	int ret = 0;
@@ -499,13 +540,13 @@ static int uart_put_char(struct tty_struct *tty, unsigned char c)
 	if (!circ->buf)
 		return 0;
 
-	spin_lock_irqsave(&port->lock, flags);
-	if (uart_circ_chars_free(circ) != 0) {
+	uport = uart_port_lock_rcu(state, flags);
+	if (uport && uart_circ_chars_free(circ) != 0) {
 		circ->buf[circ->head] = c;
 		circ->head = (circ->head + 1) & (UART_XMIT_SIZE - 1);
 		ret = 1;
 	}
-	spin_unlock_irqrestore(&port->lock, flags);
+	uart_port_unlock_rcu(uport, flags);
 	return ret;
 }
 
@@ -518,7 +559,7 @@ static int uart_write(struct tty_struct *tty,
 					const unsigned char *buf, int count)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port;
+	struct uart_port *uport;
 	struct circ_buf *circ;
 	unsigned long flags;
 	int c, ret = 0;
@@ -532,14 +573,12 @@ static int uart_write(struct tty_struct *tty,
 		return -EL3HLT;
 	}
 
-	port = state->uart_port;
 	circ = &state->xmit;
-
 	if (!circ->buf)
 		return 0;
 
-	spin_lock_irqsave(&port->lock, flags);
-	while (1) {
+	uport = uart_port_lock_rcu(state, flags);
+	while (uport) {
 		c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
 		if (count < c)
 			c = count;
@@ -553,39 +592,40 @@ static int uart_write(struct tty_struct *tty,
 	}
 
 	__uart_start(tty);
-	spin_unlock_irqrestore(&port->lock, flags);
-
+	uart_port_unlock_rcu(uport, flags);
 	return ret;
 }
 
 static int uart_write_room(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
+	struct uart_port *uport;
 	unsigned long flags;
 	int ret;
 
-	spin_lock_irqsave(&state->uart_port->lock, flags);
+	uport = uart_port_lock_rcu(state, flags);
 	ret = uart_circ_chars_free(&state->xmit);
-	spin_unlock_irqrestore(&state->uart_port->lock, flags);
+	uart_port_unlock_rcu(uport, flags);
 	return ret;
 }
 
 static int uart_chars_in_buffer(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
+	struct uart_port *uport;
 	unsigned long flags;
 	int ret;
 
-	spin_lock_irqsave(&state->uart_port->lock, flags);
+	uport = uart_port_lock_rcu(state, flags);
 	ret = uart_circ_chars_pending(&state->xmit);
-	spin_unlock_irqrestore(&state->uart_port->lock, flags);
+	uart_port_unlock_rcu(uport, flags);
 	return ret;
 }
 
 static void uart_flush_buffer(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port;
+	struct uart_port *uport;
 	unsigned long flags;
 
 	/*
@@ -597,14 +637,15 @@ static void uart_flush_buffer(struct tty_struct *tty)
 		return;
 	}
 
-	port = state->uart_port;
 	pr_debug("uart_flush_buffer(%d) called\n", tty->index);
 
-	spin_lock_irqsave(&port->lock, flags);
+	uport = uart_port_lock_rcu(state, flags);
+	if (!uport)
+		return;
 	uart_circ_clear(&state->xmit);
-	if (port->ops->flush_buffer)
-		port->ops->flush_buffer(port);
-	spin_unlock_irqrestore(&port->lock, flags);
+	if (uport->ops->flush_buffer)
+		uport->ops->flush_buffer(uport);
+	uart_port_unlock_rcu(uport, flags);
 	tty_wakeup(tty);
 }
 
@@ -622,7 +663,7 @@ static void __uart_send_xchar(struct uart_port *uport, char ch)
 		spin_lock_irqsave(&uport->lock, flags);
 		uport->x_char = ch;
 		if (ch)
-			uport->ops->start_tx(port);
+			uport->ops->start_tx(uport);
 		spin_unlock_irqrestore(&uport->lock, flags);
 	}
 }
@@ -630,57 +671,71 @@ static void __uart_send_xchar(struct uart_port *uport, char ch)
 static void uart_send_xchar(struct tty_struct *tty, char ch)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
 
 	mutex_lock(&state->port.mutex);
-	__uart_send_xchar(uport, ch);
+	uport = uart_port_check_rcu(state);
+	if (uport)
+		__uart_send_xchar(uport, ch);
 	mutex_unlock(&state->port.mutex);
 }
 
 static void uart_throttle(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port = state->uart_port;
+	struct uart_port *uport;
 	upstat_t mask = 0;
 
+	uport = uart_port_ref(state);
+	if (!uport)
+		return;
+
 	if (I_IXOFF(tty))
 		mask |= UPSTAT_AUTOXOFF;
 	if (tty->termios.c_cflag & CRTSCTS)
 		mask |= UPSTAT_AUTORTS;
 
-	if (port->status & mask) {
-		port->ops->throttle(port);
-		mask &= ~port->status;
+	if (uport->status & mask) {
+		uport->ops->throttle(uport);
+		mask &= ~uport->status;
 	}
 
 	if (mask & UPSTAT_AUTOXOFF)
-		__uart_send_xchar(port, STOP_CHAR(tty));
+		__uart_send_xchar(uport, STOP_CHAR(tty));
 
 	if (mask & UPSTAT_AUTORTS)
-		uart_clear_mctrl(port, TIOCM_RTS);
+		uart_clear_mctrl(uport, TIOCM_RTS);
+
+	uart_port_deref(uport);
 }
 
 static void uart_unthrottle(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *port = state->uart_port;
+	struct uart_port *uport;
 	upstat_t mask = 0;
 
+	uport = uart_port_ref(state);
+	if (!uport)
+		return;
+
 	if (I_IXOFF(tty))
 		mask |= UPSTAT_AUTOXOFF;
 	if (tty->termios.c_cflag & CRTSCTS)
 		mask |= UPSTAT_AUTORTS;
 
-	if (port->status & mask) {
-		port->ops->unthrottle(port);
-		mask &= ~port->status;
+	if (uport->status & mask) {
+		uport->ops->unthrottle(uport);
+		mask &= ~uport->status;
 	}
 
 	if (mask & UPSTAT_AUTOXOFF)
-		__uart_send_xchar(port, START_CHAR(tty));
+		__uart_send_xchar(uport, START_CHAR(tty));
 
 	if (mask & UPSTAT_AUTORTS)
-		uart_set_mctrl(port, TIOCM_RTS);
+		uart_set_mctrl(uport, TIOCM_RTS);
+
+	uart_port_deref(uport);
 }
 
 static int uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
@@ -696,7 +751,7 @@ static int uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
 	 * occur as we go
 	 */
 	mutex_lock(&port->mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (!uport)
 		goto out;
 
@@ -742,7 +797,7 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port,
 			 struct uart_state *state,
 			 struct serial_struct *new_info)
 {
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 	unsigned long new_port;
 	unsigned int change_irq, change_port, closing_wait;
 	unsigned int old_custom_divisor, close_delay;
@@ -965,7 +1020,7 @@ static int uart_set_info_user(struct tty_struct *tty, struct uart_state *state,
 static int uart_get_lsr_info(struct tty_struct *tty,
 			struct uart_state *state, unsigned int __user *value)
 {
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 	unsigned int result;
 
 	result = uport->ops->tx_empty(uport);
@@ -992,7 +1047,7 @@ static int uart_tiocmget(struct tty_struct *tty)
 	int result = -EIO;
 
 	mutex_lock(&port->mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (!uport)
 		goto out;
 
@@ -1016,7 +1071,7 @@ uart_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear)
 	int ret = -EIO;
 
 	mutex_lock(&port->mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (!uport)
 		goto out;
 
@@ -1037,7 +1092,7 @@ static int uart_break_ctl(struct tty_struct *tty, int break_state)
 	int ret = -EIO;
 
 	mutex_lock(&port->mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (!uport)
 		goto out;
 
@@ -1066,7 +1121,7 @@ static int uart_do_autoconfig(struct tty_struct *tty,struct uart_state *state)
 	if (mutex_lock_interruptible(&port->mutex))
 		return -ERESTARTSYS;
 
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (!uport) {
 		ret = -EIO;
 		goto out;
@@ -1118,28 +1173,32 @@ static void uart_enable_ms(struct uart_port *uport)
  * FIXME: This wants extracting into a common all driver implementation
  * of TIOCMWAIT using tty_port.
  */
-static int
-uart_wait_modem_status(struct uart_state *state, unsigned long arg)
+static int uart_wait_modem_status(struct uart_state *state, unsigned long arg)
 {
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
 	struct tty_port *port = &state->port;
 	DECLARE_WAITQUEUE(wait, current);
 	struct uart_icount cprev, cnow;
-	int ret;
+	unsigned long flags;
+	int ret = -EIO;
 
 	/*
 	 * note the counters on entry
 	 */
-	spin_lock_irq(&uport->lock);
-	memcpy(&cprev, &uport->icount, sizeof(struct uart_icount));
+	uport = uart_port_lock_rcu(state, flags);
+	if (!uport)
+		return -EIO;
+	memcpy(&cprev, &uport->icount, sizeof(cprev));
 	uart_enable_ms(uport);
-	spin_unlock_irq(&uport->lock);
+	uart_port_unlock_rcu(uport, flags);
 
 	add_wait_queue(&port->delta_msr_wait, &wait);
 	for (;;) {
-		spin_lock_irq(&uport->lock);
-		memcpy(&cnow, &uport->icount, sizeof(struct uart_icount));
-		spin_unlock_irq(&uport->lock);
+		uport = uart_port_lock_rcu(state, flags);
+		if (!uport)
+			break;
+		memcpy(&cnow, &uport->icount, sizeof(cnow));
+		uart_port_unlock_rcu(uport, flags);
 
 		set_current_state(TASK_INTERRUPTIBLE);
 
@@ -1178,11 +1237,14 @@ static int uart_get_icount(struct tty_struct *tty,
 {
 	struct uart_state *state = tty->driver_data;
 	struct uart_icount cnow;
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
+	unsigned long flags;
 
-	spin_lock_irq(&uport->lock);
+	uport = uart_port_lock_rcu(state, flags);
+	if (!uport)
+		return -EIO;
 	memcpy(&cnow, &uport->icount, sizeof(struct uart_icount));
-	spin_unlock_irq(&uport->lock);
+	uart_port_unlock_rcu(uport, flags);
 
 	icount->cts         = cnow.cts;
 	icount->dsr         = cnow.dsr;
@@ -1300,7 +1362,7 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
 		goto out;
 
 	mutex_lock(&port->mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 
 	if (!uport || test_bit(TTY_IO_ERROR, &tty->flags)) {
 		ret = -EIO;
@@ -1341,7 +1403,7 @@ static void uart_set_ldisc(struct tty_struct *tty)
 	struct uart_port *uport;
 
 	mutex_lock(&state->port.mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (uport && uport->ops->set_ldisc)
 		uport->ops->set_ldisc(uport, &tty->termios);
 	mutex_unlock(&state->port.mutex);
@@ -1357,7 +1419,7 @@ static void uart_set_termios(struct tty_struct *tty,
 	bool sw_changed = false;
 
 	mutex_lock(&state->port.mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (!uport)
 		goto out;
 
@@ -1433,7 +1495,7 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
 		return;
 
 	mutex_lock(&port->mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 
 	/*
 	 * At this point, we stop accepting input.  To do this, we
@@ -1450,7 +1512,7 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
 		 * important if there is a transmit FIFO!
 		 */
 		__uart_wait_until_sent(tty, uport->timeout);
-		uport = state->uart_port;
+		uport = uart_port_check_rcu(state);
 	}
 
 	uart_shutdown(tty, state);
@@ -1482,7 +1544,7 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
 static void __uart_wait_until_sent(struct tty_struct *tty, int timeout)
 {
 	struct uart_state *state = tty->driver_data;
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 	unsigned long char_time, expire;
 
 	if (uport->type == PORT_UNKNOWN || uport->fifosize == 0)
@@ -1529,7 +1591,7 @@ static void __uart_wait_until_sent(struct tty_struct *tty, int timeout)
 		mutex_unlock(&state->port.mutex);
 		msleep_interruptible(jiffies_to_msecs(char_time));
 		mutex_lock(&state->port.mutex);
-		uport = state->uart_port;
+		uport = uart_port_check_rcu(state);
 		if (!uport)
 			break;
 		if (signal_pending(current))
@@ -1542,9 +1604,11 @@ static void __uart_wait_until_sent(struct tty_struct *tty, int timeout)
 static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
 {
 	struct uart_state *state = tty->driver_data;
+	struct uart_port *uport;
 
 	mutex_lock(&state->port.mutex);
-	if (state->uart_port)
+	uport = uart_port_check_rcu(state);
+	if (uport)
 		__uart_wait_until_sent(tty, timeout);
 	mutex_unlock(&state->port.mutex);
 }
@@ -1564,7 +1628,7 @@ static void uart_hangup(struct tty_struct *tty)
 	pr_debug("uart_hangup(%d)\n", tty->index);
 
 	mutex_lock(&port->mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	WARN(!uport, "hangup of detached port!\n");
 
 	if (port->flags & ASYNC_NORMAL_ACTIVE) {
@@ -1587,7 +1651,7 @@ static void uart_hangup(struct tty_struct *tty)
 static void uart_port_shutdown(struct tty_port *port)
 {
 	struct uart_state *state = container_of(port, struct uart_state, port);
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 
 	/*
 	 * clear delta_msr_wait queue to avoid mem leaks: we may free
@@ -1614,26 +1678,40 @@ static void uart_port_shutdown(struct tty_port *port)
 static int uart_carrier_raised(struct tty_port *port)
 {
 	struct uart_state *state = container_of(port, struct uart_state, port);
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
+	unsigned long flags;
 	int mctrl;
-	spin_lock_irq(&uport->lock);
+
+	uport = uart_port_lock_rcu(state, flags);
+	/*
+	 * Should never observe uport == NULL since checks for hangup should
+	 * abort the tty_port_block_til_ready() loop before checking for carrier
+	 * raised -- but report carrier raised if it does to cause open to
+	 * continue and not sleep
+	 */
+	if (WARN_ON(!uport))
+		return 1;
 	uart_enable_ms(uport);
 	mctrl = uport->ops->get_mctrl(uport);
-	spin_unlock_irq(&uport->lock);
-	if (mctrl & TIOCM_CAR)
-		return 1;
-	return 0;
+	uart_port_unlock_rcu(uport, flags);
+	return !!(mctrl & TIOCM_CAR);
 }
 
 static void uart_dtr_rts(struct tty_port *port, int onoff)
 {
 	struct uart_state *state = container_of(port, struct uart_state, port);
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport;
+
+	uport = uart_port_ref(state);
+	if (!uport)
+		return;
 
 	if (onoff)
 		uart_set_mctrl(uport, TIOCM_DTR | TIOCM_RTS);
 	else
 		uart_clear_mctrl(uport, TIOCM_DTR | TIOCM_RTS);
+
+	uart_port_deref(uport);
 }
 
 /*
@@ -1652,6 +1730,7 @@ static int uart_open(struct tty_struct *tty, struct file *filp)
 	int retval, line = tty->index;
 	struct uart_state *state = drv->state + line;
 	struct tty_port *port = &state->port;
+	struct uart_port *uport;
 
 	pr_debug("uart_open(%d) called\n", line);
 
@@ -1671,15 +1750,15 @@ static int uart_open(struct tty_struct *tty, struct file *filp)
 		goto end;
 	}
 
-	if (!state->uart_port || state->uart_port->flags & UPF_DEAD) {
+	uport = uart_port_check_rcu(state);
+	if (!uport || (uport->flags & UPF_DEAD)) {
 		retval = -ENXIO;
 		goto err_unlock;
 	}
 
 	tty->driver_data = state;
-	state->uart_port->state = state;
-	state->port.low_latency =
-		(state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
+	uport->state = state;
+	port->low_latency = !!(uport->flags & UPF_LOW_LATENCY);
 	tty_port_tty_set(port, tty);
 
 	/*
@@ -1727,7 +1806,7 @@ static void uart_line_info(struct seq_file *m, struct uart_driver *drv, int i)
 	int mmio;
 
 	mutex_lock(&port->mutex);
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (!uport)
 		goto out;
 
@@ -2060,7 +2139,7 @@ EXPORT_SYMBOL_GPL(uart_set_options);
 static void uart_change_pm(struct uart_state *state,
 			   enum uart_pm_state pm_state)
 {
-	struct uart_port *uport = state->uart_port;
+	struct uart_port *uport = uart_port_check_rcu(state);
 
 	if (state->pm_state != pm_state) {
 		if (uport && uport->ops->pm)
@@ -2347,7 +2426,7 @@ static int uart_poll_init(struct tty_driver *driver, int line, char *options)
 	port = &state->port;
 	mutex_lock(&port->mutex);
 
-	uport = state->uart_port;
+	uport = uart_port_check_rcu(state);
 	if (!(uport->ops->poll_get_char && uport->ops->poll_put_char))
 		goto out;
 
@@ -2374,29 +2453,35 @@ static int uart_poll_get_char(struct tty_driver *driver, int line)
 {
 	struct uart_driver *drv = driver->driver_state;
 	struct uart_state *state = drv->state + line;
-	struct uart_port *port;
+	struct uart_port *uport;
+	int ret = -1;
 
-	if (!state || !state->uart_port)
+	if (!state)
 		return -1;
 
-	port = state->uart_port;
-	return port->ops->poll_get_char(port);
+	uport = uart_port_ref(state);
+	if (uport)
+		ret = uport->ops->poll_get_char(uport);
+	uart_port_deref(uport);
+	return ret;
 }
 
 static void uart_poll_put_char(struct tty_driver *driver, int line, char ch)
 {
 	struct uart_driver *drv = driver->driver_state;
 	struct uart_state *state = drv->state + line;
-	struct uart_port *port;
+	struct uart_port *uport;
 
-	if (!state || !state->uart_port)
+	if (!state)
 		return;
 
-	port = state->uart_port;
-
+	uport = uart_port_ref(state);
+	if (!uport)
+		return;
 	if (ch == '\n')
-		port->ops->poll_put_char(port, '\r');
-	port->ops->poll_put_char(port, ch);
+		uport->ops->poll_put_char(uport, '\r');
+	uport->ops->poll_put_char(uport, ch);
+	uart_port_deref(uport);
 }
 #endif
 
@@ -2737,13 +2822,13 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
 
 	mutex_lock(&port_mutex);
 	mutex_lock(&port->mutex);
-	if (state->uart_port) {
+	if (uart_port_check_rcu(state)) {
 		ret = -EINVAL;
 		goto out;
 	}
 
 	/* Link the port to the driver state table and vice versa */
-	state->uart_port = uport;
+	rcu_assign_pointer(state->uart_port, uport);
 	uport->state = state;
 
 	state->pm_state = UART_PM_STATE_UNDEFINED;
@@ -2815,15 +2900,12 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
 {
 	struct uart_state *state = drv->state + uport->line;
 	struct tty_port *port = &state->port;
+	struct uart_port *uart_port;
 	struct tty_struct *tty;
 	int ret = 0;
 
 	BUG_ON(in_interrupt());
 
-	if (state->uart_port != uport)
-		dev_alert(uport->dev, "Removing wrong port: %p != %p\n",
-			state->uart_port, uport);
-
 	mutex_lock(&port_mutex);
 
 	/*
@@ -2831,7 +2913,12 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
 	 * succeeding while we shut down the port.
 	 */
 	mutex_lock(&port->mutex);
-	if (!state->uart_port) {
+	uart_port = uart_port_check_rcu(state);
+	if (uart_port != uport)
+		dev_alert(uport->dev, "Removing wrong port: %p != %p\n",
+			  uart_port, uport);
+
+	if (!uart_port) {
 		mutex_unlock(&port->mutex);
 		ret = -EINVAL;
 		goto out;
@@ -2869,8 +2956,11 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
 	uport->type = PORT_UNKNOWN;
 
 	mutex_lock(&port->mutex);
-	state->uart_port = NULL;
+	rcu_assign_pointer(state->uart_port, NULL);
 	mutex_unlock(&port->mutex);
+
+	/* wait for existing uart port RCU read sections to complete */
+	synchronize_rcu();
 out:
 	mutex_unlock(&port_mutex);
 
-- 
2.6.3


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 4/8] serial: core: Take port mutex for send_xchar() tty method
  2015-12-13  0:36 ` [PATCH 4/8] serial: core: Take port mutex for send_xchar() tty method Peter Hurley
@ 2016-01-11  5:23   ` Peter Hurley
  0 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2016-01-11  5:23 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel

On 12/12/2015 04:36 PM, Peter Hurley wrote:
> Prepare for separate methods of preventing unsafe uart port access
> when sending xchar; claim port mutex for the tty ops method (which
> might sleep) and rcu for the throttle/unthrottle uses.
> 
> The implied limitation is that uart drivers which support
> AUTOXOFF flow control cannot define a send_xchar() method that sleeps.

This patch doesn't do what I want, so I need to rework this solution.

Regards,
Peter Hurley


> Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
> ---
>  drivers/tty/serial/serial_core.c | 30 +++++++++++++++++++-----------
>  1 file changed, 19 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> index 4430518..806eba81 100644
> --- a/drivers/tty/serial/serial_core.c
> +++ b/drivers/tty/serial/serial_core.c
> @@ -610,23 +610,31 @@ static void uart_flush_buffer(struct tty_struct *tty)
>   * This function is used to send a high-priority XON/XOFF character to
>   * the device
>   */
> -static void uart_send_xchar(struct tty_struct *tty, char ch)
> +static void __uart_send_xchar(struct uart_port *uport, char ch)
>  {
> -	struct uart_state *state = tty->driver_data;
> -	struct uart_port *port = state->uart_port;
>  	unsigned long flags;
>  
> -	if (port->ops->send_xchar)
> -		port->ops->send_xchar(port, ch);
> +	if (uport->ops->send_xchar)
> +		uport->ops->send_xchar(uport, ch);
>  	else {
> -		spin_lock_irqsave(&port->lock, flags);
> -		port->x_char = ch;
> +		spin_lock_irqsave(&uport->lock, flags);
> +		uport->x_char = ch;
>  		if (ch)
> -			port->ops->start_tx(port);
> -		spin_unlock_irqrestore(&port->lock, flags);
> +			uport->ops->start_tx(port);
> +		spin_unlock_irqrestore(&uport->lock, flags);
>  	}
>  }
>  
> +static void uart_send_xchar(struct tty_struct *tty, char ch)
> +{
> +	struct uart_state *state = tty->driver_data;
> +	struct uart_port *uport = state->uart_port;
> +
> +	mutex_lock(&state->port.mutex);
> +	__uart_send_xchar(uport, ch);
> +	mutex_unlock(&state->port.mutex);
> +}
> +
>  static void uart_throttle(struct tty_struct *tty)
>  {
>  	struct uart_state *state = tty->driver_data;
> @@ -644,7 +652,7 @@ static void uart_throttle(struct tty_struct *tty)
>  	}
>  
>  	if (mask & UPSTAT_AUTOXOFF)
> -		uart_send_xchar(tty, STOP_CHAR(tty));
> +		__uart_send_xchar(port, STOP_CHAR(tty));
>  
>  	if (mask & UPSTAT_AUTORTS)
>  		uart_clear_mctrl(port, TIOCM_RTS);
> @@ -667,7 +675,7 @@ static void uart_unthrottle(struct tty_struct *tty)
>  	}
>  
>  	if (mask & UPSTAT_AUTOXOFF)
> -		uart_send_xchar(tty, START_CHAR(tty));
> +		__uart_send_xchar(port, START_CHAR(tty));
>  
>  	if (mask & UPSTAT_AUTORTS)
>  		uart_set_mctrl(port, TIOCM_RTS);
> 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 0/8] Fix unsafe uart port access
  2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
                   ` (7 preceding siblings ...)
  2015-12-13  0:36 ` [PATCH 8/8] serial: core: Prevent unsafe uart port access, part 3 Peter Hurley
@ 2016-01-11  5:29 ` Peter Hurley
  8 siblings, 0 replies; 11+ messages in thread
From: Peter Hurley @ 2016-01-11  5:29 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Jiri Slaby, linux-kernel

On 12/12/2015 04:36 PM, Peter Hurley wrote:
> Hi Greg,
> 
> The serial core has always intended to allow uart drivers to detach and
> unload, even while ttys are open and running. Since the serial core is
> the actual tty driver, it acts as a proxy for the uart driver, which
> allows the uart driver to remove the port (which hangs up the tty) and
> then unload.
> 
> However, all of this is broken.
> 
> Firstly, there is no mechanism for blocking the uart port removal until
> current operations are complete. The tty core does not, and never has,
> serialized ioctl() or any other tty operation other than open/close
> with hangup.
> 
> Secondly, uart_register_driver() directly binds data from the uart driver
> with the tty core, rather than providing proxy copies _without taking
> module references to the uart driver_. This produces some spectacular
> results if the tty is in-use when the uart driver unloads.
> 
> Thirdly, uart_unregister_driver() immediately destroys the tty port
> despite that it may be in use, and will continue to be for the
> lifetime of the tty, which is unbounded (userspace may retain open
> ttys forever).
> 
> This series addresses the first problem of ensuring safe uart port
> dereferencing with concurrent uart port removal. Two distinct
> mechanisms are used to provide the necessary safe dereference: the
> tty_port mutex and RCU.
> 
> By serializing the uart port detach (ie, reset to NULL) with the
> tty_port mutex, existing sections of the serial core that already
> hold the port mutex are guaranteed the uart port detach will not
> be concurrent, and so can simply test for NULL value before first
> dereference.
> 
> Most of the existing serial core that holds the port mutex is
> ioctl-related and so can return -EIO as if the tty has been hungup
> (which it has been).
> 
> Other portions of the serial core that sleep (eg. uart_wait_until_sent())
> also now claim the port mutex to prevent concurrent uart port detach,
> and thus protect the reference. The port mutex is dropped for the
> actual sleep, retaken on wakeup and the uart port is re-checked
> for NULL.
> 
> For portions of the serial core that don't sleep, RCU is used to
> defer actual uart port teardown after detach (with synchronize_rcu())
> until the current holders of rcu_read_lock() are complete.
> 
> The xmit buffer operations that don't modify the buffer indexes --
> uart_write_room() and uart_chars_in_buffer() -- just report the state
> of the xmit buffer anyway if the uart port has been removed, despite
> that the xmit buffer is no longer lockable (the lock is in the
> uart_port that is now gone).
> 
> Xmit buffer operations that do modify the buffer indexes are aborted.
> 
> Extra care is taken with the close/hangup/shutdown paths since this
> must continue to perform other work even if the uart port has been
> removed (for example, if the uart port was /dev/console and so will
> only be closed when the file descriptor is released).
> 
> I do have a plan for the second and third problems but this series
> does not implement those.
> 
> Regards,
> 
> Peter Hurley (8):
>   serial: core: Fold __uart_put_char() into caller
>   serial: core: Fold do_uart_get_info() into caller
>   serial: core: Use tty->index for port # in debug messages

Hi Greg,

I split the first three patches above and submitted those
in the "Misc serial cleanups" v2 series.

For the remaining patches below, I need to rework how to
guarantee the lifespan of uart port during throttle/unthrottle,
and then rebase the following patches.

Regards
Peter Hurley

>   serial: core: Take port mutex for send_xchar() tty method
>   serial: core: Expand port mutex section in uart_poll_init()
>   serial: core: Prevent unsafe uart port access, part 1
>   serial: core: Prevent unsafe uart port access, part 2
>   serial: core: Prevent unsafe uart port access, part 3
> 
>  drivers/tty/serial/8250/8250_port.c |   6 +-
>  drivers/tty/serial/serial_core.c    | 547 +++++++++++++++++++++++-------------
>  2 files changed, 355 insertions(+), 198 deletions(-)
> 

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2016-01-11  5:29 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-12-13  0:36 [PATCH 0/8] Fix unsafe uart port access Peter Hurley
2015-12-13  0:36 ` [PATCH 1/8] serial: core: Fold __uart_put_char() into caller Peter Hurley
2015-12-13  0:36 ` [PATCH 2/8] serial: core: Fold do_uart_get_info() " Peter Hurley
2015-12-13  0:36 ` [PATCH 3/8] serial: core: Use tty->index for port # in debug messages Peter Hurley
2015-12-13  0:36 ` [PATCH 4/8] serial: core: Take port mutex for send_xchar() tty method Peter Hurley
2016-01-11  5:23   ` Peter Hurley
2015-12-13  0:36 ` [PATCH 5/8] serial: core: Expand port mutex section in uart_poll_init() Peter Hurley
2015-12-13  0:36 ` [PATCH 6/8] serial: core: Prevent unsafe uart port access, part 1 Peter Hurley
2015-12-13  0:36 ` [PATCH 7/8] serial: core: Prevent unsafe uart port access, part 2 Peter Hurley
2015-12-13  0:36 ` [PATCH 8/8] serial: core: Prevent unsafe uart port access, part 3 Peter Hurley
2016-01-11  5:29 ` [PATCH 0/8] Fix unsafe uart port access Peter Hurley

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.