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 <stable@kernel.org>
Signed-off-by: Johan Hovold <johan@kernel.org>
Link: https://patch.msgid.link/20260903163736.1499280-1-johan@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Johan Hovold 2026-09-03 18:37:36 +02:00 committed by Greg Kroah-Hartman
parent 5b114bfc1d
commit 845a2737e0
2 changed files with 28 additions and 9 deletions

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);

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;