Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tty: Prevent writing chars during tcsetattr TCSADRAIN/FLUSH

If userspace races tcsetattr() with a write, the drained condition
might not be guaranteed by the kernel. There is a race window after
checking Tx is empty before tty_set_termios() takes termios_rwsem for
write. During that race window, more characters can be queued by a
racing writer.

Any ongoing transmission might produce garbage during HW's
->set_termios() call. The intent of TCSADRAIN/FLUSH seems to be
preventing such a character corruption. If those flags are set, take
tty's write lock to stop any writer before performing the lower layer
Tx empty check and wait for the pending characters to be sent (if any).

The initial wait for all-writers-done must be placed outside of tty's
write lock to avoid deadlock which makes it impossible to use
tty_wait_until_sent(). The write lock is retried if a racing write is
detected.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://lore.kernel.org/r/20230317113318.31327-2-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Ilpo Järvinen and committed by
Greg Kroah-Hartman
094fb49a a4312fd4

+38 -15
+2
drivers/tty/tty.h
··· 62 62 int tty_check_change(struct tty_struct *tty); 63 63 void __stop_tty(struct tty_struct *tty); 64 64 void __start_tty(struct tty_struct *tty); 65 + void tty_write_unlock(struct tty_struct *tty); 66 + int tty_write_lock(struct tty_struct *tty, int ndelay); 65 67 void tty_vhangup_session(struct tty_struct *tty); 66 68 void tty_open_proc_set_tty(struct file *filp, struct tty_struct *tty); 67 69 int tty_signal_session_leader(struct tty_struct *tty, int exit_session);
+2 -2
drivers/tty/tty_io.c
··· 933 933 return i; 934 934 } 935 935 936 - static void tty_write_unlock(struct tty_struct *tty) 936 + void tty_write_unlock(struct tty_struct *tty) 937 937 { 938 938 mutex_unlock(&tty->atomic_write_lock); 939 939 wake_up_interruptible_poll(&tty->write_wait, EPOLLOUT); 940 940 } 941 941 942 - static int tty_write_lock(struct tty_struct *tty, int ndelay) 942 + int tty_write_lock(struct tty_struct *tty, int ndelay) 943 943 { 944 944 if (!mutex_trylock(&tty->atomic_write_lock)) { 945 945 if (ndelay)
+34 -13
drivers/tty/tty_ioctl.c
··· 501 501 tmp_termios.c_ispeed = tty_termios_input_baud_rate(&tmp_termios); 502 502 tmp_termios.c_ospeed = tty_termios_baud_rate(&tmp_termios); 503 503 504 - ld = tty_ldisc_ref(tty); 504 + if (opt & (TERMIOS_FLUSH|TERMIOS_WAIT)) { 505 + retry_write_wait: 506 + retval = wait_event_interruptible(tty->write_wait, !tty_chars_in_buffer(tty)); 507 + if (retval < 0) 508 + return retval; 505 509 506 - if (ld != NULL) { 507 - if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer) 508 - ld->ops->flush_buffer(tty); 509 - tty_ldisc_deref(ld); 510 + if (tty_write_lock(tty, 0) < 0) 511 + goto retry_write_wait; 512 + 513 + /* Racing writer? */ 514 + if (tty_chars_in_buffer(tty)) { 515 + tty_write_unlock(tty); 516 + goto retry_write_wait; 517 + } 518 + 519 + ld = tty_ldisc_ref(tty); 520 + if (ld != NULL) { 521 + if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer) 522 + ld->ops->flush_buffer(tty); 523 + tty_ldisc_deref(ld); 524 + } 525 + 526 + if ((opt & TERMIOS_WAIT) && tty->ops->wait_until_sent) { 527 + tty->ops->wait_until_sent(tty, 0); 528 + if (signal_pending(current)) { 529 + tty_write_unlock(tty); 530 + return -ERESTARTSYS; 531 + } 532 + } 533 + 534 + tty_set_termios(tty, &tmp_termios); 535 + 536 + tty_write_unlock(tty); 537 + } else { 538 + tty_set_termios(tty, &tmp_termios); 510 539 } 511 - 512 - if (opt & TERMIOS_WAIT) { 513 - tty_wait_until_sent(tty, 0); 514 - if (signal_pending(current)) 515 - return -ERESTARTSYS; 516 - } 517 - 518 - tty_set_termios(tty, &tmp_termios); 519 540 520 541 /* FIXME: Arguably if tmp_termios == tty->termios AND the 521 542 actual requested termios was not tmp_termios then we may