serial: fix race between flush_to_ldisc and tty_open [Linux 4.9.156]

This Linux kernel change "serial: fix race between flush_to_ldisc and tty_open" is included in the Linux 4.9.156 release. This change is authored by Greg Kroah-Hartman <gregkh [at] linuxfoundation.org> on Thu Jan 31 17:43:16 2019 +0800. The commit for this change in Linux stable tree is d13ed61 (patch) which is from upstream commit fedb576. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream fedb576.

serial: fix race between flush_to_ldisc and tty_open

commit fedb5760648a291e949f2380d383b5b2d2749b5e upstream.

There still is a race window after the commit b027e2298bd588
("tty: fix data race between tty_init_dev and flush of buf"),
and we encountered this crash issue if receive_buf call comes
before tty initialization completes in tty_open and
tty->driver_data may be NULL.

CPU0                                    CPU1
----                                    ----
                                  tty_open
                                   tty_init_dev
                                     tty_ldisc_unlock
                                       schedule
flush_to_ldisc
 receive_buf
  tty_port_default_receive_buf
   tty_ldisc_receive_buf
    n_tty_receive_buf_common
      __receive_buf
       uart_flush_chars
        uart_start
        /*tty->driver_data is NULL*/
                                   tty->ops->open
                                   /*init tty->driver_data*/

it can be fixed by extending ldisc semaphore lock in tty_init_dev
to driver_data initialized completely after tty->ops->open(), but
this will lead to get lock on one function and unlock in some other
function, and hard to maintain, so fix this race only by checking
tty->driver_data when receiving, and return if tty->driver_data
is NULL, and n_tty_receive_buf_common maybe calls uart_unthrottle,
so add the same check.

Because the tty layer knows nothing about the driver associated with the
device, the tty layer can not do anything here, it is up to the tty
driver itself to check for this type of race.  Fix up the serial driver
to correctly check to see if it is finished binding with the device when
being called, and if not, abort the tty calls.

[Description and problem report and testing from Li RongQing, I rewrote
the patch to be in the serial layer, not in the tty core - gregkh]

Reported-by: Li RongQing <lirongqing@baidu.com>
Tested-by: Li RongQing <lirongqing@baidu.com>
Signed-off-by: Wang Li <wangli39@baidu.com>
Signed-off-by: Zhang Yu <zhangyu31@baidu.com>
Signed-off-by: Li RongQing <lirongqing@baidu.com>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

There are 6 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/tty/serial/serial_core.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 95fc7e8..55ed4c6 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -141,6 +141,9 @@ static void uart_start(struct tty_struct *tty)
    struct uart_port *port;
    unsigned long flags;

+   if (!state)
+       return;
+
    port = uart_port_lock(state, flags);
    __uart_start(tty);
    uart_port_unlock(port, flags);
@@ -717,6 +720,9 @@ static void uart_unthrottle(struct tty_struct *tty)
    struct uart_port *port;
    upstat_t mask = 0;

+   if (!state)
+       return;
+
    port = uart_port_ref(state);
    if (!port)
        return;

Leave a Reply

Your email address will not be published. Required fields are marked *