From 80c8e52837206e5c61cffd35183798b64e659675 Mon Sep 17 00:00:00 2001 From: Sergey Senozhatsky Date: Thu, 13 Dec 2018 13:58:39 +0900 Subject: tty/serial: do not free trasnmit buffer page under port lock [ Upstream commit d72402145ace0697a6a9e8e75a3de5bf3375f78d ] LKP has hit yet another circular locking dependency between uart console drivers and debugobjects [1]: CPU0 CPU1 rhltable_init() __init_work() debug_object_init uart_shutdown() /* db->lock */ /* uart_port->lock */ debug_print_object() free_page() printk() call_console_drivers() debug_check_no_obj_freed() /* uart_port->lock */ /* db->lock */ debug_print_object() So there are two dependency chains: uart_port->lock -> db->lock And db->lock -> uart_port->lock This particular circular locking dependency can be addressed in several ways: a) One way would be to move debug_print_object() out of db->lock scope and, thus, break the db->lock -> uart_port->lock chain. b) Another one would be to free() transmit buffer page out of db->lock in UART code; which is what this patch does. It makes sense to apply a) and b) independently: there are too many things going on behind free(), none of which depend on uart_port->lock. The patch fixes transmit buffer page free() in uart_shutdown() and, additionally, in uart_port_startup() (as was suggested by Dmitry Safonov). [1] https://lore.kernel.org/lkml/20181211091154.GL23332@shao2-debian/T/#u Signed-off-by: Sergey Senozhatsky Reviewed-by: Petr Mladek Acked-by: Peter Zijlstra (Intel) Cc: Greg Kroah-Hartman Cc: Jiri Slaby Cc: Andrew Morton Cc: Waiman Long Cc: Dmitry Safonov Cc: Steven Rostedt Signed-off-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin --- drivers/tty/serial/serial_core.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'drivers/tty/serial/serial_core.c') diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 53e6db8b0330..bcfdaf6ddbb2 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -195,10 +195,15 @@ static int uart_port_startup(struct tty_struct *tty, struct uart_state *state, if (!state->xmit.buf) { state->xmit.buf = (unsigned char *) page; uart_circ_clear(&state->xmit); + uart_port_unlock(uport, flags); } else { + uart_port_unlock(uport, flags); + /* + * Do not free() the page under the port lock, see + * uart_shutdown(). + */ free_page(page); } - uart_port_unlock(uport, flags); retval = uport->ops->startup(uport); if (retval == 0) { @@ -258,6 +263,7 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state) struct uart_port *uport = uart_port_check(state); struct tty_port *port = &state->port; unsigned long flags = 0; + char *xmit_buf = NULL; /* * Set the TTY IO error marker @@ -288,14 +294,18 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state) tty_port_set_suspended(port, 0); /* - * Free the transmit buffer page. + * Do not free() the transmit buffer page under the port lock since + * this can create various circular locking scenarios. For instance, + * console driver may need to allocate/free a debug object, which + * can endup in printk() recursion. */ uart_port_lock(state, flags); - if (state->xmit.buf) { - free_page((unsigned long)state->xmit.buf); - state->xmit.buf = NULL; - } + xmit_buf = state->xmit.buf; + state->xmit.buf = NULL; uart_port_unlock(uport, flags); + + if (xmit_buf) + free_page((unsigned long)xmit_buf); } /** -- cgit v1.2.3 From b7bd4a2b4263853043ab2033cbc796da3d75f0f1 Mon Sep 17 00:00:00 2001 From: Samir Virmani Date: Wed, 16 Jan 2019 10:28:07 -0800 Subject: uart: Fix crash in uart_write and uart_put_char commit aff9cf5955185d1f183227e46c5f8673fa483813 upstream. We were experiencing a crash similar to the one reported as part of commit:a5ba1d95e46e ("uart: fix race between uart_put_char() and uart_shutdown()") in our testbed as well. We continue to observe the same crash after integrating the commit a5ba1d95e46e ("uart: fix race between uart_put_char() and uart_shutdown()") On reviewing the change, the port lock should be taken prior to checking for if (!circ->buf) in fn. __uart_put_char and other fns. that update the buffer uart_state->xmit. Traceback: [11/27/2018 06:24:32.4870] Unable to handle kernel NULL pointer dereference at virtual address 0000003b [11/27/2018 06:24:32.4950] PC is at memcpy+0x48/0x180 [11/27/2018 06:24:32.4950] LR is at uart_write+0x74/0x120 [11/27/2018 06:24:32.4950] pc : [] lr : [] pstate: 000001c5 [11/27/2018 06:24:32.4950] sp : ffffffc076433d30 [11/27/2018 06:24:32.4950] x29: ffffffc076433d30 x28: 0000000000000140 [11/27/2018 06:24:32.4950] x27: ffffffc0009b9d5e x26: ffffffc07ce36580 [11/27/2018 06:24:32.4950] x25: 0000000000000000 x24: 0000000000000140 [11/27/2018 06:24:32.4950] x23: ffffffc000891200 x22: ffffffc01fc34000 [11/27/2018 06:24:32.4950] x21: 0000000000000fff x20: 0000000000000076 [11/27/2018 06:24:32.4950] x19: 0000000000000076 x18: 0000000000000000 [11/27/2018 06:24:32.4950] x17: 000000000047cf08 x16: ffffffc000099e68 [11/27/2018 06:24:32.4950] x15: 0000000000000018 x14: 776d726966205948 [11/27/2018 06:24:32.4950] x13: 50203a6c6974755f x12: 74647075205d3333 [11/27/2018 06:24:32.4950] x11: 3a35323a36203831 x10: 30322f37322f3131 [11/27/2018 06:24:32.4950] x9 : 5b205d303638342e x8 : 746164206f742070 [11/27/2018 06:24:32.4950] x7 : 7520736920657261 x6 : 000000000000003b [11/27/2018 06:24:32.4950] x5 : 000000000000817a x4 : 0000000000000008 [11/27/2018 06:24:32.4950] x3 : 2f37322f31312a5b x2 : 000000000000006e [11/27/2018 06:24:32.4950] x1 : ffffffc0009b9cf0 x0 : 000000000000003b [11/27/2018 06:24:32.4950] CPU2: stopping [11/27/2018 06:24:32.4950] CPU: 2 PID: 0 Comm: swapper/2 Tainted: P D O 4.1.51 #3 [11/27/2018 06:24:32.4950] Hardware name: Broadcom-v8A (DT) [11/27/2018 06:24:32.4950] Call trace: [11/27/2018 06:24:32.4950] [] dump_backtrace+0x0/0x150 [11/27/2018 06:24:32.4950] [] show_stack+0x14/0x20 [11/27/2018 06:24:32.4950] [] dump_stack+0x90/0xb0 [11/27/2018 06:24:32.4950] [] handle_IPI+0x18c/0x1a0 [11/27/2018 06:24:32.4950] [] gic_handle_irq+0x88/0x90 Fixes: a5ba1d95e46e ("uart: fix race between uart_put_char() and uart_shutdown()") Cc: stable Signed-off-by: Samir Virmani Acked-by: Tycho Andersen Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/tty/serial/serial_core.c') diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index bcfdaf6ddbb2..95fc7e893fd2 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -540,10 +540,12 @@ static int uart_put_char(struct tty_struct *tty, unsigned char c) int ret = 0; circ = &state->xmit; - if (!circ->buf) + port = uart_port_lock(state, flags); + if (!circ->buf) { + uart_port_unlock(port, flags); return 0; + } - port = uart_port_lock(state, flags); if (port && uart_circ_chars_free(circ) != 0) { circ->buf[circ->head] = c; circ->head = (circ->head + 1) & (UART_XMIT_SIZE - 1); @@ -576,11 +578,13 @@ static int uart_write(struct tty_struct *tty, return -EL3HLT; } + port = uart_port_lock(state, flags); circ = &state->xmit; - if (!circ->buf) + if (!circ->buf) { + uart_port_unlock(port, flags); return 0; + } - port = uart_port_lock(state, flags); while (port) { c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE); if (count < c) -- cgit v1.2.3 From d13ed61172034ca603cc640b052217c42d2e4f84 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 31 Jan 2019 17:43:16 +0800 Subject: 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 Tested-by: Li RongQing Signed-off-by: Wang Li Signed-off-by: Zhang Yu Signed-off-by: Li RongQing Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/tty/serial/serial_core.c') diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 95fc7e893fd2..55ed4c66f77f 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; -- cgit v1.2.3