Patch "tty: fix hang on tty device with no_room set" has been added to the 5.15-stable tree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is a note to let you know that I've just added the patch titled

    tty: fix hang on tty device with no_room set

to the 5.15-stable tree which can be found at:
    http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary

The filename of the patch is:
     tty-fix-hang-on-tty-device-with-no_room-set.patch
and it can be found in the queue-5.15 subdirectory.

If you, or anyone else, feels it should not be added to the stable tree,
please let <stable@xxxxxxxxxxxxxxx> know about it.



commit 04ea13fcf5ac39d5faed5c4a0f51f20aa9d4816a
Author: Hui Li <caelli@xxxxxxxxxxx>
Date:   Thu Apr 6 10:44:50 2023 +0800

    tty: fix hang on tty device with no_room set
    
    [ Upstream commit 4903fde8047a28299d1fc79c1a0dcc255e928f12 ]
    
    It is possible to hang pty devices in this case, the reader was
    blocking at epoll on master side, the writer was sleeping at
    wait_woken inside n_tty_write on slave side, and the write buffer
    on tty_port was full, we found that the reader and writer would
    never be woken again and blocked forever.
    
    The problem was caused by a race between reader and kworker:
    n_tty_read(reader):  n_tty_receive_buf_common(kworker):
    copy_from_read_buf()|
                        |room = N_TTY_BUF_SIZE - (ldata->read_head - tail)
                        |room <= 0
    n_tty_kick_worker() |
                        |ldata->no_room = true
    
    After writing to slave device, writer wakes up kworker to flush
    data on tty_port to reader, and the kworker finds that reader
    has no room to store data so room <= 0 is met. At this moment,
    reader consumes all the data on reader buffer and calls
    n_tty_kick_worker to check ldata->no_room which is false and
    reader quits reading. Then kworker sets ldata->no_room=true
    and quits too.
    
    If write buffer is not full, writer will wake kworker to flush data
    again after following writes, but if write buffer is full and writer
    goes to sleep, kworker will never be woken again and tty device is
    blocked.
    
    This problem can be solved with a check for read buffer size inside
    n_tty_receive_buf_common, if read buffer is empty and ldata->no_room
    is true, a call to n_tty_kick_worker is necessary to keep flushing
    data to reader.
    
    Cc: <stable@xxxxxxxxxxxxxxx>
    Fixes: 42458f41d08f ("n_tty: Ensure reader restarts worker for next reader")
    Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@xxxxxxxxxxxxxxx>
    Signed-off-by: Hui Li <caelli@xxxxxxxxxxx>
    Message-ID: <1680749090-14106-1-git-send-email-caelli@xxxxxxxxxxx>
    Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
    Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>

diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 8f57448e1ce46..6259249b11670 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -202,8 +202,8 @@ static void n_tty_kick_worker(struct tty_struct *tty)
 	struct n_tty_data *ldata = tty->disc_data;
 
 	/* Did the input worker stop? Restart it */
-	if (unlikely(ldata->no_room)) {
-		ldata->no_room = 0;
+	if (unlikely(READ_ONCE(ldata->no_room))) {
+		WRITE_ONCE(ldata->no_room, 0);
 
 		WARN_RATELIMIT(tty->port->itty == NULL,
 				"scheduling with invalid itty\n");
@@ -1661,7 +1661,7 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,
 			if (overflow && room < 0)
 				ldata->read_head--;
 			room = overflow;
-			ldata->no_room = flow && !room;
+			WRITE_ONCE(ldata->no_room, flow && !room);
 		} else
 			overflow = 0;
 
@@ -1692,6 +1692,17 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,
 	} else
 		n_tty_check_throttle(tty);
 
+	if (unlikely(ldata->no_room)) {
+		/*
+		 * Barrier here is to ensure to read the latest read_tail in
+		 * chars_in_buffer() and to make sure that read_tail is not loaded
+		 * before ldata->no_room is set.
+		 */
+		smp_mb();
+		if (!chars_in_buffer(tty))
+			n_tty_kick_worker(tty);
+	}
+
 	up_read(&tty->termios_rwsem);
 
 	return rcvd;
@@ -2252,8 +2263,14 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
 		if (time)
 			timeout = time;
 	}
-	if (old_tail != ldata->read_tail)
+	if (old_tail != ldata->read_tail) {
+		/*
+		 * Make sure no_room is not read in n_tty_kick_worker()
+		 * before setting ldata->read_tail in copy_from_read_buf().
+		 */
+		smp_mb();
 		n_tty_kick_worker(tty);
+	}
 	up_read(&tty->termios_rwsem);
 
 	remove_wait_queue(&tty->read_wait, &wait);



[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux