2024-04-23 16:33:57

by Linus Torvalds

[permalink] [raw]
Subject: [PATCH] tty: add the option to have a tty reject a new ldisc

.. and use it to limit the virtual terminals to just N_TTY. They are
kind of special, and in particular, the "con_write()" routine violates
the "writes cannot sleep" rule that some ldiscs rely on.

This avoids the

BUG: sleeping function called from invalid context at kernel/printk/printk.c:2659

when N_GSM has been attached to a virtual console, and gsmld_write()
calls con_write() while holding a spinlock, and con_write() then tries
to get the console lock.

Tested-by: Tetsuo Handa <[email protected]>
Cc: Jiri Slaby <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Daniel Starke <[email protected]>
Reported-by: syzbot <[email protected]>
Closes: https://syzkaller.appspot.com/bug?extid=dbac96d8e73b61aa559c
Signed-off-by: Linus Torvalds <[email protected]>
---
drivers/tty/tty_ldisc.c | 6 ++++++
drivers/tty/vt/vt.c | 10 ++++++++++
include/linux/tty_driver.h | 8 ++++++++
3 files changed, 24 insertions(+)

diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index 3f68e213df1f..d80e9d4c974b 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -545,6 +545,12 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
goto out;
}

+ if (tty->ops->ldisc_ok) {
+ retval = tty->ops->ldisc_ok(tty, disc);
+ if (retval)
+ goto out;
+ }
+
old_ldisc = tty->ldisc;

/* Shutdown the old discipline. */
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 9b5b98dfc8b4..cd87e3d1291e 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -3576,6 +3576,15 @@ static void con_cleanup(struct tty_struct *tty)
tty_port_put(&vc->port);
}

+/*
+ * We can't deal with anything but the N_TTY ldisc,
+ * because we can sleep in our write() routine.
+ */
+static int con_ldisc_ok(struct tty_struct *tty, int ldisc)
+{
+ return ldisc == N_TTY ? 0 : -EINVAL;
+}
+
static int default_color = 7; /* white */
static int default_italic_color = 2; // green (ASCII)
static int default_underline_color = 3; // cyan (ASCII)
@@ -3695,6 +3704,7 @@ static const struct tty_operations con_ops = {
.resize = vt_resize,
.shutdown = con_shutdown,
.cleanup = con_cleanup,
+ .ldisc_ok = con_ldisc_ok,
};

static struct cdev vc0_cdev;
diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h
index 7372124fbf90..dd4b31ce6d5d 100644
--- a/include/linux/tty_driver.h
+++ b/include/linux/tty_driver.h
@@ -154,6 +154,13 @@ struct serial_struct;
*
* Optional. Called under the @tty->termios_rwsem. May sleep.
*
+ * @ldisc_ok: ``int ()(struct tty_struct *tty, int ldisc)``
+ *
+ * This routine allows the @tty driver to decide if it can deal
+ * with a particular @ldisc.
+ *
+ * Optional. Called under the @tty->ldisc_sem and @tty->termios_rwsem.
+ *
* @set_ldisc: ``void ()(struct tty_struct *tty)``
*
* This routine allows the @tty driver to be notified when the device's
@@ -372,6 +379,7 @@ struct tty_operations {
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
+ int (*ldisc_ok)(struct tty_struct *tty, int ldisc);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, u8 ch);
--
2.44.0.330.g4d18c88175



2024-04-24 06:25:24

by Jiri Slaby

[permalink] [raw]
Subject: Re: [PATCH] tty: add the option to have a tty reject a new ldisc

On 23. 04. 24, 18:33, Linus Torvalds wrote:
> ... and use it to limit the virtual terminals to just N_TTY. They are
> kind of special, and in particular, the "con_write()" routine violates
> the "writes cannot sleep" rule that some ldiscs rely on.
>
> This avoids the
>
> BUG: sleeping function called from invalid context at kernel/printk/printk.c:2659
>
> when N_GSM has been attached to a virtual console, and gsmld_write()
> calls con_write() while holding a spinlock, and con_write() then tries
> to get the console lock.
..> --- a/drivers/tty/tty_ldisc.c
> +++ b/drivers/tty/tty_ldisc.c
> @@ -545,6 +545,12 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
> goto out;
> }
>
> + if (tty->ops->ldisc_ok) {




> + retval = tty->ops->ldisc_ok(tty, disc);
> + if (retval)
> + goto out;

This caught my eye as it looks like:
if (ldisc_ok)
die;

Perhaps dub it ldisc_check()? Alternatively, make it return a bool and
invert the logic here? That would make the ugly ternary in
con_ldisc_ok() go away too.

But whatever, it finally evades that group of bugs.

Now, I am not sure what the problem with attaching some weird ldisc to a
pty was. I will take a look next week (I am unavailable ATM).

thanks,
--
js
suse labs