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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kd.h>

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-03-31 00:52:15.409815840 -0800
+++ 25-akpm/drivers/char/vt.c	2004-03-31 00:53:18.729189832 -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-03-31 00:52:15.410815688 -0800
+++ 25-akpm/drivers/char/tty_io.c	2004-03-31 00:52:15.418814472 -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-03-31 00:52:15.412815384 -0800
+++ 25-akpm/include/linux/tty.h	2004-03-31 00:52:15.419814320 -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;
 

_