diff mbox

[Trusty,pull,request] (upstream) n_tty: Fix buffer overruns with larger-than-4k pastes

Message ID 73C05D00-F03A-4279-8587-3177B11E39FD@canonical.com
State New
Headers show

Commit Message

Rafael David Tinoco July 24, 2014, 8:43 p.m. UTC
From 0b8b060c8932c600a240fb1fa055fa103b5e969a Mon Sep 17 00:00:00 2001
From: Peter Hurley <peter@hurleysoftware.com>
Date: Tue, 10 Dec 2013 17:12:02 -0500
Subject: n_tty: Fix buffer overruns  with larger-than-4k pastes

BugLink: http://bugs.launchpad.net/bugs/1208740

n_tty: Fix buffer overruns with larger-than-4k pastes

readline() inadvertently triggers an error recovery path when
pastes larger than 4k overrun the line discipline buffer. The
error recovery path discards input when the line discipline buffer
is full and operating in canonical mode and no newline has been
received. Because readline() changes the termios to non-canonical
mode to read the line char-by-char, the line discipline buffer
can become full, and then when readline() restores termios back
to canonical mode for the caller, the now-full line discipline
buffer triggers the error recovery.

When changing termios from non-canon to canon mode and the read
buffer contains data, simulate an EOF push _without_ the
DISABLED_CHAR in the read buffer.

Importantly for the readline() problem, the termios can be
changed back to non-canonical mode without changes to the read
buffer occurring; ie., as if the previous termios change had not
happened (as long as no intervening read took place).

Preserve existing userspace behavior which allows '\0's already
received in non-canon mode to be read as '\0's in canon mode
(rather than trigger add'l EOF pushes or an actual EOF).

Patch based on original proposal and discussion here
https://bugzilla.kernel.org/show_bug.cgi?id=55991
by Stas Sergeev <stsp@users.sourceforge.net>

OriginalAuthor: Peter Hurley <peter@hurleysoftware.com>
Reported-by: Margarita Manterola <margamanterola@gmail.com>
Acked-by: Stas Sergeev <stsp@users.sourceforge.net>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
(cherry-picked from commit 4d0ed18277cc6f07513ee0b04475f19cd69e75ef v3.14-rc1)
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Rafael David Tinoco <rafael.tinoco@canonical.com>
---
 drivers/tty/n_tty.c | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

Comments

Andy Whitcroft July 25, 2014, 9:54 a.m. UTC | #1
On Thu, Jul 24, 2014 at 05:43:29PM -0300, Rafael David Tinoco wrote:
> From 0b8b060c8932c600a240fb1fa055fa103b5e969a Mon Sep 17 00:00:00 2001
> From: Peter Hurley <peter@hurleysoftware.com>
> Date: Tue, 10 Dec 2013 17:12:02 -0500
> Subject: n_tty: Fix buffer overruns  with larger-than-4k pastes
> 
> BugLink: http://bugs.launchpad.net/bugs/1208740
> 
> n_tty: Fix buffer overruns with larger-than-4k pastes
> 
> readline() inadvertently triggers an error recovery path when
> pastes larger than 4k overrun the line discipline buffer. The
> error recovery path discards input when the line discipline buffer
> is full and operating in canonical mode and no newline has been
> received. Because readline() changes the termios to non-canonical
> mode to read the line char-by-char, the line discipline buffer
> can become full, and then when readline() restores termios back
> to canonical mode for the caller, the now-full line discipline
> buffer triggers the error recovery.
> 
> When changing termios from non-canon to canon mode and the read
> buffer contains data, simulate an EOF push _without_ the
> DISABLED_CHAR in the read buffer.
> 
> Importantly for the readline() problem, the termios can be
> changed back to non-canonical mode without changes to the read
> buffer occurring; ie., as if the previous termios change had not
> happened (as long as no intervening read took place).
> 
> Preserve existing userspace behavior which allows '\0's already
> received in non-canon mode to be read as '\0's in canon mode
> (rather than trigger add'l EOF pushes or an actual EOF).
> 
> Patch based on original proposal and discussion here
> https://bugzilla.kernel.org/show_bug.cgi?id=55991
> by Stas Sergeev <stsp@users.sourceforge.net>
> 
> OriginalAuthor: Peter Hurley <peter@hurleysoftware.com>
> Reported-by: Margarita Manterola <margamanterola@gmail.com>
> Acked-by: Stas Sergeev <stsp@users.sourceforge.net>
> Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
> (cherry-picked from commit 4d0ed18277cc6f07513ee0b04475f19cd69e75ef v3.14-rc1)
> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Signed-off-by: Rafael David Tinoco <rafael.tinoco@canonical.com>
> ---
>  drivers/tty/n_tty.c | 26 +++++++++++++++++++++++---
>  1 file changed, 23 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
> index 86093e2..f591184 100644
> --- a/drivers/tty/n_tty.c
> +++ b/drivers/tty/n_tty.c
> @@ -105,6 +105,7 @@ struct n_tty_data {
>  
>  	/* must hold exclusive termios_rwsem to reset these */
>  	unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
> +	unsigned char push:1;
>  
>  	/* shared by producer and consumer */
>  	char read_buf[N_TTY_BUF_SIZE];
> @@ -342,6 +343,7 @@ static void reset_buffer_flags(struct n_tty_data *ldata)
>  
>  	ldata->erasing = 0;
>  	bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
> +	ldata->push = 0;
>  }
>  
>  static void n_tty_packet_mode_flush(struct tty_struct *tty)
> @@ -1762,7 +1764,16 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
>  
>  	if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) {
>  		bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
> -		ldata->line_start = ldata->canon_head = ldata->read_tail;
> +		ldata->line_start = ldata->read_tail;
> +		if (!L_ICANON(tty) || !read_cnt(ldata)) {
> +			ldata->canon_head = ldata->read_tail;
> +			ldata->push = 0;
> +		} else {
> +			set_bit((ldata->read_head - 1) & (N_TTY_BUF_SIZE - 1),
> +				ldata->read_flags);
> +			ldata->canon_head = ldata->read_head;
> +			ldata->push = 1;
> +		}
>  		ldata->erasing = 0;
>  		ldata->lnext = 0;
>  	}
> @@ -1967,6 +1978,12 @@ static int copy_from_read_buf(struct tty_struct *tty,
>   *	it copies one line of input up to and including the line-delimiting
>   *	character into the user-space buffer.
>   *
> + *	NB: When termios is changed from non-canonical to canonical mode and
> + *	the read buffer contains data, n_tty_set_termios() simulates an EOF
> + *	push (as if C-d were input) _without_ the DISABLED_CHAR in the buffer.
> + *	This causes data already processed as input to be immediately available
> + *	as input although a newline has not been received.
> + *
>   *	Called under the atomic_read_lock mutex
>   *
>   *	n_tty_read()/consumer path:
> @@ -2013,7 +2030,7 @@ static int canon_copy_from_read_buf(struct tty_struct *tty,
>  	n += found;
>  	c = n;
>  
> -	if (found && read_buf(ldata, eol) == __DISABLED_CHAR) {
> +	if (found && !ldata->push && read_buf(ldata, eol) == __DISABLED_CHAR) {
>  		n--;
>  		eof_push = !n && ldata->read_tail != ldata->line_start;
>  	}
> @@ -2040,7 +2057,10 @@ static int canon_copy_from_read_buf(struct tty_struct *tty,
>  	ldata->read_tail += c;
>  
>  	if (found) {
> -		ldata->line_start = ldata->read_tail;
> +		if (!ldata->push)
> +			ldata->line_start = ldata->read_tail;
> +		else
> +			ldata->push = 0;
>  		tty_audit_push(tty);
>  	}
>  	return eof_push ? -EAGAIN : 0;
> -- 

Looks reasonable, I would like Chris Arges to look over this as he was
involved in the last batch of tty fixes for "pasting wholy unreasonable
amounts of data does not work" and we have some local fixes applied at
least in Saucy for this.  But assming he is happy:

Acked-by: Andy Whitcroft <apw@canonical.com>

-apw
Chris J Arges July 25, 2014, 2:15 p.m. UTC | #2
On 07/25/2014 04:54 AM, Andy Whitcroft wrote:
> On Thu, Jul 24, 2014 at 05:43:29PM -0300, Rafael David Tinoco wrote:
>> From 0b8b060c8932c600a240fb1fa055fa103b5e969a Mon Sep 17 00:00:00 2001
>> From: Peter Hurley <peter@hurleysoftware.com>
>> Date: Tue, 10 Dec 2013 17:12:02 -0500
>> Subject: n_tty: Fix buffer overruns  with larger-than-4k pastes
>>
>> BugLink: http://bugs.launchpad.net/bugs/1208740
>>
>> n_tty: Fix buffer overruns with larger-than-4k pastes
>>
>> readline() inadvertently triggers an error recovery path when
>> pastes larger than 4k overrun the line discipline buffer. The
>> error recovery path discards input when the line discipline buffer
>> is full and operating in canonical mode and no newline has been
>> received. Because readline() changes the termios to non-canonical
>> mode to read the line char-by-char, the line discipline buffer
>> can become full, and then when readline() restores termios back
>> to canonical mode for the caller, the now-full line discipline
>> buffer triggers the error recovery.
>>
>> When changing termios from non-canon to canon mode and the read
>> buffer contains data, simulate an EOF push _without_ the
>> DISABLED_CHAR in the read buffer.
>>
>> Importantly for the readline() problem, the termios can be
>> changed back to non-canonical mode without changes to the read
>> buffer occurring; ie., as if the previous termios change had not
>> happened (as long as no intervening read took place).
>>
>> Preserve existing userspace behavior which allows '\0's already
>> received in non-canon mode to be read as '\0's in canon mode
>> (rather than trigger add'l EOF pushes or an actual EOF).
>>
>> Patch based on original proposal and discussion here
>> https://bugzilla.kernel.org/show_bug.cgi?id=55991
>> by Stas Sergeev <stsp@users.sourceforge.net>
>>
>> OriginalAuthor: Peter Hurley <peter@hurleysoftware.com>
>> Reported-by: Margarita Manterola <margamanterola@gmail.com>
>> Acked-by: Stas Sergeev <stsp@users.sourceforge.net>
>> Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
>> (cherry-picked from commit 4d0ed18277cc6f07513ee0b04475f19cd69e75ef v3.14-rc1)
>> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
>> Signed-off-by: Rafael David Tinoco <rafael.tinoco@canonical.com>
>> ---
>>  drivers/tty/n_tty.c | 26 +++++++++++++++++++++++---
>>  1 file changed, 23 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
>> index 86093e2..f591184 100644
>> --- a/drivers/tty/n_tty.c
>> +++ b/drivers/tty/n_tty.c
>> @@ -105,6 +105,7 @@ struct n_tty_data {
>>  
>>  	/* must hold exclusive termios_rwsem to reset these */
>>  	unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
>> +	unsigned char push:1;
>>  
>>  	/* shared by producer and consumer */
>>  	char read_buf[N_TTY_BUF_SIZE];
>> @@ -342,6 +343,7 @@ static void reset_buffer_flags(struct n_tty_data *ldata)
>>  
>>  	ldata->erasing = 0;
>>  	bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
>> +	ldata->push = 0;
>>  }
>>  
>>  static void n_tty_packet_mode_flush(struct tty_struct *tty)
>> @@ -1762,7 +1764,16 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
>>  
>>  	if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) {
>>  		bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
>> -		ldata->line_start = ldata->canon_head = ldata->read_tail;
>> +		ldata->line_start = ldata->read_tail;
>> +		if (!L_ICANON(tty) || !read_cnt(ldata)) {
>> +			ldata->canon_head = ldata->read_tail;
>> +			ldata->push = 0;
>> +		} else {
>> +			set_bit((ldata->read_head - 1) & (N_TTY_BUF_SIZE - 1),
>> +				ldata->read_flags);
>> +			ldata->canon_head = ldata->read_head;
>> +			ldata->push = 1;
>> +		}
>>  		ldata->erasing = 0;
>>  		ldata->lnext = 0;
>>  	}
>> @@ -1967,6 +1978,12 @@ static int copy_from_read_buf(struct tty_struct *tty,
>>   *	it copies one line of input up to and including the line-delimiting
>>   *	character into the user-space buffer.
>>   *
>> + *	NB: When termios is changed from non-canonical to canonical mode and
>> + *	the read buffer contains data, n_tty_set_termios() simulates an EOF
>> + *	push (as if C-d were input) _without_ the DISABLED_CHAR in the buffer.
>> + *	This causes data already processed as input to be immediately available
>> + *	as input although a newline has not been received.
>> + *
>>   *	Called under the atomic_read_lock mutex
>>   *
>>   *	n_tty_read()/consumer path:
>> @@ -2013,7 +2030,7 @@ static int canon_copy_from_read_buf(struct tty_struct *tty,
>>  	n += found;
>>  	c = n;
>>  
>> -	if (found && read_buf(ldata, eol) == __DISABLED_CHAR) {
>> +	if (found && !ldata->push && read_buf(ldata, eol) == __DISABLED_CHAR) {
>>  		n--;
>>  		eof_push = !n && ldata->read_tail != ldata->line_start;
>>  	}
>> @@ -2040,7 +2057,10 @@ static int canon_copy_from_read_buf(struct tty_struct *tty,
>>  	ldata->read_tail += c;
>>  
>>  	if (found) {
>> -		ldata->line_start = ldata->read_tail;
>> +		if (!ldata->push)
>> +			ldata->line_start = ldata->read_tail;
>> +		else
>> +			ldata->push = 0;
>>  		tty_audit_push(tty);
>>  	}
>>  	return eof_push ? -EAGAIN : 0;
>> -- 
> 
> Looks reasonable, I would like Chris Arges to look over this as he was
> involved in the last batch of tty fixes for "pasting wholy unreasonable
> amounts of data does not work" and we have some local fixes applied at
> least in Saucy for this.  But assming he is happy:
> 
> Acked-by: Andy Whitcroft <apw@canonical.com>
> 

I tested with and without this patch and it clearly fixes the issue.
FYI, the previous patch "SAUCE: (no-up) Only let characters through when
there are active readers." was the preliminary version in < 3.13
versions that fixes this issue. But now that the upstream patch was
accepted, we should use this version of this patch as posted.

Acked-by: Chris J Arges <chris.j.arges@canonical.com>

> -apw
>
Brad Figg July 25, 2014, 2:46 p.m. UTC | #3
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 07/24/2014 01:43 PM, Rafael David Tinoco wrote:
> From 0b8b060c8932c600a240fb1fa055fa103b5e969a Mon Sep 17 00:00:00 2001
> From: Peter Hurley <peter@hurleysoftware.com>
> Date: Tue, 10 Dec 2013 17:12:02 -0500
> Subject: n_tty: Fix buffer overruns  with larger-than-4k pastes
> 
> BugLink: http://bugs.launchpad.net/bugs/1208740
> 
> n_tty: Fix buffer overruns with larger-than-4k pastes
> 
> readline() inadvertently triggers an error recovery path when
> pastes larger than 4k overrun the line discipline buffer. The
> error recovery path discards input when the line discipline buffer
> is full and operating in canonical mode and no newline has been
> received. Because readline() changes the termios to non-canonical
> mode to read the line char-by-char, the line discipline buffer
> can become full, and then when readline() restores termios back
> to canonical mode for the caller, the now-full line discipline
> buffer triggers the error recovery.
> 
> When changing termios from non-canon to canon mode and the read
> buffer contains data, simulate an EOF push _without_ the
> DISABLED_CHAR in the read buffer.
> 
> Importantly for the readline() problem, the termios can be
> changed back to non-canonical mode without changes to the read
> buffer occurring; ie., as if the previous termios change had not
> happened (as long as no intervening read took place).
> 
> Preserve existing userspace behavior which allows '\0's already
> received in non-canon mode to be read as '\0's in canon mode
> (rather than trigger add'l EOF pushes or an actual EOF).
> 
> Patch based on original proposal and discussion here
> https://bugzilla.kernel.org/show_bug.cgi?id=55991
> by Stas Sergeev <stsp@users.sourceforge.net>
> 
> OriginalAuthor: Peter Hurley <peter@hurleysoftware.com>
> Reported-by: Margarita Manterola <margamanterola@gmail.com>
> Acked-by: Stas Sergeev <stsp@users.sourceforge.net>
> Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
> (cherry-picked from commit 4d0ed18277cc6f07513ee0b04475f19cd69e75ef v3.14-rc1)
> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Signed-off-by: Rafael David Tinoco <rafael.tinoco@canonical.com>
> ---
>  drivers/tty/n_tty.c | 26 +++++++++++++++++++++++---
>  1 file changed, 23 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
> index 86093e2..f591184 100644
> --- a/drivers/tty/n_tty.c
> +++ b/drivers/tty/n_tty.c
> @@ -105,6 +105,7 @@ struct n_tty_data {
>  
>  	/* must hold exclusive termios_rwsem to reset these */
>  	unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
> +	unsigned char push:1;
>  
>  	/* shared by producer and consumer */
>  	char read_buf[N_TTY_BUF_SIZE];
> @@ -342,6 +343,7 @@ static void reset_buffer_flags(struct n_tty_data *ldata)
>  
>  	ldata->erasing = 0;
>  	bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
> +	ldata->push = 0;
>  }
>  
>  static void n_tty_packet_mode_flush(struct tty_struct *tty)
> @@ -1762,7 +1764,16 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
>  
>  	if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) {
>  		bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
> -		ldata->line_start = ldata->canon_head = ldata->read_tail;
> +		ldata->line_start = ldata->read_tail;
> +		if (!L_ICANON(tty) || !read_cnt(ldata)) {
> +			ldata->canon_head = ldata->read_tail;
> +			ldata->push = 0;
> +		} else {
> +			set_bit((ldata->read_head - 1) & (N_TTY_BUF_SIZE - 1),
> +				ldata->read_flags);
> +			ldata->canon_head = ldata->read_head;
> +			ldata->push = 1;
> +		}
>  		ldata->erasing = 0;
>  		ldata->lnext = 0;
>  	}
> @@ -1967,6 +1978,12 @@ static int copy_from_read_buf(struct tty_struct *tty,
>   *	it copies one line of input up to and including the line-delimiting
>   *	character into the user-space buffer.
>   *
> + *	NB: When termios is changed from non-canonical to canonical mode and
> + *	the read buffer contains data, n_tty_set_termios() simulates an EOF
> + *	push (as if C-d were input) _without_ the DISABLED_CHAR in the buffer.
> + *	This causes data already processed as input to be immediately available
> + *	as input although a newline has not been received.
> + *
>   *	Called under the atomic_read_lock mutex
>   *
>   *	n_tty_read()/consumer path:
> @@ -2013,7 +2030,7 @@ static int canon_copy_from_read_buf(struct tty_struct *tty,
>  	n += found;
>  	c = n;
>  
> -	if (found && read_buf(ldata, eol) == __DISABLED_CHAR) {
> +	if (found && !ldata->push && read_buf(ldata, eol) == __DISABLED_CHAR) {
>  		n--;
>  		eof_push = !n && ldata->read_tail != ldata->line_start;
>  	}
> @@ -2040,7 +2057,10 @@ static int canon_copy_from_read_buf(struct tty_struct *tty,
>  	ldata->read_tail += c;
>  
>  	if (found) {
> -		ldata->line_start = ldata->read_tail;
> +		if (!ldata->push)
> +			ldata->line_start = ldata->read_tail;
> +		else
> +			ldata->push = 0;
>  		tty_audit_push(tty);
>  	}
>  	return eof_push ? -EAGAIN : 0;
> 
> 
> 


- -- 
Brad Figg brad.figg@canonical.com http://www.canonical.com
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iQIcBAEBAgAGBQJT0m2sAAoJEAx7WJsQW+f3TxYQAIZVxLQsVeBuDIsjRWgzSoLt
WYGIjgC7erWhbziJ54jwZUEGqGoUBIwYMlMV0M7ty0B/J0tbH9VaACkM/MjOwoYZ
AhKgZot4GarmZTlelb8hAoq0nNIa25L2P0fKzHU3Rfd2DWJ/ck7el1+YMPc+xxgS
Zq6tCTvCq3R51zUxXc4SFe/nNPH/6ljEF/OKRQeuZE89YarOkhT9c8BDCxnxkoKu
5bMCjGZ55vt85N72Hr2C21qufeKGIP8n3s46hQmLe5KVNmgmC9Zh7HK61e3HTUge
QkP8LphlaIxWqZYxUsy5Eul1++xqD0r3Y1Lti+U3e70x5YG8TlUsNV5X2dzV00xW
nRIlD6gu1d1hPY9IJctseYKqpjVmlP9bhFnuKZsPnsqIFOO1rMO5SELc2PTg7iCH
LWF29Ldin1Jz2pyJSCGRaGqaHeKtym4Sb13L1RhfJ64UStvxAJVY8wmd5/ch73+I
ZMYCzKfNRFQFTwUT4BBqOcD6ugvhqSYD3ymBm+hIXg/xu2KAh+Vc5KkgLUtfFx8e
kf5KKzklVcqDi0bAi19xZ3xzZeghWA9yK3u21yqw1Zg/mWT9P3vYcEC68BtMPipg
hqcNACpkebIuqS9MMyVCgSvP0337PxsBk7dAmZpLM/w8aexeKRPk0achm73qO/gV
zQjHQfZ8jtNsGCTNnwmH
=Dfqh
-----END PGP SIGNATURE-----
diff mbox

Patch

diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 86093e2..f591184 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -105,6 +105,7 @@  struct n_tty_data {
 
 	/* must hold exclusive termios_rwsem to reset these */
 	unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
+	unsigned char push:1;
 
 	/* shared by producer and consumer */
 	char read_buf[N_TTY_BUF_SIZE];
@@ -342,6 +343,7 @@  static void reset_buffer_flags(struct n_tty_data *ldata)
 
 	ldata->erasing = 0;
 	bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
+	ldata->push = 0;
 }
 
 static void n_tty_packet_mode_flush(struct tty_struct *tty)
@@ -1762,7 +1764,16 @@  static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
 
 	if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) {
 		bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
-		ldata->line_start = ldata->canon_head = ldata->read_tail;
+		ldata->line_start = ldata->read_tail;
+		if (!L_ICANON(tty) || !read_cnt(ldata)) {
+			ldata->canon_head = ldata->read_tail;
+			ldata->push = 0;
+		} else {
+			set_bit((ldata->read_head - 1) & (N_TTY_BUF_SIZE - 1),
+				ldata->read_flags);
+			ldata->canon_head = ldata->read_head;
+			ldata->push = 1;
+		}
 		ldata->erasing = 0;
 		ldata->lnext = 0;
 	}
@@ -1967,6 +1978,12 @@  static int copy_from_read_buf(struct tty_struct *tty,
  *	it copies one line of input up to and including the line-delimiting
  *	character into the user-space buffer.
  *
+ *	NB: When termios is changed from non-canonical to canonical mode and
+ *	the read buffer contains data, n_tty_set_termios() simulates an EOF
+ *	push (as if C-d were input) _without_ the DISABLED_CHAR in the buffer.
+ *	This causes data already processed as input to be immediately available
+ *	as input although a newline has not been received.
+ *
  *	Called under the atomic_read_lock mutex
  *
  *	n_tty_read()/consumer path:
@@ -2013,7 +2030,7 @@  static int canon_copy_from_read_buf(struct tty_struct *tty,
 	n += found;
 	c = n;
 
-	if (found && read_buf(ldata, eol) == __DISABLED_CHAR) {
+	if (found && !ldata->push && read_buf(ldata, eol) == __DISABLED_CHAR) {
 		n--;
 		eof_push = !n && ldata->read_tail != ldata->line_start;
 	}
@@ -2040,7 +2057,10 @@  static int canon_copy_from_read_buf(struct tty_struct *tty,
 	ldata->read_tail += c;
 
 	if (found) {
-		ldata->line_start = ldata->read_tail;
+		if (!ldata->push)
+			ldata->line_start = ldata->read_tail;
+		else
+			ldata->push = 0;
 		tty_audit_push(tty);
 	}
 	return eof_push ? -EAGAIN : 0;