tty: abort break signalling on hangup

The break ioctls can race with hangup and end up calling into a tty
driver for a device that is already gone or powered down.

Drivers must handle this race, but TCSBRK and TCSBRKP should still be
aborted to avoid calling back into the driver after a user-controlled
timeout (possibly even after the tty has been reopened).

Note that drivers should disable any break state on shutdown so just
return on hangup (calling break_ctl() again is racy and will most likely
fail for drivers handling the race).

Fixes: f34d7a5b70 ("tty: The big operations rework")
Cc: stable <[email protected]>
Signed-off-by: Johan Hovold <[email protected]>
Link: https://patch.msgid.link/[email protected]
Signed-off-by: Greg Kroah-Hartman <[email protected]>
This commit is contained in:
Johan Hovold authored and Greg Kroah-Hartman committed 2026-09-23 11:58:36 +02:00
1 parent 5b114bfc1d
commit 845a2737e0
2 files changed
+28 -9

No files matched your search

+27 -9
View File
@@ -620,6 +620,8 @@ static void __tty_hangup(struct tty_struct *tty, int exit_session)
tty_ldisc_hangup(tty, cons_filp != NULL);
wake_up_interruptible(&tty->break_wait);
spin_lock_irq(&tty->ctrl.lock);
clear_bit(TTY_THROTTLED, &tty->flags);
clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
@@ -2431,6 +2433,7 @@ static int tiocgetd(struct tty_struct *tty, int __user *p)
* send_break - performed time break
* @tty: device to break on
* @duration: timeout in mS
* @file: file object
*
* Perform a timed break on hardware that lacks its own driver level timed
* break functionality.
@@ -2438,8 +2441,9 @@ 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)
{
long timeout;
int retval;
if (tty->ops->break_ctl == NULL)
@@ -2453,13 +2457,26 @@ static int send_break(struct tty_struct *tty, unsigned int duration)
return -EINTR;
retval = tty->ops->break_ctl(tty, -1);
if (!retval) {
msleep_interruptible(duration);
retval = tty->ops->break_ctl(tty, 0);
} else if (retval == -EOPNOTSUPP) {
/* some drivers can tell only dynamically */
retval = 0;
if (retval) {
if (retval == -EOPNOTSUPP) {
/* some drivers can tell only dynamically */
retval = 0;
}
goto out_unlock;
}
timeout = msecs_to_jiffies(duration);
timeout = wait_event_interruptible_timeout(tty->break_wait,
tty_hung_up_p(file),
timeout);
/* return early on hangup only */
if (timeout > 0) {
retval = -EIO;
goto out_unlock;
}
retval = tty->ops->break_ctl(tty, 0);
out_unlock:
tty_write_unlock(tty);
if (signal_pending(current))
@@ -2727,10 +2744,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);
@@ -3090,6 +3107,7 @@ struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx)
init_ldsem(&tty->ldisc_sem);
init_waitqueue_head(&tty->write_wait);
init_waitqueue_head(&tty->read_wait);
init_waitqueue_head(&tty->break_wait);
INIT_WORK(&tty->hangup_work, do_tty_hangup);
mutex_init(&tty->atomic_write_lock);
spin_lock_init(&tty->ctrl.lock);
+1
View File
@@ -230,6 +230,7 @@ struct tty_struct {
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
wait_queue_head_t break_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;