* FAILED: patch "[PATCH] serial: fix ioctl hangup race" failed to apply to 6.12-stable tree
@ 2026-10-05 10:15 gregkh
2026-10-05 22:24 ` [PATCH 6.12.y 1/3] serial: introduce uart_port_lock() guard()s Sasha Levin
0 siblings, 1 reply; 4+ messages in thread
From: gregkh @ 2026-10-05 10:15 UTC (permalink / raw)
To: johan, gregkh; +Cc: stable
The patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 8ca59f7baee7c72cf7729950b4ab2e7f910c9549
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026100556-skincare-lumping-5e28@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 8ca59f7baee7c72cf7729950b4ab2e7f910c9549 Mon Sep 17 00:00:00 2001
From: Johan Hovold <johan@kernel.org>
Date: Thu, 10 Sep 2026 15:08:14 +0200
Subject: [PATCH] serial: fix ioctl hangup race
The tty ioctls can race with hangup and end up calling into a tty
driver for a device that is already gone or powered down.
Add the missing checks to make sure the port has not been hung up before
accessing the hardware to avoid issues like kernel panic due to
unclocked accesses.
Note that TIOCGSERIAL, TIOCGICOUNT do not access hardware and are
therefore not affected by the race.
Also note that the initialised check in uart_set_ldisc() added by commit
2f70e49ed860 ("serial_core: Check for port state when tty is in error
state") should have been done under the port lock to avoid racing with
uart_do_autoconfig().
Fixes: 04f378b198da ("tty: BKL pushdown")
Cc: stable@kernel.org # 2.6.26
Signed-off-by: Johan Hovold <johan@kernel.org>
Link: https://patch.msgid.link/20260910130816.642699-3-johan@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 1553bc6cbe7b..139b93876229 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -896,7 +896,7 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port,
upf_t old_flags, new_flags;
int retval;
- if (!uport)
+ if (!uport || tty_io_error(tty))
return -EIO;
new_port = new_info->port;
@@ -1119,7 +1119,7 @@ static int uart_break_ctl(struct tty_struct *tty, int break_state)
guard(mutex)(&port->mutex);
uport = uart_port_check(state);
- if (!uport)
+ if (!uport || tty_io_error(tty))
return -EIO;
if (uport->type != PORT_UNKNOWN && uport->ops->break_ctl)
@@ -1144,7 +1144,7 @@ static int uart_do_autoconfig(struct tty_struct *tty, struct uart_state *state)
*/
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->mutex) {
uport = uart_port_check(state);
- if (!uport)
+ if (!uport || tty_io_error(tty))
return -EIO;
if (tty_port_users(port) != 1)
@@ -1199,7 +1199,7 @@ 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 tty_struct *tty, struct uart_state *state, unsigned long arg)
{
struct uart_port *uport;
struct tty_port *port = &state->port;
@@ -1213,11 +1213,21 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg)
uport = uart_port_ref(state);
if (!uport)
return -EIO;
+
+ mutex_lock(&port->mutex);
+ if (tty_io_error(tty)) {
+ mutex_unlock(&port->mutex);
+ ret = -EIO;
+ goto out_deref;
+ }
+
uart_port_lock_irq(uport);
memcpy(&cprev, &uport->icount, sizeof(struct uart_icount));
uart_enable_ms(uport);
uart_port_unlock_irq(uport);
+ mutex_unlock(&port->mutex);
+
add_wait_queue(&port->delta_msr_wait, &wait);
for (;;) {
uart_port_lock_irq(uport);
@@ -1246,6 +1256,7 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg)
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&port->delta_msr_wait, &wait);
+out_deref:
uart_port_deref(uport);
return ret;
@@ -1568,7 +1579,7 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
/* This should only be used when the hardware is present. */
if (cmd == TIOCMIWAIT)
- return uart_wait_modem_status(state, arg);
+ return uart_wait_modem_status(tty, state, arg);
/* rs485_config requires more locking than others */
if (cmd == TIOCSRS485)
@@ -1624,14 +1635,13 @@ static void uart_set_ldisc(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *uport;
- struct tty_port *port = &state->port;
-
- if (!tty_port_initialized(port))
- return;
guard(mutex)(&state->port.mutex);
uport = uart_port_check(state);
- if (uport && uport->ops->set_ldisc)
+ if (!uport || tty_io_error(tty))
+ return;
+
+ if (uport->ops->set_ldisc)
uport->ops->set_ldisc(uport, &tty->termios);
}
@@ -1647,7 +1657,7 @@ static void uart_set_termios(struct tty_struct *tty,
guard(mutex)(&state->port.mutex);
uport = uart_port_check(state);
- if (!uport)
+ if (!uport || tty_io_error(tty))
return;
/*
@@ -1799,7 +1809,14 @@ static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
* 'timeout' / 'expire' give us the maximum amount of time
* we wait.
*/
- while (!port->ops->tx_empty(port)) {
+ for (;;) {
+ mutex_lock(&state->port.mutex);
+ if (tty_io_error(tty) || port->ops->tx_empty(port)) {
+ mutex_unlock(&state->port.mutex);
+ break;
+ }
+ mutex_unlock(&state->port.mutex);
+
msleep_interruptible(jiffies_to_msecs(char_time));
if (signal_pending(current))
break;
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 6.12.y 1/3] serial: introduce uart_port_lock() guard()s
2026-10-05 10:15 FAILED: patch "[PATCH] serial: fix ioctl hangup race" failed to apply to 6.12-stable tree gregkh
@ 2026-10-05 22:24 ` Sasha Levin
2026-10-05 22:24 ` [PATCH 6.12.y 2/3] serial: serial_core: use guard()s Sasha Levin
2026-10-05 22:24 ` [PATCH 6.12.y 3/3] serial: fix ioctl hangup race Sasha Levin
0 siblings, 2 replies; 4+ messages in thread
From: Sasha Levin @ 2026-10-05 22:24 UTC (permalink / raw)
To: stable; +Cc: Jiri Slaby (SUSE), Greg Kroah-Hartman, Sasha Levin
From: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
[ Upstream commit 0fd60b689b0dacce659253ec15cb3d3bf660e30b ]
Having this, guards like these work:
guard(uart_port_lock_irq)(&up->port);
or
scoped_guard(uart_port_lock_irqsave, port) {
...
}
See e.g. "serial: 8250: use guard()s" later in this series.
Signed-off-by: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
Link: https://lore.kernel.org/r/20250814072456.182853-4-jirislaby@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Stable-dep-of: 8ca59f7baee7 ("serial: fix ioctl hangup race")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
include/linux/serial_core.h | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 4ab65874a850b..191154b8ada2a 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -782,6 +782,19 @@ static inline void uart_port_unlock_irqrestore(struct uart_port *up, unsigned lo
spin_unlock_irqrestore(&up->lock, flags);
}
+DEFINE_GUARD(uart_port_lock, struct uart_port *, uart_port_lock(_T), uart_port_unlock(_T));
+DEFINE_GUARD_COND(uart_port_lock, _try, uart_port_trylock(_T));
+
+DEFINE_GUARD(uart_port_lock_irq, struct uart_port *, uart_port_lock_irq(_T),
+ uart_port_unlock_irq(_T));
+
+DEFINE_LOCK_GUARD_1(uart_port_lock_irqsave, struct uart_port,
+ uart_port_lock_irqsave(_T->lock, &_T->flags),
+ uart_port_unlock_irqrestore(_T->lock, _T->flags),
+ unsigned long flags);
+DEFINE_LOCK_GUARD_1_COND(uart_port_lock_irqsave, _try,
+ uart_port_trylock_irqsave(_T->lock, &_T->flags));
+
static inline int serial_port_in(struct uart_port *up, int offset)
{
return up->serial_in(up, offset);
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 6.12.y 2/3] serial: serial_core: use guard()s
2026-10-05 22:24 ` [PATCH 6.12.y 1/3] serial: introduce uart_port_lock() guard()s Sasha Levin
@ 2026-10-05 22:24 ` Sasha Levin
2026-10-05 22:24 ` [PATCH 6.12.y 3/3] serial: fix ioctl hangup race Sasha Levin
1 sibling, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-10-05 22:24 UTC (permalink / raw)
To: stable; +Cc: Jiri Slaby (SUSE), Greg Kroah-Hartman, Sasha Levin
From: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
[ Upstream commit 56609c0500514d35c7e35adca3bfe6f0061785bf ]
Having all the new guards, use them in the serial_core code. This
improves readability, makes error handling easier, and marks locked
portions of code explicit.
Signed-off-by: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
Link: https://lore.kernel.org/r/20250814072456.182853-9-jirislaby@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Leave uart_wait_modem_status() using explicit locking, as required by the
following ioctl hangup fix, and preserve the ordering that sets the task
state before sampling modem counters to avoid lost wakeups.
Include the uart_ioctl() early-return cleanup and the mutex guard in
uart_set_ldisc(). These prepare the ioctl call site and ensure that the
hangup fix can return early from uart_set_ldisc() with the mutex released.
Stable-dep-of: 8ca59f7baee7 ("serial: fix ioctl hangup race")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/tty/serial/serial_core.c | 168 ++++++++++++-------------------
1 file changed, 63 insertions(+), 105 deletions(-)
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 2a33ad1e039d0..381758f640824 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -176,15 +176,13 @@ static void uart_start(struct tty_struct *tty)
static void
uart_update_mctrl(struct uart_port *port, unsigned int set, unsigned int clear)
{
- unsigned long flags;
unsigned int old;
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(port);
old = port->mctrl;
port->mctrl = (old & ~clear) | set;
if (old != port->mctrl && !(port->rs485.flags & SER_RS485_ENABLED))
port->ops->set_mctrl(port, port->mctrl);
- uart_port_unlock_irqrestore(port, flags);
}
#define uart_set_mctrl(port, set) uart_update_mctrl(port, set, 0)
@@ -219,7 +217,7 @@ static void uart_change_line_settings(struct tty_struct *tty, struct uart_state
/*
* Set modem status enables based on termios cflag
*/
- uart_port_lock_irq(uport);
+ guard(uart_port_lock_irq)(uport);
if (termios->c_cflag & CRTSCTS)
uport->status |= UPSTAT_CTS_ENABLE;
else
@@ -240,7 +238,6 @@ static void uart_change_line_settings(struct tty_struct *tty, struct uart_state
else
__uart_start(state);
}
- uart_port_unlock_irq(uport);
}
static int uart_alloc_xmit_buf(struct tty_port *port)
@@ -710,7 +707,6 @@ static void uart_send_xchar(struct tty_struct *tty, u8 ch)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
- unsigned long flags;
port = uart_port_ref(state);
if (!port)
@@ -719,11 +715,10 @@ static void uart_send_xchar(struct tty_struct *tty, u8 ch)
if (port->ops->send_xchar)
port->ops->send_xchar(port, ch);
else {
- uart_port_lock_irqsave(port, &flags);
+ guard(uart_port_lock_irqsave)(port);
port->x_char = ch;
if (ch)
port->ops->start_tx(port);
- uart_port_unlock_irqrestore(port, flags);
}
uart_port_deref(port);
}
@@ -1104,7 +1099,6 @@ 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;
- int result;
guard(mutex)(&port->mutex);
@@ -1112,12 +1106,9 @@ static int uart_tiocmget(struct tty_struct *tty)
if (!uport || tty_io_error(tty))
return -EIO;
- uart_port_lock_irq(uport);
- result = uport->mctrl;
- result |= uport->ops->get_mctrl(uport);
- uart_port_unlock_irq(uport);
+ guard(uart_port_lock_irq)(uport);
- return result;
+ return uport->mctrl | uport->ops->get_mctrl(uport);
}
static int
@@ -1446,7 +1437,6 @@ static void uart_set_rs485_rx_during_tx(struct uart_port *port,
static int uart_rs485_config(struct uart_port *port)
{
struct serial_rs485 *rs485 = &port->rs485;
- unsigned long flags;
int ret;
if (!(rs485->flags & SER_RS485_ENABLED))
@@ -1456,9 +1446,8 @@ static int uart_rs485_config(struct uart_port *port)
uart_set_rs485_termination(port, rs485);
uart_set_rs485_rx_during_tx(port, rs485);
- uart_port_lock_irqsave(port, &flags);
- ret = port->rs485_config(port, NULL, rs485);
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port)
+ ret = port->rs485_config(port, NULL, rs485);
if (ret) {
memset(rs485, 0, sizeof(*rs485));
/* unset GPIOs */
@@ -1472,12 +1461,10 @@ static int uart_rs485_config(struct uart_port *port)
static int uart_get_rs485_config(struct uart_port *port,
struct serial_rs485 __user *rs485)
{
- unsigned long flags;
struct serial_rs485 aux;
- uart_port_lock_irqsave(port, &flags);
- aux = port->rs485;
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port)
+ aux = port->rs485;
if (copy_to_user(rs485, &aux, sizeof(aux)))
return -EFAULT;
@@ -1490,7 +1477,6 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
{
struct serial_rs485 rs485;
int ret;
- unsigned long flags;
if (!(port->rs485_supported.flags & SER_RS485_ENABLED))
return -ENOTTY;
@@ -1505,16 +1491,16 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
uart_set_rs485_termination(port, &rs485);
uart_set_rs485_rx_during_tx(port, &rs485);
- uart_port_lock_irqsave(port, &flags);
- ret = port->rs485_config(port, &tty->termios, &rs485);
- if (!ret) {
- port->rs485 = rs485;
+ scoped_guard(uart_port_lock_irqsave, port) {
+ ret = port->rs485_config(port, &tty->termios, &rs485);
+ if (!ret) {
+ port->rs485 = rs485;
- /* Reset RTS and other mctrl lines when disabling RS485 */
- if (!(rs485.flags & SER_RS485_ENABLED))
- port->ops->set_mctrl(port, port->mctrl);
+ /* Reset RTS and other mctrl lines when disabling RS485 */
+ if (!(rs485.flags & SER_RS485_ENABLED))
+ port->ops->set_mctrl(port, port->mctrl);
+ }
}
- uart_port_unlock_irqrestore(port, flags);
if (ret) {
/* restore old GPIO settings */
gpiod_set_value_cansleep(port->rs485_term_gpio,
@@ -1533,15 +1519,13 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
static int uart_get_iso7816_config(struct uart_port *port,
struct serial_iso7816 __user *iso7816)
{
- unsigned long flags;
struct serial_iso7816 aux;
if (!port->iso7816_config)
return -ENOTTY;
- uart_port_lock_irqsave(port, &flags);
- aux = port->iso7816;
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port)
+ aux = port->iso7816;
if (copy_to_user(iso7816, &aux, sizeof(aux)))
return -EFAULT;
@@ -1553,8 +1537,7 @@ static int uart_set_iso7816_config(struct uart_port *port,
struct serial_iso7816 __user *iso7816_user)
{
struct serial_iso7816 iso7816;
- int i, ret;
- unsigned long flags;
+ int i;
if (!port->iso7816_config)
return -ENOTTY;
@@ -1570,11 +1553,11 @@ static int uart_set_iso7816_config(struct uart_port *port,
if (iso7816.reserved[i])
return -EINVAL;
- uart_port_lock_irqsave(port, &flags);
- ret = port->iso7816_config(port, &iso7816);
- uart_port_unlock_irqrestore(port, flags);
- if (ret)
- return ret;
+ scoped_guard(uart_port_lock_irqsave, port) {
+ int ret = port->iso7816_config(port, &iso7816);
+ if (ret)
+ return ret;
+ }
if (copy_to_user(iso7816_user, &port->iso7816, sizeof(port->iso7816)))
return -EFAULT;
@@ -1594,37 +1577,20 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
void __user *uarg = (void __user *)arg;
int ret = -ENOIOCTLCMD;
-
- /*
- * These ioctls don't rely on the hardware to be present.
- */
- switch (cmd) {
- case TIOCSERCONFIG:
+ /* This ioctl doesn't rely on the hardware to be present. */
+ if (cmd == TIOCSERCONFIG) {
down_write(&tty->termios_rwsem);
ret = uart_do_autoconfig(tty, state);
up_write(&tty->termios_rwsem);
- break;
- }
-
- if (ret != -ENOIOCTLCMD)
- goto out;
-
- if (tty_io_error(tty)) {
- ret = -EIO;
- goto out;
+ return ret;
}
- /*
- * The following should only be used when hardware is present.
- */
- switch (cmd) {
- case TIOCMIWAIT:
- ret = uart_wait_modem_status(state, arg);
- break;
- }
+ if (tty_io_error(tty))
+ return -EIO;
- if (ret != -ENOIOCTLCMD)
- goto out;
+ /* This should only be used when the hardware is present. */
+ if (cmd == TIOCMIWAIT)
+ return uart_wait_modem_status(state, arg);
/* rs485_config requires more locking than others */
if (cmd == TIOCSRS485)
@@ -1672,7 +1638,7 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
mutex_unlock(&port->mutex);
if (cmd == TIOCSRS485)
up_write(&tty->termios_rwsem);
-out:
+
return ret;
}
@@ -1685,11 +1651,10 @@ static void uart_set_ldisc(struct tty_struct *tty)
if (!tty_port_initialized(port))
return;
- mutex_lock(&state->port.mutex);
+ guard(mutex)(&state->port.mutex);
uport = uart_port_check(state);
if (uport && uport->ops->set_ldisc)
uport->ops->set_ldisc(uport, &tty->termios);
- mutex_unlock(&state->port.mutex);
}
static void uart_set_termios(struct tty_struct *tty,
@@ -1786,9 +1751,8 @@ static void uart_tty_port_shutdown(struct tty_port *port)
if (WARN(!uport, "detached port still initialized!\n"))
return;
- uart_port_lock_irq(uport);
- uport->ops->stop_rx(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport)
+ uport->ops->stop_rx(uport);
serial_base_port_shutdown(uport);
uart_port_shutdown(port);
@@ -2062,9 +2026,8 @@ static void uart_line_info(struct seq_file *m, struct uart_driver *drv, int i)
pm_state = state->pm_state;
if (pm_state != UART_PM_STATE_ON)
uart_change_pm(state, UART_PM_STATE_ON);
- uart_port_lock_irq(uport);
- status = uport->ops->get_mctrl(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport)
+ status = uport->ops->get_mctrl(uport);
if (pm_state != UART_PM_STATE_ON)
uart_change_pm(state, pm_state);
@@ -2400,9 +2363,8 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport)
*/
if (!console_suspend_enabled && uart_console(uport)) {
if (uport->ops->start_rx) {
- uart_port_lock_irq(uport);
+ guard(uart_port_lock_irq)(uport);
uport->ops->stop_rx(uport);
- uart_port_unlock_irq(uport);
}
device_set_awake_path(uport->dev);
return 0;
@@ -2418,15 +2380,15 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport)
tty_port_set_suspended(port, true);
tty_port_set_initialized(port, false);
- uart_port_lock_irq(uport);
- ops->stop_tx(uport);
- if (!(uport->rs485.flags & SER_RS485_ENABLED))
- ops->set_mctrl(uport, 0);
- /* save mctrl so it can be restored on resume */
- mctrl = uport->mctrl;
- uport->mctrl = 0;
- ops->stop_rx(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport) {
+ ops->stop_tx(uport);
+ if (!(uport->rs485.flags & SER_RS485_ENABLED))
+ ops->set_mctrl(uport, 0);
+ /* save mctrl so it can be restored on resume */
+ mctrl = uport->mctrl;
+ uport->mctrl = 0;
+ ops->stop_rx(uport);
+ }
/*
* Wait for the transmitter to empty.
@@ -2495,9 +2457,8 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
uart_change_pm(state, UART_PM_STATE_ON);
uport->ops->set_termios(uport, &termios, NULL);
if (!console_suspend_enabled && uport->ops->start_rx) {
- uart_port_lock_irq(uport);
+ guard(uart_port_lock_irq)(uport);
uport->ops->start_rx(uport);
- uart_port_unlock_irq(uport);
}
if (console_suspend_enabled)
console_start(uport->cons);
@@ -2508,10 +2469,9 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
int ret;
uart_change_pm(state, UART_PM_STATE_ON);
- uart_port_lock_irq(uport);
- if (!(uport->rs485.flags & SER_RS485_ENABLED))
- ops->set_mctrl(uport, 0);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport)
+ if (!(uport->rs485.flags & SER_RS485_ENABLED))
+ ops->set_mctrl(uport, 0);
if (console_suspend_enabled || !uart_console(uport)) {
/* Protected by port mutex for now */
struct tty_struct *tty = port->tty;
@@ -2521,11 +2481,11 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
if (tty)
uart_change_line_settings(tty, state, NULL);
uart_rs485_config(uport);
- uart_port_lock_irq(uport);
- if (!(uport->rs485.flags & SER_RS485_ENABLED))
- ops->set_mctrl(uport, uport->mctrl);
- ops->start_tx(uport);
- uart_port_unlock_irq(uport);
+ scoped_guard(uart_port_lock_irq, uport) {
+ if (!(uport->rs485.flags & SER_RS485_ENABLED))
+ ops->set_mctrl(uport, uport->mctrl);
+ ops->start_tx(uport);
+ }
tty_port_set_initialized(port, true);
} else {
/*
@@ -2619,8 +2579,6 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
}
if (port->type != PORT_UNKNOWN) {
- unsigned long flags;
-
uart_report_port(drv, port);
/* Synchronize with possible boot console. */
@@ -2635,11 +2593,11 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,
* keep the DTR setting that is set in uart_set_options()
* We probably don't need a spinlock around this, but
*/
- uart_port_lock_irqsave(port, &flags);
- port->mctrl &= TIOCM_DTR;
- if (!(port->rs485.flags & SER_RS485_ENABLED))
- port->ops->set_mctrl(port, port->mctrl);
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port) {
+ port->mctrl &= TIOCM_DTR;
+ if (!(port->rs485.flags & SER_RS485_ENABLED))
+ port->ops->set_mctrl(port, port->mctrl);
+ }
uart_rs485_config(port);
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 6.12.y 3/3] serial: fix ioctl hangup race
2026-10-05 22:24 ` [PATCH 6.12.y 1/3] serial: introduce uart_port_lock() guard()s Sasha Levin
2026-10-05 22:24 ` [PATCH 6.12.y 2/3] serial: serial_core: use guard()s Sasha Levin
@ 2026-10-05 22:24 ` Sasha Levin
1 sibling, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-10-05 22:24 UTC (permalink / raw)
To: stable; +Cc: Johan Hovold, stable, Greg Kroah-Hartman, Sasha Levin
From: Johan Hovold <johan@kernel.org>
[ Upstream commit 8ca59f7baee7c72cf7729950b4ab2e7f910c9549 ]
The tty ioctls can race with hangup and end up calling into a tty
driver for a device that is already gone or powered down.
Add the missing checks to make sure the port has not been hung up before
accessing the hardware to avoid issues like kernel panic due to
unclocked accesses.
Note that TIOCGSERIAL, TIOCGICOUNT do not access hardware and are
therefore not affected by the race.
Also note that the initialised check in uart_set_ldisc() added by commit
2f70e49ed860 ("serial_core: Check for port state when tty is in error
state") should have been done under the port lock to avoid racing with
uart_do_autoconfig().
Fixes: 04f378b198da ("tty: BKL pushdown")
Cc: stable@kernel.org # 2.6.26
Signed-off-by: Johan Hovold <johan@kernel.org>
Link: https://patch.msgid.link/20260910130816.642699-3-johan@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/tty/serial/serial_core.c | 41 ++++++++++++++++++++++----------
1 file changed, 29 insertions(+), 12 deletions(-)
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 381758f640824..68e13c1ba498d 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -844,7 +844,7 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port,
upf_t old_flags, new_flags;
int retval = 0;
- if (!uport)
+ if (!uport || tty_io_error(tty))
return -EIO;
new_port = new_info->port;
@@ -1138,7 +1138,7 @@ static int uart_break_ctl(struct tty_struct *tty, int break_state)
guard(mutex)(&port->mutex);
uport = uart_port_check(state);
- if (!uport)
+ if (!uport || tty_io_error(tty))
return -EIO;
if (uport->type != PORT_UNKNOWN && uport->ops->break_ctl)
@@ -1163,7 +1163,7 @@ static int uart_do_autoconfig(struct tty_struct *tty, struct uart_state *state)
*/
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->mutex) {
uport = uart_port_check(state);
- if (!uport)
+ if (!uport || tty_io_error(tty))
return -EIO;
if (tty_port_users(port) != 1)
@@ -1218,7 +1218,7 @@ 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 tty_struct *tty, struct uart_state *state, unsigned long arg)
{
struct uart_port *uport;
struct tty_port *port = &state->port;
@@ -1232,11 +1232,21 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg)
uport = uart_port_ref(state);
if (!uport)
return -EIO;
+
+ mutex_lock(&port->mutex);
+ if (tty_io_error(tty)) {
+ mutex_unlock(&port->mutex);
+ ret = -EIO;
+ goto out_deref;
+ }
+
uart_port_lock_irq(uport);
memcpy(&cprev, &uport->icount, sizeof(struct uart_icount));
uart_enable_ms(uport);
uart_port_unlock_irq(uport);
+ mutex_unlock(&port->mutex);
+
add_wait_queue(&port->delta_msr_wait, &wait);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
@@ -1265,6 +1275,7 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg)
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&port->delta_msr_wait, &wait);
+out_deref:
uart_port_deref(uport);
return ret;
@@ -1590,7 +1601,7 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
/* This should only be used when the hardware is present. */
if (cmd == TIOCMIWAIT)
- return uart_wait_modem_status(state, arg);
+ return uart_wait_modem_status(tty, state, arg);
/* rs485_config requires more locking than others */
if (cmd == TIOCSRS485)
@@ -1646,14 +1657,13 @@ static void uart_set_ldisc(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *uport;
- struct tty_port *port = &state->port;
-
- if (!tty_port_initialized(port))
- return;
guard(mutex)(&state->port.mutex);
uport = uart_port_check(state);
- if (uport && uport->ops->set_ldisc)
+ if (!uport || tty_io_error(tty))
+ return;
+
+ if (uport->ops->set_ldisc)
uport->ops->set_ldisc(uport, &tty->termios);
}
@@ -1669,7 +1679,7 @@ static void uart_set_termios(struct tty_struct *tty,
guard(mutex)(&state->port.mutex);
uport = uart_port_check(state);
- if (!uport)
+ if (!uport || tty_io_error(tty))
return;
/*
@@ -1822,7 +1832,14 @@ static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
* 'timeout' / 'expire' give us the maximum amount of time
* we wait.
*/
- while (!port->ops->tx_empty(port)) {
+ for (;;) {
+ mutex_lock(&state->port.mutex);
+ if (tty_io_error(tty) || port->ops->tx_empty(port)) {
+ mutex_unlock(&state->port.mutex);
+ break;
+ }
+ mutex_unlock(&state->port.mutex);
+
msleep_interruptible(jiffies_to_msecs(char_time));
if (signal_pending(current))
break;
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-05 22:25 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-05 10:15 FAILED: patch "[PATCH] serial: fix ioctl hangup race" failed to apply to 6.12-stable tree gregkh
2026-10-05 22:24 ` [PATCH 6.12.y 1/3] serial: introduce uart_port_lock() guard()s Sasha Levin
2026-10-05 22:24 ` [PATCH 6.12.y 2/3] serial: serial_core: use guard()s Sasha Levin
2026-10-05 22:24 ` [PATCH 6.12.y 3/3] serial: fix ioctl hangup race Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox