• Linus Torvalds's avatar
    tty: n_tty: do not look ahead for EOL character past the end of the buffer · 35930307
    Linus Torvalds authored
    Daniel Gibson reports that the n_tty code gets line termination wrong in
    very specific cases:
    
     "If you feed a line with exactly 64 chars + terminating newline, and
      directly afterwards (without reading) another line into a pseudo
      terminal, the the first read() on the other side will return the 64
      char line *without* terminating newline, and the next read() will
      return the missing terminating newline AND the complete next line (if
      it fits in the buffer)"
    
    and bisected the behavior to commit 3b830a9c ("tty: convert
    tty_ldisc_ops 'read()' function to take a kernel pointer").
    
    Now, digging deeper, it turns out that the behavior isn't exactly new:
    what changed in commit 3b830a9c was that the tty line discipline
    .read() function is now passed an intermediate kernel buffer rather than
    the final user space buffer.
    
    And that intermediate kernel buffer is 64 bytes in size - thus that
    special case with exactly 64 bytes plus terminating newline.
    
    The same problem did exist before, but historically the boundary was not
    the 64-byte chunk, but the user-supplied buffer size, which is obviously
    generally bigger (and potentially bigger than N_TTY_BUF_SIZE, which
    would hide the issue entirely).
    
    The reason is that the n_tty canon_copy_from_read_buf() code would look
    ahead for the EOL character one byte further than it would actually
    copy.  It would then decide that it had found the terminator, and unmark
    it as an EOL character - which in turn explains why the next read
    wouldn't then be terminated by it.
    
    Now, the reason it did all this in the first place is related to some
    historical and pretty obscure EOF behavior, see commit ac8f3bf8
    ("n_tty: Fix poll() after buffer-limited eof push read") and commit
    40d5e090 ("n_tty: Fix EOF push handling").
    
    And the reason for the EOL confusion is that we treat EOF as a special
    EOL condition, with the EOL character being NUL (aka "__DISABLED_CHAR"
    in the kernel sources).
    
    So that EOF look-ahead also affects the normal EOL handling.
    
    This patch just removes the look-ahead that causes problems, because EOL
    is much more critical than the historical "EOF in the middle of a line
    that coincides with the end of the buffer" handling ever was.
    
    Now, it is possible that we should indeed re-introduce the "look at next
    character to see if it's a EOF" behavior, but if so, that should be done
    not at the kernel buffer chunk boundary in canon_copy_from_read_buf(),
    but at a higher level, when we run out of the user buffer.
    
    In particular, the place to do that would be at the top of
    'n_tty_read()', where we check if it's a continuation of a previously
    started read, and there is no more buffer space left, we could decide to
    just eat the __DISABLED_CHAR at that point.
    
    But that would be a separate patch, because I suspect nobody actually
    cares, and I'd like to get a report about it before bothering.
    
    Fixes: 3b830a9c ("tty: convert tty_ldisc_ops 'read()' function to take a kernel pointer")
    Fixes: ac8f3bf8 ("n_tty: Fix  poll() after buffer-limited eof push read")
    Fixes: 40d5e090 ("n_tty: Fix EOF push handling")
    Link: https://bugzilla.kernel.org/show_bug.cgi?id=215611
    
    Reported-and-tested-by: default avatarDaniel Gibson <metalcaedes@gmail.com>
    Cc: Peter Hurley <peter@hurleysoftware.com>
    Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
    Cc: Jiri Slaby <jirislaby@kernel.org>
    Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
    35930307
n_tty.c 61.1 KB