The race is that con_close() can sleep, and drops the BKL while tty->count==1. But another thread can come into init_dev() and will take a new ref against the tty and start using it. But con_close() doesn't notice that new ref and proceeds to null out tty->driver_data while someone else is using the resurrected tty. So the patch serialises con_close() against init_dev() with tty_sem. Here's a test app which reproduced the oops instantly on 2-way. It realy needs to be run against all tty-capable devices. /* * Run this against a tty which nobody currently has open, such as /dev/tty9 */ #include #include #include #include #include #include void doit(char *filename) { int fd,x; fd = open(filename, O_RDWR); if (fd < 0) { perror("open"); exit(1); } ioctl(fd, KDKBDREP, &x); close(fd); } main(int argc, char *argv[]) { char *filename = argv[1]; for ( ; ; ) doit(filename); } --- 25-akpm/drivers/char/tty_io.c | 2 +- 25-akpm/drivers/char/vt.c | 14 ++++++++++++++ 25-akpm/include/linux/tty.h | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff -puN drivers/char/vt.c~tty-race-fix-43 drivers/char/vt.c --- 25/drivers/char/vt.c~tty-race-fix-43 2004-04-03 02:59:46.633263608 -0800 +++ 25-akpm/drivers/char/vt.c 2004-04-03 02:59:46.644261936 -0800 @@ -2480,8 +2480,16 @@ static int con_open(struct tty_struct *t return ret; } +/* + * We take tty_sem in here to prevent another thread from coming in via init_dev + * and taking a ref against the tty while we're in the process of forgetting + * about it and cleaning things up. + * + * This is because vcs_remove_devfs() can sleep and will drop the BKL. + */ static void con_close(struct tty_struct *tty, struct file *filp) { + down(&tty_sem); acquire_console_sem(); if (tty && tty->count == 1) { struct vt_struct *vt; @@ -2492,9 +2500,15 @@ static void con_close(struct tty_struct tty->driver_data = 0; release_console_sem(); vcs_remove_devfs(tty); + up(&tty_sem); + /* + * tty_sem is released, but we still hold BKL, so there is + * still exclusion against init_dev() + */ return; } release_console_sem(); + up(&tty_sem); } static void vc_init(unsigned int currcons, unsigned int rows, diff -puN drivers/char/tty_io.c~tty-race-fix-43 drivers/char/tty_io.c --- 25/drivers/char/tty_io.c~tty-race-fix-43 2004-04-03 02:59:46.635263304 -0800 +++ 25-akpm/drivers/char/tty_io.c 2004-04-03 02:59:46.646261632 -0800 @@ -123,7 +123,7 @@ LIST_HEAD(tty_drivers); /* linked list struct tty_ldisc ldiscs[NR_LDISCS]; /* line disc dispatch table */ /* Semaphore to protect creating and releasing a tty */ -static DECLARE_MUTEX(tty_sem); +DECLARE_MUTEX(tty_sem); #ifdef CONFIG_UNIX98_PTYS extern struct tty_driver *ptm_driver; /* Unix98 pty masters; for /dev/ptmx */ diff -puN include/linux/tty.h~tty-race-fix-43 include/linux/tty.h --- 25/include/linux/tty.h~tty-race-fix-43 2004-04-03 02:59:46.638262848 -0800 +++ 25-akpm/include/linux/tty.h 2004-04-03 02:59:46.646261632 -0800 @@ -363,6 +363,9 @@ extern void tty_flip_buffer_push(struct extern int tty_get_baud_rate(struct tty_struct *tty); extern int tty_termios_baud_rate(struct termios *termios); +struct semaphore; +extern struct semaphore tty_sem; + /* n_tty.c */ extern struct tty_ldisc tty_ldisc_N_TTY; _