FAILED: patch "[PATCH] tty: port: fix tty_port_tty_vhangup() race" failed to apply to 5.15-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.15-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 <[email protected]>.
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-5.15.y
git checkout FETCH_HEAD
git cherry-pick -x b48820de8c1a596c5b99775524dfce73c6576c5d
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026100552-dumpster-splashed-bbd3@gregkh' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From b48820de8c1a596c5b99775524dfce73c6576c5d Mon Sep 17 00:00:00 2001
From: Johan Hovold <[email protected]>
Date: Thu, 10 Sep 2026 14:51:14 +0200
Subject: [PATCH] tty: port: fix tty_port_tty_vhangup() race
Drivers rely on tty_port_tty_vhangup() to synchronously hang up their ports before tearing down resources as part of deregistration (e.g. on physical disconnect).
If tty_port_tty_vhangup() races with another hangup it may however return before the port has been shut down. This is in turn can lead to use-after-free and NULL-pointer dereferences in racing ioctls (including a synchronous hangup) and in processing of racing asynchronous hangups (carrier loss).
Add the missing serialisation to tty_port_tty_vhangup() and tty_port_hangup() so that the former does not return until the port has been shut down also when there is a racing hangup.
Note that serial core does not use tty_port_hangup(), but it already holds the port mutex when clearing port->tty and shutting down the port in uart_hangup().
drivers/tty/tty_port.c +58 -29
@@ -340,19 +340,9 @@ void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty) }EXPORT_SYMBOL (tty_port_tty_set); -/** -* tty_port_shutdown - internal helper to shutdown the device -* @port: tty port to be shut down -* @tty: the associated tty -* -* It is used by tty_port_hangup() and tty_port_close(). Its task is to -* shutdown the device if it was initialized (note consoles remain -* functioning). It lowers DTR/RTS (if @tty has HUPCL set) and invokes -* @port->ops->shutdown(). -*/ -static void tty_port_shutdown (struct tty_port *port,struct tty_struct *tty) +static void tty_port_shutdown_locked (struct tty_port *port,struct tty_struct *tty) { -guard (mutex)(&port->mutex); +lockdep_assert_held (&port->mutex);if (port->console)return ;
@@ -372,6 +362,23 @@ static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty) port->ops->shutdown (port); } +/** +* tty_port_shutdown - internal helper to shutdown the device +* @port: tty port to be shut down +* @tty: the associated tty +* +* It is used by tty_port_hangup() and tty_port_close(). Its task is to +* shutdown the device if it was initialized (note consoles remain +* functioning). It lowers DTR/RTS (if @tty has HUPCL set) and invokes +* @port->ops->shutdown(). +*/ +static void tty_port_shutdown (struct tty_port *port,struct tty_struct *tty) +{ +guard (mutex)(&port->mutex); + +tty_port_shutdown_locked (port, tty); +} +/** * tty_port_hangup - hangup helper * @port: tty port
@@ -385,36 +392,58 @@ void tty_port_hangup(struct tty_port *port) {struct tty_struct *tty; - scoped_guard(spinlock_irqsave, &port->lock) { - port->count =0 ; - tty = port->tty; -if (tty) -set_bit (TTY_IO_ERROR , &tty->flags); - port->tty =NULL ; - } + scoped_guard(mutex, &port->mutex) { + scoped_guard(spinlock_irqsave, &port->lock) { + port->count =0 ; + tty = port->tty; +if (tty) +set_bit (TTY_IO_ERROR , &tty->flags); + port->tty =NULL ; + } -tty_port_set_active (port, false); -tty_port_shutdown (port, tty); +tty_port_set_active (port, false); +tty_port_shutdown_locked (port, tty); + }tty_kref_put (tty);wake_up_interruptible (&port->open_wait);wake_up_interruptible (&port->delta_msr_wait); }EXPORT_SYMBOL (tty_port_hangup); -void __tty_port_tty_hangup (struct tty_port *port,bool check_clocal,bool async) +/** +* tty_port_tty_hangup - helper to hang up a tty asynchronously +* @port: tty port +* @check_clocal: hang only ttys with %CLOCAL unset? +*/ +void tty_port_tty_hangup (struct tty_port *port,bool check_clocal) { scoped_guard(tty_port_tty, port) {struct tty_struct *tty =scoped_tty (); -if (!check_clocal || !C_CLOCAL (tty)) { -if (async) -tty_hangup (tty); -else -tty_vhangup (tty); - } +if (!check_clocal || !C_CLOCAL (tty)) +tty_hangup (tty); } } -EXPORT_SYMBOL_GPL (__tty_port_tty_hangup); +EXPORT_SYMBOL_GPL (tty_port_tty_hangup); + +/** +* tty_port_tty_vhangup - helper to hang up a tty synchronously +* @port: tty port +*/ +void tty_port_tty_vhangup (struct tty_port *port) +{ +struct tty_struct *tty; + +mutex_lock (&port->mutex); + tty =tty_port_tty_get (port); +mutex_unlock (&port->mutex); + +if (tty) { +tty_vhangup (tty); +tty_kref_put (tty); + } +} +EXPORT_SYMBOL_GPL (tty_port_tty_vhangup);/** * tty_port_tty_wakeup - helper to wake up a tty
include/linux/tty_port.h +2 -20
@@ -245,7 +245,8 @@ bool tty_port_carrier_raised(struct tty_port *port);void tty_port_raise_dtr_rts (struct tty_port *port);void tty_port_lower_dtr_rts (struct tty_port *port);void tty_port_hangup (struct tty_port *port); -void __tty_port_tty_hangup (struct tty_port *port,bool check_clocal,bool async); +void tty_port_tty_hangup (struct tty_port *port,bool check_clocal); +void tty_port_tty_vhangup (struct tty_port *port);void tty_port_tty_wakeup (struct tty_port *port);int tty_port_block_til_ready (struct tty_port *port,struct tty_struct *tty,struct file *filp);
@@ -264,25 +265,6 @@ static inline int tty_port_users(struct tty_port *port)return port->count + port->blocked_open; } -/** -* tty_port_tty_hangup - helper to hang up a tty asynchronously -* @port: tty port -* @check_clocal: hang only ttys with %CLOCAL unset? -*/ -static inline void tty_port_tty_hangup (struct tty_port *port,bool check_clocal) -{ -__tty_port_tty_hangup (port, check_clocal, true); -} - -/** -* tty_port_tty_vhangup - helper to hang up a tty synchronously -* @port: tty port -*/ -static inline void tty_port_tty_vhangup (struct tty_port *port) -{ -__tty_port_tty_hangup (port, false, false); -} -#ifdef CONFIG_TTY void tty_kref_put (struct tty_struct *tty);__DEFINE_CLASS_IS_CONDITIONAL (tty_port_tty, true);
PATCH 1/2 tty: introduce and use tty_port_tty_vhangup() helper
From: "Jiri Slaby (SUSE)" <[email protected]>
[ Upstream commit 2b5eac0f8c6e79bc152c8804f9f88d16717013ab ]
This code (tty_get -> vhangup -> tty_put) is repeated on few places. Introduce a helper similar to tty_port_tty_hangup() (asynchronous) to handle even vhangup (synchronous).
And use it on those places.
In fact, reuse the tty_port_tty_hangup()'s code and call tty_vhangup() depending on a new bool parameter.
[ Ting-Han: adjust context in uart_remove_one_port(), which is still
called that and keeps its "ret" variable in this version ]
--- drivers/isdn/capi/capi.c | 8 +------- drivers/staging/greybus/uart.c | 7 +------ drivers/tty/serial/serial_core.c | 7 +------ drivers/tty/tty_port.c | 12 ++++++++---- drivers/usb/class/cdc-acm.c | 7 +------ drivers/usb/serial/usb-serial.c | 7 +------ include/linux/tty_port.h | 12 +++++++++++- net/bluetooth/rfcomm/tty.c | 7 +------ 8 files changed, 25 insertions(+), 42 deletions(-)
drivers/isdn/capi/capi.c +1 -7
@@ -304,15 +304,9 @@ static void capincci_alloc_minor(struct capidev *cdev, struct capincci *np)static void capincci_free_minor (struct capincci *np) {struct capiminor *mp = np->minorp; -struct tty_struct *tty;if (mp) { - tty =tty_port_tty_get (&mp->port); -if (tty) { -tty_vhangup (tty); -tty_kref_put (tty); - } - +tty_port_tty_vhangup (&mp->port);capiminor_free (mp); } }
drivers/staging/greybus/uart.c +1 -6
@@ -915,7 +915,6 @@ static void gb_uart_remove(struct gbphy_device *gbphy_dev) {struct gb_tty *gb_tty =gb_gbphy_get_data (gbphy_dev);struct gb_connection *connection = gb_tty->connection; -struct tty_struct *tty;int ret; ret =gbphy_runtime_get_sync (gbphy_dev);
@@ -928,11 +927,7 @@ static void gb_uart_remove(struct gbphy_device *gbphy_dev)wake_up_all (&gb_tty->wioctl);mutex_unlock (&gb_tty->mutex); - tty =tty_port_tty_get (&gb_tty->port); -if (tty) { -tty_vhangup (tty); -tty_kref_put (tty); - } +tty_port_tty_vhangup (&gb_tty->port);gb_connection_disable_rx (connection);tty_unregister_device (gb_tty_driver, gb_tty->minor);
drivers/tty/serial/serial_core.c +1 -6
@@ -3036,7 +3036,6 @@ 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 ;mutex_lock (&port_mutex);
@@ -3064,11 +3063,7 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)*/ tty_port_unregister_device (port, drv->tty_driver, uport->line); - tty =tty_port_tty_get (port); -if (tty) { -tty_vhangup (port->tty); -tty_kref_put (tty); - } +tty_port_tty_vhangup (port);/* * If the port is used as a console, unregister it
drivers/tty/tty_port.c +8 -4
@@ -369,15 +369,19 @@ EXPORT_SYMBOL(tty_port_hangup);* @port: tty port * @check_clocal: hang only ttys with CLOCAL unset? */ -void tty_port_tty_hangup (struct tty_port *port,bool check_clocal) +void __tty_port_tty_hangup (struct tty_port *port,bool check_clocal,bool async) {struct tty_struct *tty =tty_port_tty_get (port); -if (tty && (!check_clocal || !C_CLOCAL (tty))) -tty_hangup (tty); +if (tty && (!check_clocal || !C_CLOCAL (tty))) { +if (async) +tty_hangup (tty); +else +tty_vhangup (tty); + }tty_kref_put (tty); } -EXPORT_SYMBOL_GPL (tty_port_tty_hangup); +EXPORT_SYMBOL_GPL (__tty_port_tty_hangup);/** * tty_port_tty_wakeup - helper to wake up a tty
drivers/usb/class/cdc-acm.c +1 -6
@@ -1560,7 +1560,6 @@ static int acm_probe(struct usb_interface *intf,static void acm_disconnect (struct usb_interface *intf) {struct acm *acm =usb_get_intfdata (intf); -struct tty_struct *tty;int i;/* sibling interface is already cleaning up */
@@ -1587,11 +1586,7 @@ static void acm_disconnect(struct usb_interface *intf)usb_set_intfdata (acm->data,NULL );mutex_unlock (&acm->mutex); - tty =tty_port_tty_get (&acm->port); -if (tty) { -tty_vhangup (tty); -tty_kref_put (tty); - } +tty_port_tty_vhangup (&acm->port);cancel_delayed_work_sync (&acm->dwork);
drivers/usb/serial/usb-serial.c +1 -6
@@ -1175,7 +1175,6 @@ static void usb_serial_disconnect(struct usb_interface *interface)struct usb_serial *serial =usb_get_intfdata (interface);struct device *dev = &interface->dev;struct usb_serial_port *port; -struct tty_struct *tty;/* sibling interface is cleaning up */ if (!serial)
@@ -1190,11 +1189,7 @@ static void usb_serial_disconnect(struct usb_interface *interface)for (i =0 ; i < serial->num_ports; ++i) { port = serial->port[i]; - tty =tty_port_tty_get (&port->port); -if (tty) { -tty_vhangup (tty); -tty_kref_put (tty); - } +tty_port_tty_vhangup (&port->port);usb_serial_port_poison_urbs (port);wake_up_interruptible (&port->port.delta_msr_wait);cancel_work_sync (&port->work);
include/linux/tty_port.h +11 -1
@@ -186,7 +186,7 @@ int tty_port_carrier_raised(struct tty_port *port);void tty_port_raise_dtr_rts (struct tty_port *port);void tty_port_lower_dtr_rts (struct tty_port *port);void tty_port_hangup (struct tty_port *port); -void tty_port_tty_hangup (struct tty_port *port,bool check_clocal); +void __tty_port_tty_hangup (struct tty_port *port,bool check_clocal,bool async);void tty_port_tty_wakeup (struct tty_port *port);int tty_port_block_til_ready (struct tty_port *port,struct tty_struct *tty,struct file *filp);
@@ -205,4 +205,14 @@ static inline int tty_port_users(struct tty_port *port) return port->count + port->blocked_open; } +static inline void tty_port_tty_hangup (struct tty_port *port,bool check_clocal) +{ +__tty_port_tty_hangup (port, check_clocal, true); +} + +static inline void tty_port_tty_vhangup (struct tty_port *port) +{ +__tty_port_tty_hangup (port, false, false); +} +#endif
net/bluetooth/rfcomm/tty.c +1 -6
@@ -439,7 +439,6 @@ static int __rfcomm_release_dev(void __user *arg) {struct rfcomm_dev_req req;struct rfcomm_dev *dev; -struct tty_struct *tty;if (copy_from_user (&req, arg,sizeof (req)))return -EFAULT ;
@@ -465,11 +464,7 @@ static int __rfcomm_release_dev(void __user *arg)rfcomm_dlc_close (dev->dlc,0 );/* Shut down TTY synchronously before freeing rfcomm_dev */ - tty =tty_port_tty_get (&dev->port); -if (tty) { -tty_vhangup (tty); -tty_kref_put (tty); - } +tty_port_tty_vhangup (&dev->port);if (!test_bit (RFCOMM_TTY_OWNED , &dev->status))tty_port_put (&dev->port);
Signature · 1 line
2.43.0
PATCH 2/2 tty: port: fix tty_port_tty_vhangup() race
In reply to [email protected]From: Johan Hovold <[email protected]>
[ Upstream commit b48820de8c1a596c5b99775524dfce73c6576c5d ]
Drivers rely on tty_port_tty_vhangup() to synchronously hang up their ports before tearing down resources as part of deregistration (e.g. on physical disconnect).
If tty_port_tty_vhangup() races with another hangup it may however return before the port has been shut down. This is in turn can lead to use-after-free and NULL-pointer dereferences in racing ioctls (including a synchronous hangup) and in processing of racing asynchronous hangups (carrier loss).
Add the missing serialisation to tty_port_tty_vhangup() and tty_port_hangup() so that the former does not return until the port has been shut down also when there is a racing hangup.
Note that serial core does not use tty_port_hangup(), but it already holds the port mutex when clearing port->tty and shutting down the port in uart_hangup().
[ Ting-Han: use mutex_lock()/spin_lock_irqsave() instead of guard(),
scoped_guard() and the tty_port_tty class, matching the existing code in this version of tty_port.c; tty_port_shutdown() has no kernel-doc here, so there is none to move ]
--- drivers/tty/tty_port.c | 54 ++++++++++++++++++++++++++++------------ include/linux/tty_port.h | 13 ++-------- 2 files changed, 40 insertions(+), 27 deletions(-)
drivers/tty/tty_port.c +38 -16
@@ -312,11 +312,12 @@ void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty) }EXPORT_SYMBOL (tty_port_tty_set); -static void tty_port_shutdown (struct tty_port *port,struct tty_struct *tty) +static void tty_port_shutdown_locked (struct tty_port *port,struct tty_struct *tty) { -mutex_lock (&port->mutex); +lockdep_assert_held (&port->mutex); +if (port->console) - goto out; +return ;if (tty_port_initialized (port)) {tty_port_set_initialized (port,0 );
@@ -330,7 +331,12 @@ static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty)if (port->ops->shutdown) port->ops->shutdown (port); } -out: +} + +static void tty_port_shutdown (struct tty_port *port,struct tty_struct *tty) +{ +mutex_lock (&port->mutex); +tty_port_shutdown_locked (port, tty);mutex_unlock (&port->mutex); }
@@ -348,6 +354,7 @@ void tty_port_hangup(struct tty_port *port)struct tty_struct *tty;unsigned long flags; +mutex_lock (&port->mutex);spin_lock_irqsave (&port->lock, flags); port->count =0 ; tty = port->tty;
@@ -356,7 +363,8 @@ void tty_port_hangup(struct tty_port *port) port->tty =NULL ;spin_unlock_irqrestore (&port->lock, flags);tty_port_set_active (port,0 ); -tty_port_shutdown (port, tty); +tty_port_shutdown_locked (port, tty); +mutex_unlock (&port->mutex);tty_kref_put (tty);wake_up_interruptible (&port->open_wait);wake_up_interruptible (&port->delta_msr_wait);
@@ -364,24 +372,38 @@ void tty_port_hangup(struct tty_port *port)EXPORT_SYMBOL (tty_port_hangup);/** -* tty_port_tty_hangup - helper to hang up a tty -* +* tty_port_tty_hangup - helper to hang up a tty asynchronously * @port: tty port -* @check_clocal: hang only ttys with CLOCAL unset? +* @check_clocal: hang only ttys with %CLOCAL unset? */ -void __tty_port_tty_hangup (struct tty_port *port,bool check_clocal,bool async) +void tty_port_tty_hangup (struct tty_port *port,bool check_clocal) {struct tty_struct *tty =tty_port_tty_get (port); -if (tty && (!check_clocal || !C_CLOCAL (tty))) { -if (async) -tty_hangup (tty); -else -tty_vhangup (tty); - } +if (tty && (!check_clocal || !C_CLOCAL (tty))) +tty_hangup (tty);tty_kref_put (tty); } -EXPORT_SYMBOL_GPL (__tty_port_tty_hangup); +EXPORT_SYMBOL_GPL (tty_port_tty_hangup); + +/** +* tty_port_tty_vhangup - helper to hang up a tty synchronously +* @port: tty port +*/ +void tty_port_tty_vhangup (struct tty_port *port) +{ +struct tty_struct *tty; + +mutex_lock (&port->mutex); + tty =tty_port_tty_get (port); +mutex_unlock (&port->mutex); + +if (tty) { +tty_vhangup (tty); +tty_kref_put (tty); + } +} +EXPORT_SYMBOL_GPL (tty_port_tty_vhangup);/** * tty_port_tty_wakeup - helper to wake up a tty
include/linux/tty_port.h +2 -11
@@ -186,7 +186,8 @@ int tty_port_carrier_raised(struct tty_port *port);void tty_port_raise_dtr_rts (struct tty_port *port);void tty_port_lower_dtr_rts (struct tty_port *port);void tty_port_hangup (struct tty_port *port); -void __tty_port_tty_hangup (struct tty_port *port,bool check_clocal,bool async); +void tty_port_tty_hangup (struct tty_port *port,bool check_clocal); +void tty_port_tty_vhangup (struct tty_port *port);void tty_port_tty_wakeup (struct tty_port *port);int tty_port_block_til_ready (struct tty_port *port,struct tty_struct *tty,struct file *filp);
@@ -205,14 +206,4 @@ static inline int tty_port_users(struct tty_port *port) return port->count + port->blocked_open; } -static inline void tty_port_tty_hangup (struct tty_port *port,bool check_clocal) -{ -__tty_port_tty_hangup (port, check_clocal, true); -} - -static inline void tty_port_tty_vhangup (struct tty_port *port) -{ -__tty_port_tty_hangup (port, false, false); -} -#endif
Signature · 1 line
2.43.0
Re: [PATCH 5.15.y 1/2] tty: introduce and use tty_port_tty_vhangup() helper
In reply to Ting-Han HouQuoted 2 lines
Introduce a helper similar to
tty_port_tty_hangup()(asynchronous) to handle even vhangup (synchronous).
Queued the series for 5.15, thanks.
Signature · 2 lines
Thanks, Sasha