]> git.itanic.dy.fi Git - linux-stable/commitdiff
tty: Prevent writing chars during tcsetattr TCSADRAIN/FLUSH
authorIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Thu, 11 May 2023 12:32:43 +0000 (15:32 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 17 May 2023 09:11:51 +0000 (11:11 +0200)
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>
(cherry picked from commit 094fb49a2d0d6827c86d2e0840873e6db0c491d2)
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/tty_io.c
drivers/tty/tty_ioctl.c
include/linux/tty.h

index 8cbd6fa6351f8a2854133ea19c63ef4265e07028..bf834387fb0d8663d2c9a40959d359982c1d616f 100644 (file)
@@ -874,13 +874,13 @@ static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
        return i;
 }
 
-static void tty_write_unlock(struct tty_struct *tty)
+void tty_write_unlock(struct tty_struct *tty)
 {
        mutex_unlock(&tty->atomic_write_lock);
        wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
 }
 
-static int tty_write_lock(struct tty_struct *tty, int ndelay)
+int tty_write_lock(struct tty_struct *tty, int ndelay)
 {
        if (!mutex_trylock(&tty->atomic_write_lock)) {
                if (ndelay)
index efa96e6c4c1b44f1e37a8fa94c8a4caa52e9a688..0339a3856c134981558ca0fc9ae144e2048db9e4 100644 (file)
@@ -396,21 +396,42 @@ static int set_termios(struct tty_struct *tty, void __user *arg, int opt)
        tmp_termios.c_ispeed = tty_termios_input_baud_rate(&tmp_termios);
        tmp_termios.c_ospeed = tty_termios_baud_rate(&tmp_termios);
 
-       ld = tty_ldisc_ref(tty);
+       if (opt & (TERMIOS_FLUSH|TERMIOS_WAIT)) {
+retry_write_wait:
+               retval = wait_event_interruptible(tty->write_wait, !tty_chars_in_buffer(tty));
+               if (retval < 0)
+                       return retval;
 
-       if (ld != NULL) {
-               if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer)
-                       ld->ops->flush_buffer(tty);
-               tty_ldisc_deref(ld);
-       }
+               if (tty_write_lock(tty, 0) < 0)
+                       goto retry_write_wait;
 
-       if (opt & TERMIOS_WAIT) {
-               tty_wait_until_sent(tty, 0);
-               if (signal_pending(current))
-                       return -ERESTARTSYS;
-       }
+               /* Racing writer? */
+               if (tty_chars_in_buffer(tty)) {
+                       tty_write_unlock(tty);
+                       goto retry_write_wait;
+               }
+
+               ld = tty_ldisc_ref(tty);
+               if (ld != NULL) {
+                       if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer)
+                               ld->ops->flush_buffer(tty);
+                       tty_ldisc_deref(ld);
+               }
+
+               if ((opt & TERMIOS_WAIT) && tty->ops->wait_until_sent) {
+                       tty->ops->wait_until_sent(tty, 0);
+                       if (signal_pending(current)) {
+                               tty_write_unlock(tty);
+                               return -ERESTARTSYS;
+                       }
+               }
+
+               tty_set_termios(tty, &tmp_termios);
 
-       tty_set_termios(tty, &tmp_termios);
+               tty_write_unlock(tty);
+       } else {
+               tty_set_termios(tty, &tmp_termios);
+       }
 
        /* FIXME: Arguably if tmp_termios == tty->termios AND the
           actual requested termios was not tmp_termios then we may
index 641c93acc5571d81e298fb277f9547901952d2ad..33a9dbe74a066976f8fe509e7202adddbfcd34d0 100644 (file)
@@ -479,6 +479,8 @@ extern void __stop_tty(struct tty_struct *tty);
 extern void stop_tty(struct tty_struct *tty);
 extern void __start_tty(struct tty_struct *tty);
 extern void start_tty(struct tty_struct *tty);
+void tty_write_unlock(struct tty_struct *tty);
+int tty_write_lock(struct tty_struct *tty, int ndelay);
 extern int tty_register_driver(struct tty_driver *driver);
 extern int tty_unregister_driver(struct tty_driver *driver);
 extern struct device *tty_register_device(struct tty_driver *driver,