Skip to contentSkip to conversation

tty: Hold write ldisc sem in tty_reopen()

PATCH Superseded Dmitry Safonov 4 participants 11 messages Sep 3, 2018 – Sep 4, 2018

Subsystems (2)

v2 of 6 Superseded Latest is v6 →

lore
Share & tools
lore
#0 lore raw

v2 cover tty: Hold write ldisc sem in tty_reopen()

The first two fixes are worth to have in stables as we've hit it on v4.9 stable.

And for linux-next - adding lockdep asserts for line discipline changing code, verifying that write ldisc sem will be held forthwith.

Changes since v1:
- Added tested-by/reported-by tags
- Dropped 3/4 (locking tty pair for lockdep sake),
Because of that - not adding lockdep_assert_held() in tty_ldisc_open()
- Added 4/4 cleanup to inc tty->count only on success of tty_ldisc_reinit()
- lock ldisc without (5*HZ) timeout in tty_reopen()

v1 link:
lkml.kernel.org/r/<[email protected]>

Huuge cc list:
Cc: Daniel Axtens <[email protected]>
Cc: Dmitry Vyukov <[email protected]>
Cc: Michael Neuling <[email protected]>
Cc: Mikulas Patocka <[email protected]>
Cc: Nathan March <[email protected]>
Cc: Pasi Kärkkäinen <[email protected]>
Cc: Peter Hurley <[email protected]>
Cc: Sergey Senozhatsky <[email protected]>
Cc: Tan Xiaojun <[email protected]>
Cc: Tetsuo Handa <[email protected]> (please, ignore if I Cc'ed you mistakenly)

Dmitry Safonov (4):
tty: Drop tty->count on tty_reopen() failure
tty: Hold tty_ldisc_lock() during tty_reopen()
tty/lockdep: Add ldisc_sem asserts
tty: Simplify tty->count math in tty_reopen()

 drivers/tty/tty_io.c    | 12 ++++++++----
 drivers/tty/tty_ldisc.c |  5 +++++
 2 files changed, 13 insertions(+), 4 deletions(-)
Signature · 1 line
2.13.6
#1 lore raw

PATCH v2 1/4 tty: Drop tty->count on tty_reopen() failure

In case of tty_ldisc_reinit() failure, tty->count should be decremented back, otherwise we will never release_tty().
Tetsuo reported that it fixes noisy warnings on tty release like:
pts pts4033: tty_release: tty->count(10529) != (#fd's(7) + #kopen's(0))

Cc Greg Kroah-Hartman <[email protected]>
Cc Jiri Slaby <[email protected]>
Fixes commit 892d1fa7eaae ("tty: Destroy ldisc instance on hangup")
---
 drivers/tty/tty_io.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
drivers/tty/tty_io.c +8 -3
index 32bc3e3fe4d3..5e5da9acaf0a 100644
@@ -1255,6 +1255,7 @@ static void tty_driver_remove_tty(struct tty_driver *driver, struct tty_struct *
 static int tty_reopen(struct tty_struct *tty)
 {
 	struct tty_driver *driver = tty->driver;
+	int retval;
 
 	if (driver->type == TTY_DRIVER_TYPE_PTY &&
 	    driver->subtype == PTY_TYPE_MASTER)
@@ -1268,10 +1269,14 @@ static int tty_reopen(struct tty_struct *tty)
 
 	tty->count++;
 
-	if (!tty->ldisc)
-		return tty_ldisc_reinit(tty, tty->termios.c_line);
+	if (tty->ldisc)
+		return 0;
 
-	return 0;
+	retval = tty_ldisc_reinit(tty, tty->termios.c_line);
+	if (retval)
+		tty->count--;
+
+	return retval;
 }
 
 /**
Signature · 1 line
2.13.6
Reviewed-by on v2 1/4
#2 lore raw

Re: [PATCHv2 1/4] tty: Drop tty->count on tty_reopen() failure

Quoted 10 lines · Dmitry Safonov

In case of tty_ldisc_reinit() failure, tty->count should be decremented back, otherwise we will never release_tty().
Tetsuo reported that it fixes noisy warnings on tty release like:
pts pts4033: tty_release: tty->count(10529) != (#fd's(7) + #kopen's(0))

Cc Greg Kroah-Hartman <[email protected]>
Cc Jiri Slaby <[email protected]>
Fixes commit 892d1fa7eaae ("tty: Destroy ldisc instance on hangup")
Quoted 2 lines

thanks,

Signature · 2 lines
js
suse labs
#3 lore raw

PATCH v2 2/4 tty: Hold tty_ldisc_lock() during tty_reopen()

In reply to Dmitry Safonov

tty_ldisc_reinit() doesn't race with neither tty_ldisc_hangup() nor set_ldisc() nor tty_ldisc_release() as they use tty lock. But it races with anyone who expects line discipline to be the same after hoding read semaphore in tty_ldisc_ref().

We've seen the following crash on v4.9.108 stable:

BUG: unable to handle kernel paging request at 0000000000002260
IP: [..] n_tty_receive_buf_common+0x5f/0x86d
Workqueue: events_unbound flush_to_ldisc
Call Trace:
 [..] n_tty_receive_buf2
 [..] tty_ldisc_receive_buf
 [..] flush_to_ldisc
 [..] process_one_work
 [..] worker_thread
 [..] kthread
 [..] ret_from_fork

tty_ldisc_reinit() should be called with ldisc_sem hold for writing, which will protect any reader against line discipline changes.

Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jiri Slaby <[email protected]>
Cc: [email protected] # depends on commit b027e2298bd5 ("tty: fix data race between tty_init_dev and flush of buf")

---
 drivers/tty/tty_io.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
drivers/tty/tty_io.c +6 -3
index 5e5da9acaf0a..a947719b4626 100644
@@ -1255,7 +1255,7 @@ static void tty_driver_remove_tty(struct tty_driver *driver, struct tty_struct *
 static int tty_reopen(struct tty_struct *tty)
 {
 	struct tty_driver *driver = tty->driver;
-	int retval;
+	int retval = 0;
 
 	if (driver->type == TTY_DRIVER_TYPE_PTY &&
 	    driver->subtype == PTY_TYPE_MASTER)
@@ -1267,15 +1267,18 @@ static int tty_reopen(struct tty_struct *tty)
 	if (test_bit(TTY_EXCLUSIVE, &tty->flags) && !capable(CAP_SYS_ADMIN))
 		return -EBUSY;
 
-	tty->count++;
+	tty_ldisc_lock(tty, MAX_SCHEDULE_TIMEOUT);
 
+	tty->count++;
 	if (tty->ldisc)
-		return 0;
+		goto out_unlock;
 
 	retval = tty_ldisc_reinit(tty, tty->termios.c_line);
 	if (retval)
 		tty->count--;
 
+out_unlock:
+	tty_ldisc_unlock(tty);
 	return retval;
 }
 
Signature · 1 line
2.13.6
#4 lore raw

Re: [PATCHv2 2/4] tty: Hold tty_ldisc_lock() during tty_reopen()

Quoted 15 lines · Dmitry Safonov

We've seen the following crash on v4.9.108 stable:

BUG: unable to handle kernel paging request at 0000000000002260
IP: [..] n_tty_receive_buf_common+0x5f/0x86d
Workqueue: events_unbound flush_to_ldisc
Call Trace:
 [..] n_tty_receive_buf2
 [..] tty_ldisc_receive_buf
 [..] flush_to_ldisc
 [..] process_one_work
 [..] worker_thread
 [..] kthread
 [..] ret_from_fork
Quoted 7 lines

tty_ldisc_reinit() should be called with ldisc_sem hold for writing, which will protect any reader against line discipline changes.

Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jiri Slaby <[email protected]>
Cc: [email protected] # depends on commit b027e2298bd5 ("tty: fix data race between tty_init_dev and flush of buf")

I believe there's a "Fixes" tag for that

Fixes b027e2298bd5 ("tty: fix data race between tty_init_dev and flush of buf")
	-ss
#5 lore raw

Re: [PATCHv2 2/4] tty: Hold tty_ldisc_lock() during tty_reopen()

Quoted 24 lines · Sergey Senozhatsky
Quoted 15 lines · Dmitry Safonov

We've seen the following crash on v4.9.108 stable:

BUG: unable to handle kernel paging request at 0000000000002260
IP: [..] n_tty_receive_buf_common+0x5f/0x86d
Workqueue: events_unbound flush_to_ldisc
Call Trace:
 [..] n_tty_receive_buf2
 [..] tty_ldisc_receive_buf
 [..] flush_to_ldisc
 [..] process_one_work
 [..] worker_thread
 [..] kthread
 [..] ret_from_fork
Quoted 7 lines

tty_ldisc_reinit() should be called with ldisc_sem hold for writing, which will protect any reader against line discipline changes.

Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jiri Slaby <[email protected]>
Cc: [email protected] # depends on commit b027e2298bd5 ("tty: fix data race between tty_init_dev and flush of buf")

Quoted 4 lines

I believe there's a "Fixes" tag for that

Fixes b027e2298bd5 ("tty: fix data race between tty_init_dev and flush of buf")

Nope, it would be translated as:
Backport-first: b027e2298bd5
:)

thanks,

Signature · 2 lines
js
suse labs
#6 lore raw

Re: [PATCHv2 2/4] tty: Hold tty_ldisc_lock() during tty_reopen()

Quoted 4 lines · Jiri Slaby
Quoted 4 lines
Quoted 3 lines

Cc: [email protected] # depends on commit b027e2298bd5 ("tty: fix data race between tty_init_dev and flush of buf")

Quoted 8 lines
Quoted 4 lines

I believe there's a "Fixes" tag for that

Fixes b027e2298bd5 ("tty: fix data race between tty_init_dev and flush of buf")

Nope, it would be translated as:
Backport-first: b027e2298bd5
:)

:) Ah, OK.
So this "Backport-first:" is for pre-4.9 longterm kernels. As far as I can see linux-4.9.y picked up b027e2298bd5:

commit 55eaecffe3d663d02084023b9fc06d5f39b97389

Author: Gaurav Kohli
Date:   Tue Jan 23 13:16:34 2018 +0530
    tty: fix data race between tty_init_dev and flush of buf

    commit b027e2298bd588d6fa36ed2eda97447fb3eac078 upstream.

	-ss
Reviewed-by on v2 2/4
#7 lore raw

Re: [PATCHv2 2/4] tty: Hold tty_ldisc_lock() during tty_reopen()

In reply to Dmitry Safonov
Quoted 19 lines · Dmitry Safonov

tty_ldisc_reinit() doesn't race with neither tty_ldisc_hangup() nor set_ldisc() nor tty_ldisc_release() as they use tty lock. But it races with anyone who expects line discipline to be the same after hoding read semaphore in tty_ldisc_ref().

We've seen the following crash on v4.9.108 stable:

BUG: unable to handle kernel paging request at 0000000000002260
IP: [..] n_tty_receive_buf_common+0x5f/0x86d
Workqueue: events_unbound flush_to_ldisc
Call Trace:
 [..] n_tty_receive_buf2
 [..] tty_ldisc_receive_buf
 [..] flush_to_ldisc
 [..] process_one_work
 [..] worker_thread
 [..] kthread
 [..] ret_from_fork
Quoted 5 lines

tty_ldisc_reinit() should be called with ldisc_sem hold for writing, which will protect any reader against line discipline changes.

Cc Greg Kroah-Hartman <[email protected]>
Cc Jiri Slaby <[email protected]>
Quoted 5 lines

Cc: [email protected] # depends on commit b027e2298bd5 ("tty: fix data race between tty_init_dev and flush of buf")

thanks,

Signature · 2 lines
js
suse labs
#8 lore raw

PATCH v2 3/4 tty/lockdep: Add ldisc_sem asserts

In reply to Dmitry Safonov

Make sure under CONFIG_LOCKDEP that each change to line discipline is done with held write semaphor.
Otherwise potential reader will have a good time dereferencing incomplete/uninitialized ldisc.

Exception here is tty_ldisc_open(), as it's called without ldisc_sem locked by tty_init_dev() for the tty->link.

Cc Greg Kroah-Hartman <[email protected]>
Cc Jiri Slaby <[email protected]>
---
 drivers/tty/tty_ldisc.c | 5 +++++
 1 file changed, 5 insertions(+)
drivers/tty/tty_ldisc.c +5 -0
index fc4c97cae01e..202cb645582f 100644
@@ -471,6 +471,7 @@ static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
 
 static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
 {
+	lockdep_assert_held(&tty->ldisc_sem);
 	WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags));
 	clear_bit(TTY_LDISC_OPEN, &tty->flags);
 	if (ld->ops->close)
@@ -492,6 +493,7 @@ static int tty_ldisc_failto(struct tty_struct *tty, int ld)
 	struct tty_ldisc *disc = tty_ldisc_get(tty, ld);
 	int r;
 
+	lockdep_assert_held(&tty->ldisc_sem);
 	if (IS_ERR(disc))
 		return PTR_ERR(disc);
 	tty->ldisc = disc;
@@ -615,6 +617,7 @@ EXPORT_SYMBOL_GPL(tty_set_ldisc);
  */
 static void tty_ldisc_kill(struct tty_struct *tty)
 {
+	lockdep_assert_held(&tty->ldisc_sem);
 	if (!tty->ldisc)
 		return;
 	/*
@@ -662,6 +665,7 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
 	struct tty_ldisc *ld;
 	int retval;
 
+	lockdep_assert_held(&tty->ldisc_sem);
 	ld = tty_ldisc_get(tty, disc);
 	if (IS_ERR(ld)) {
 		BUG_ON(disc == N_TTY);
@@ -825,6 +829,7 @@ int tty_ldisc_init(struct tty_struct *tty)
  */
 void tty_ldisc_deinit(struct tty_struct *tty)
 {
+	/* no ldisc_sem, tty is being destroyed */
 	if (tty->ldisc)
 		tty_ldisc_put(tty->ldisc);
 	tty->ldisc = NULL;
Signature · 1 line
2.13.6
#9 lore raw

PATCH v2 4/4 tty: Simplify tty->count math in tty_reopen()

In reply to Dmitry Safonov

As noted by Jiri, tty_ldisc_reinit() shouldn't rely on tty counter. Simplify math by increasing the counter after reinit success.

Cc Greg Kroah-Hartman <[email protected]>
Cc Jiri Slaby <[email protected]>
Link lkml.kernel.org/r/<[email protected]>
---
 drivers/tty/tty_io.c | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)
drivers/tty/tty_io.c +5 -9
index a947719b4626..7f968ac14bbd 100644
@@ -1268,17 +1268,13 @@ static int tty_reopen(struct tty_struct *tty)
 		return -EBUSY;
 
 	tty_ldisc_lock(tty, MAX_SCHEDULE_TIMEOUT);
+	if (!tty->ldisc)
+		retval = tty_ldisc_reinit(tty, tty->termios.c_line);
+	tty_ldisc_unlock(tty);
 
-	tty->count++;
-	if (tty->ldisc)
-		goto out_unlock;
+	if (retval == 0)
+		tty->count++;
 
-	retval = tty_ldisc_reinit(tty, tty->termios.c_line);
-	if (retval)
-		tty->count--;
-
-out_unlock:
-	tty_ldisc_unlock(tty);
 	return retval;
 }
 
Signature · 1 line
2.13.6
Reviewed-by on v2 4/4
#10 lore raw

Re: [PATCHv2 4/4] tty: Simplify tty->count math in tty_reopen()

Quoted 5 lines · Dmitry Safonov

As noted by Jiri, tty_ldisc_reinit() shouldn't rely on tty counter. Simplify math by increasing the counter after reinit success.

Cc Greg Kroah-Hartman <[email protected]>
Cc Jiri Slaby <[email protected]>
Quoted 3 lines

thanks,

Signature · 2 lines
js
suse labs