Linux Serial subsystem development
 help / color / mirror / Atom feed
* [PATCH v2 0/2] tty: fix break race
@ 2026-09-03  4:24 Adriano Cordova
  2026-09-03  4:24 ` [PATCH v2 1/2] USB: serial: fix break_ctl() NULL-pointer dereference Adriano Cordova
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Adriano Cordova @ 2026-09-03  4:24 UTC (permalink / raw)
  To: gregkh
  Cc: johan, jirislaby, linux-usb, linux-serial, Adriano Cordova,
	syzbot+473d7477c523b41d4046

A timed break (TCSBRK/TCSBRKP) and the unconditional TIOCSBRK/TIOCCBRK
can race against USB disconnect and call break_ctl() after the device is
gone, dereferencing freed port data in keyspan_break_ctl().

Both patches are needed: the tty core must not issue break_ctl() to a
hung-up tty, and serial_break() must not call the driver after
disconnect. This is the same two-layer protection read and write already
use.

Changes since v1:
  - v1 https://lore.kernel.org/stable/20260903022452.111316-2-adrianox@gmail.com/T/#t
  - Proper check on the tty side, plus check on the USB side.
  - Tested syzbot

Reported-by: syzbot+473d7477c523b41d4046@syzkaller.appspotmail.com
Link: https://syzkaller.appspot.com/bug?extid=473d7477c523b41d4046
Tested-by: syzbot+473d7477c523b41d4046@syzkaller.appspotmail.com

Adriano Cordova (2):
  USB: serial: fix break_ctl() NULL-pointer dereference
  tty: fix break race

 drivers/tty/tty_io.c            | 19 +++++++++++++++----
 drivers/usb/serial/usb-serial.c |  3 +++
 2 files changed, 18 insertions(+), 4 deletions(-)

-- 
2.51.0

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

* [PATCH v2 1/2] USB: serial: fix break_ctl() NULL-pointer dereference
  2026-09-03  4:24 [PATCH v2 0/2] tty: fix break race Adriano Cordova
@ 2026-09-03  4:24 ` Adriano Cordova
  2026-09-03  4:41   ` sashiko-bot
  2026-09-03  4:24 ` [PATCH v2 2/2] tty: fix break race Adriano Cordova
  2026-09-03  6:22 ` [PATCH v2 0/2] " Johan Hovold
  2 siblings, 1 reply; 6+ messages in thread
From: Adriano Cordova @ 2026-09-03  4:24 UTC (permalink / raw)
  To: gregkh
  Cc: johan, jirislaby, linux-usb, linux-serial, Adriano Cordova,
	syzbot+473d7477c523b41d4046, stable

Don't call into the driver after disconnect, just like serial_write()
already does.

Fixes: 9e98966c7bb9 ("tty: rework break handling")
Reported-by: syzbot+473d7477c523b41d4046@syzkaller.appspotmail.com
Link: https://syzkaller.appspot.com/bug?extid=473d7477c523b41d4046
Tested-by: syzbot+473d7477c523b41d4046@syzkaller.appspotmail.com
Cc: stable@vger.kernel.org
Signed-off-by: Adriano Cordova <adrianox@gmail.com>
---
 drivers/usb/serial/usb-serial.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c
index 17edc057a311..5f42bfe39212 100644
--- a/drivers/usb/serial/usb-serial.c
+++ b/drivers/usb/serial/usb-serial.c
@@ -532,6 +532,9 @@ static int serial_break(struct tty_struct *tty, int break_state)
 {
 	struct usb_serial_port *port = tty->driver_data;
 
+	if (port->serial->dev->state == USB_STATE_NOTATTACHED)
+		return -ENODEV;
+
 	dev_dbg(&port->dev, "%s\n", __func__);
 
 	if (port->serial->type->break_ctl)
-- 
2.51.0


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

* [PATCH v2 2/2] tty: fix break race
  2026-09-03  4:24 [PATCH v2 0/2] tty: fix break race Adriano Cordova
  2026-09-03  4:24 ` [PATCH v2 1/2] USB: serial: fix break_ctl() NULL-pointer dereference Adriano Cordova
@ 2026-09-03  4:24 ` Adriano Cordova
  2026-09-03  4:40   ` sashiko-bot
  2026-09-03  6:22 ` [PATCH v2 0/2] " Johan Hovold
  2 siblings, 1 reply; 6+ messages in thread
From: Adriano Cordova @ 2026-09-03  4:24 UTC (permalink / raw)
  To: gregkh
  Cc: johan, jirislaby, linux-usb, linux-serial, Adriano Cordova,
	syzbot+473d7477c523b41d4046, stable

A timed break (TCSBRK and TCSBRKP) can race against hangup and call
break_ctl() on a tty whose driver data is gone, causing a use-after-free
or NULL-pointer dereference in the driver.

Check the tty hung-up state before each break_ctl() call, including the
unconditional TIOCSBRK/TIOCCBRK paths, so the driver is no longer called
after hangup.

Suggested-by: Johan Hovold <johan@kernel.org>
Reported-by: syzbot+473d7477c523b41d4046@syzkaller.appspotmail.com
Link: https://syzkaller.appspot.com/bug?extid=473d7477c523b41d4046
Tested-by: syzbot+473d7477c523b41d4046@syzkaller.appspotmail.com
Cc: stable@vger.kernel.org
Signed-off-by: Adriano Cordova <adrianox@gmail.com>
---
 drivers/tty/tty_io.c | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index 48569035da56..272f2839acdb 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -2429,6 +2429,7 @@ static int tiocgetd(struct tty_struct *tty, int __user *p)
 
 /**
  * send_break - performed time break
+ * @file: file object
  * @tty: device to break on
  * @duration: timeout in mS
  *
@@ -2438,13 +2439,16 @@ static int tiocgetd(struct tty_struct *tty, int __user *p)
  * Locking:
  *	@tty->atomic_write_lock serializes
  */
-static int send_break(struct tty_struct *tty, unsigned int duration)
+static int send_break(struct file *file, struct tty_struct *tty, unsigned int duration)
 {
 	int retval;
 
 	if (tty->ops->break_ctl == NULL)
 		return 0;
 
+	if (tty_hung_up_p(file))
+		return -EIO;
+
 	if (tty->driver->flags & TTY_DRIVER_HARDWARE_BREAK)
 		return tty->ops->break_ctl(tty, duration);
 
@@ -2455,7 +2459,10 @@ static int send_break(struct tty_struct *tty, unsigned int duration)
 	retval = tty->ops->break_ctl(tty, -1);
 	if (!retval) {
 		msleep_interruptible(duration);
-		retval = tty->ops->break_ctl(tty, 0);
+		if (tty_hung_up_p(file))
+			retval = -EIO;
+		else
+			retval = tty->ops->break_ctl(tty, 0);
 	} else if (retval == -EOPNOTSUPP) {
 		/* some drivers can tell only dynamically */
 		retval = 0;
@@ -2714,10 +2721,14 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	 * Break handling
 	 */
 	case TIOCSBRK:	/* Turn break on, unconditionally */
+		if (tty_hung_up_p(file))
+			return -EIO;
 		if (tty->ops->break_ctl)
 			return tty->ops->break_ctl(tty, -1);
 		return 0;
 	case TIOCCBRK:	/* Turn break off, unconditionally */
+		if (tty_hung_up_p(file))
+			return -EIO;
 		if (tty->ops->break_ctl)
 			return tty->ops->break_ctl(tty, 0);
 		return 0;
@@ -2727,10 +2738,10 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 		 * This is used by the tcdrain() termios function.
 		 */
 		if (!arg)
-			return send_break(tty, 250);
+			return send_break(file, tty, 250);
 		return 0;
 	case TCSBRKP:	/* support for POSIX tcsendbreak() */
-		return send_break(tty, arg ? arg*100 : 250);
+		return send_break(file, tty, arg ? arg * 100 : 250);
 
 	case TIOCMGET:
 		return tty_tiocmget(tty, p);
-- 
2.51.0


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

* Re: [PATCH v2 2/2] tty: fix break race
  2026-09-03  4:24 ` [PATCH v2 2/2] tty: fix break race Adriano Cordova
@ 2026-09-03  4:40   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-09-03  4:40 UTC (permalink / raw)
  To: Adriano Cordova; +Cc: linux-serial

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] The lockless checks of `tty_hung_up_p(file)` introduce a Time-of-Check to Time-of-Use (TOCTOU) race against `__tty_hangup`, failing to fully resolve the Use-After-Free/NULL pointer dereference.
--

commit 8d56321f205bd51f6c216651862e6a270f2637ea
Author: Adriano Cordova <adrianox@gmail.com>

tty: fix break race

This commit attempts to fix a race condition where a timed break can call
break_ctl() on a tty whose driver data is gone, causing a use-after-free
or NULL-pointer dereference. It adds checks for the tty hung-up state
before each break_ctl() call.

Link: https://syzkaller.appspot.com/bug?extid=473d7477c523b41d4046

> diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
> index 48569035da56e..272f2839acdba 100644
> --- a/drivers/tty/tty_io.c
> +++ b/drivers/tty/tty_io.c
[ ... ]
> @@ -2438,13 +2439,16 @@ static int tiocgetd(struct tty_struct *tty, int __user *p)
>   * Locking:
>   *	@tty->atomic_write_lock serializes
>   */
> -static int send_break(struct tty_struct *tty, unsigned int duration)
> +static int send_break(struct file *file, struct tty_struct *tty, unsigned int duration)
>  {
>  	int retval;
>  
>  	if (tty->ops->break_ctl == NULL)
>  		return 0;
>  
> +	if (tty_hung_up_p(file))
> +		return -EIO;
> +

[Severity: High]
This isn't a bug introduced by this patch, but does this lockless check fully
resolve the Time-of-Check to Time-of-Use (TOCTOU) race? If a concurrent thread
executes __tty_hangup() immediately after tty_hung_up_p() evaluates to false,
could __tty_hangup() tear down the driver data before tty->ops->break_ctl()
is executed on the hardware break path, leading to a use-after-free?

>  	if (tty->driver->flags & TTY_DRIVER_HARDWARE_BREAK)
>  		return tty->ops->break_ctl(tty, duration);
>  
> @@ -2455,7 +2459,10 @@ static int send_break(struct tty_struct *tty, unsigned int duration)
>  	retval = tty->ops->break_ctl(tty, -1);
>  	if (!retval) {
>  		msleep_interruptible(duration);
> -		retval = tty->ops->break_ctl(tty, 0);
> +		if (tty_hung_up_p(file))
> +			retval = -EIO;
> +		else
> +			retval = tty->ops->break_ctl(tty, 0);

[Severity: High]
This is a pre-existing issue, but does checking tty_hung_up_p() here under
atomic_write_lock provide mutual exclusion against __tty_hangup()? Could a
concurrent hangup occur right after this check, causing the subsequent call
to tty->ops->break_ctl() to dereference freed driver data?

>  	} else if (retval == -EOPNOTSUPP) {
>  		/* some drivers can tell only dynamically */
>  		retval = 0;
[ ... ]
> @@ -2714,10 +2721,14 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
>  	 * Break handling
>  	 */
>  	case TIOCSBRK:	/* Turn break on, unconditionally */
> +		if (tty_hung_up_p(file))
> +			return -EIO;
>  		if (tty->ops->break_ctl)
>  			return tty->ops->break_ctl(tty, -1);

[Severity: High]
This isn't a bug introduced by this patch, but do these unconditional break
paths suffer from the same TOCTOU race? Since they do not hold tty_lock,
could a concurrent __tty_hangup() free the driver data between the
tty_hung_up_p() check and the break_ctl() call?

>  		return 0;
>  	case TIOCCBRK:	/* Turn break off, unconditionally */
> +		if (tty_hung_up_p(file))
> +			return -EIO;
>  		if (tty->ops->break_ctl)
>  			return tty->ops->break_ctl(tty, 0);

[Severity: High]
This is a pre-existing issue, but similar to TIOCSBRK, is this unprotected
check before unconditional break off vulnerable to a concurrent hangup?

>  		return 0;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903042429.130320-1-adrianox@gmail.com?part=2

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

* Re: [PATCH v2 1/2] USB: serial: fix break_ctl() NULL-pointer dereference
  2026-09-03  4:24 ` [PATCH v2 1/2] USB: serial: fix break_ctl() NULL-pointer dereference Adriano Cordova
@ 2026-09-03  4:41   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-09-03  4:41 UTC (permalink / raw)
  To: Adriano Cordova; +Cc: linux-serial

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the patch's attempt to fix it is flawed. The newly added lockless state check of USB_STATE_NOTATTACHED in `serial_break` leaves a Time-of-Check to Time-of-Use (TOCTOU) race window, failing to completely prevent Use-After-Free or NULL-pointer dereference vulnerabilities upon concurrent device disconnect.
- [High] This problem wasn't introduced by this patch, but while the patch aims to prevent calling into the driver after disconnect, it completely omits other TTY operations in the generic usb-serial driver, leaving them vulnerable to the same Use-After-Free crashes.
--

commit 35b2c0cc5ee711aab91cfbffe24f9565620f8f65
Author: Adriano Cordova <adrianox@gmail.com>
USB: serial: fix break_ctl() NULL-pointer dereference

This commit attempts to fix a NULL-pointer dereference by checking if the
USB device is detached before calling into the sub-driver's break_ctl
callback.

Link: https://syzkaller.appspot.com/bug?extid=473d7477c523b41d4046

> diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c
> --- a/drivers/usb/serial/usb-serial.c
> +++ b/drivers/usb/serial/usb-serial.c
> @@ -532,6 +532,9 @@ static int serial_break(struct tty_struct *tty, int break_state)
>  {
>  	struct usb_serial_port *port = tty->driver_data;
>  
> +	if (port->serial->dev->state == USB_STATE_NOTATTACHED)
> +		return -ENODEV;
> +

[Severity: High]
This isn't a bug introduced by this patch, but does this lockless check
leave a Time-of-Check to Time-of-Use (TOCTOU) race window?

If the thread executing serial_break() in drivers/usb/serial/usb-serial.c
evaluates USB_STATE_NOTATTACHED to false and gets preempted, could a
concurrent usb_disconnect() finish port_remove() and free driver-private
data before the thread resumes?

>  	dev_dbg(&port->dev, "%s\n", __func__);
>  
>  	if (port->serial->type->break_ctl)
>  		return port->serial->type->break_ctl(tty, break_state);

[Severity: High]
This is a pre-existing issue, but does this still execute the sub-driver's
break_ctl() function on freed memory or a NULL pointer if the race condition
described above triggers during a concurrent TIOCSBRK or TIOCCBRK ioctl?

Additionally, this is a pre-existing issue, but while this patch aims to
prevent calling into the driver after disconnect in serial_break(), what
protects other TTY operations in the generic usb-serial driver from the
same Use-After-Free crashes?

For example, serial_tiocmget() unconditionally calls into sub-drivers
without checking for disconnect:

drivers/usb/serial/usb-serial.c:serial_tiocmget() {
    ...
	if (port->serial->type->tiocmget)
		return port->serial->type->tiocmget(tty);
    ...
}

Similar unprotected calls appear to happen in serial_ioctl(),
serial_tiocmset(), serial_throttle(), and serial_unthrottle(). Can these
also be triggered by an unprivileged user spamming concurrent TTY ioctls
(like TIOCMGET) while the physical device is unplugged?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903042429.130320-1-adrianox@gmail.com?part=1

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

* Re: [PATCH v2 0/2] tty: fix break race
  2026-09-03  4:24 [PATCH v2 0/2] tty: fix break race Adriano Cordova
  2026-09-03  4:24 ` [PATCH v2 1/2] USB: serial: fix break_ctl() NULL-pointer dereference Adriano Cordova
  2026-09-03  4:24 ` [PATCH v2 2/2] tty: fix break race Adriano Cordova
@ 2026-09-03  6:22 ` Johan Hovold
  2 siblings, 0 replies; 6+ messages in thread
From: Johan Hovold @ 2026-09-03  6:22 UTC (permalink / raw)
  To: Adriano Cordova
  Cc: gregkh, jirislaby, linux-usb, linux-serial,
	syzbot+473d7477c523b41d4046

On Thu, Sep 03, 2026 at 12:24:27AM -0400, Adriano Cordova wrote:
> A timed break (TCSBRK/TCSBRKP) and the unconditional TIOCSBRK/TIOCCBRK
> can race against USB disconnect and call break_ctl() after the device is
> gone, dereferencing freed port data in keyspan_break_ctl().
> 
> Both patches are needed: the tty core must not issue break_ctl() to a
> hung-up tty, and serial_break() must not call the driver after
> disconnect. This is the same two-layer protection read and write already
> use.

This doesn't work and as I said in my reply to the syzbot report I'm
preparing a proper fix.

Johan

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

end of thread, other threads:[~2026-09-03  6:22 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03  4:24 [PATCH v2 0/2] tty: fix break race Adriano Cordova
2026-09-03  4:24 ` [PATCH v2 1/2] USB: serial: fix break_ctl() NULL-pointer dereference Adriano Cordova
2026-09-03  4:41   ` sashiko-bot
2026-09-03  4:24 ` [PATCH v2 2/2] tty: fix break race Adriano Cordova
2026-09-03  4:40   ` sashiko-bot
2026-09-03  6:22 ` [PATCH v2 0/2] " Johan Hovold

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox