Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tty: handle the case where we cannot restore a line discipline

Historically the N_TTY driver could never fail but this has become broken over
time. Rather than trying to rewrite half the ldisc layer to fix the breakage
introduce a second level of fallback with an N_NULL ldisc which cannot fail,
and thus restore the guarantees required by the ldisc layer.

We still try and fail to N_TTY first. It's much more useful to find yourself
back in your old ldisc (first attempt) or in N_TTY (second attempt), and while
I'm not aware of any code out there that makes those assumptions it's good to
drive(r) defensively.

Signed-off-by: Alan Cox <alan@linux.intel.com>
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Tested-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Alan Cox and committed by
Greg Kroah-Hartman
8a8dabf2 47f58e32

+113 -15
+2 -1
drivers/tty/Makefile
··· 1 1 obj-$(CONFIG_TTY) += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \ 2 2 tty_buffer.o tty_port.o tty_mutex.o \ 3 - tty_ldsem.o tty_baudrate.o tty_jobctrl.o 3 + tty_ldsem.o tty_baudrate.o tty_jobctrl.o \ 4 + n_null.o 4 5 obj-$(CONFIG_LEGACY_PTYS) += pty.o 5 6 obj-$(CONFIG_UNIX98_PTYS) += pty.o 6 7 obj-$(CONFIG_AUDIT) += tty_audit.o
+80
drivers/tty/n_null.c
··· 1 + #include <linux/types.h> 2 + #include <linux/errno.h> 3 + #include <linux/tty.h> 4 + #include <linux/module.h> 5 + 6 + /* 7 + * n_null.c - Null line discipline used in the failure path 8 + * 9 + * Copyright (C) Intel 2017 10 + * 11 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 + * 13 + * This program is free software; you can redistribute it and/or modify 14 + * it under the terms of the GNU General Public License version 2 15 + * as published by the Free Software Foundation. 16 + * 17 + * This program is distributed in the hope that it will be useful, 18 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 + * GNU General Public License for more details. 21 + * 22 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 + */ 24 + 25 + static int n_null_open(struct tty_struct *tty) 26 + { 27 + return 0; 28 + } 29 + 30 + static void n_null_close(struct tty_struct *tty) 31 + { 32 + } 33 + 34 + static ssize_t n_null_read(struct tty_struct *tty, struct file *file, 35 + unsigned char __user * buf, size_t nr) 36 + { 37 + return -EOPNOTSUPP; 38 + } 39 + 40 + static ssize_t n_null_write(struct tty_struct *tty, struct file *file, 41 + const unsigned char *buf, size_t nr) 42 + { 43 + return -EOPNOTSUPP; 44 + } 45 + 46 + static void n_null_receivebuf(struct tty_struct *tty, 47 + const unsigned char *cp, char *fp, 48 + int cnt) 49 + { 50 + } 51 + 52 + static struct tty_ldisc_ops null_ldisc = { 53 + .owner = THIS_MODULE, 54 + .magic = TTY_LDISC_MAGIC, 55 + .name = "n_null", 56 + .open = n_null_open, 57 + .close = n_null_close, 58 + .read = n_null_read, 59 + .write = n_null_write, 60 + .receive_buf = n_null_receivebuf 61 + }; 62 + 63 + static int __init n_null_init(void) 64 + { 65 + BUG_ON(tty_register_ldisc(N_NULL, &null_ldisc)); 66 + return 0; 67 + } 68 + 69 + static void __exit n_null_exit(void) 70 + { 71 + tty_unregister_ldisc(N_NULL); 72 + } 73 + 74 + module_init(n_null_init); 75 + module_exit(n_null_exit); 76 + 77 + MODULE_LICENSE("GPL"); 78 + MODULE_AUTHOR("Alan Cox"); 79 + MODULE_ALIAS_LDISC(N_NULL); 80 + MODULE_DESCRIPTION("Null ldisc driver");
+30 -14
drivers/tty/tty_ldisc.c
··· 492 492 } 493 493 494 494 /** 495 + * tty_ldisc_failto - helper for ldisc failback 496 + * @tty: tty to open the ldisc on 497 + * @ld: ldisc we are trying to fail back to 498 + * 499 + * Helper to try and recover a tty when switching back to the old 500 + * ldisc fails and we need something attached. 501 + */ 502 + 503 + static int tty_ldisc_failto(struct tty_struct *tty, int ld) 504 + { 505 + struct tty_ldisc *disc = tty_ldisc_get(tty, ld); 506 + int r; 507 + 508 + if (IS_ERR(disc)) 509 + return PTR_ERR(disc); 510 + tty->ldisc = disc; 511 + tty_set_termios_ldisc(tty, ld); 512 + if ((r = tty_ldisc_open(tty, disc)) < 0) 513 + tty_ldisc_put(disc); 514 + return r; 515 + } 516 + 517 + /** 495 518 * tty_ldisc_restore - helper for tty ldisc change 496 519 * @tty: tty to recover 497 520 * @old: previous ldisc ··· 525 502 526 503 static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) 527 504 { 528 - struct tty_ldisc *new_ldisc; 529 - int r; 530 - 531 505 /* There is an outstanding reference here so this is safe */ 532 506 old = tty_ldisc_get(tty, old->ops->num); 533 507 WARN_ON(IS_ERR(old)); ··· 532 512 tty_set_termios_ldisc(tty, old->ops->num); 533 513 if (tty_ldisc_open(tty, old) < 0) { 534 514 tty_ldisc_put(old); 535 - /* This driver is always present */ 536 - new_ldisc = tty_ldisc_get(tty, N_TTY); 537 - if (IS_ERR(new_ldisc)) 538 - panic("n_tty: get"); 539 - tty->ldisc = new_ldisc; 540 - tty_set_termios_ldisc(tty, N_TTY); 541 - r = tty_ldisc_open(tty, new_ldisc); 542 - if (r < 0) 543 - panic("Couldn't open N_TTY ldisc for " 544 - "%s --- error %d.", 545 - tty_name(tty), r); 515 + /* The traditional behaviour is to fall back to N_TTY, we 516 + want to avoid falling back to N_NULL unless we have no 517 + choice to avoid the risk of breaking anything */ 518 + if (tty_ldisc_failto(tty, N_TTY) < 0 && 519 + tty_ldisc_failto(tty, N_NULL) < 0) 520 + panic("Couldn't open N_NULL ldisc for %s.", 521 + tty_name(tty)); 546 522 } 547 523 } 548 524
+1
include/uapi/linux/tty.h
··· 36 36 #define N_TRACEROUTER 24 /* Trace data routing for MIPI P1149.7 */ 37 37 #define N_NCI 25 /* NFC NCI UART */ 38 38 #define N_SPEAKUP 26 /* Speakup communication with synths */ 39 + #define N_NULL 27 /* Null ldisc used for error handling */ 39 40 40 41 #endif /* _UAPI_LINUX_TTY_H */