[PATCH] tty races

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



There are a couple of tty race conditions, which lead to inconsistent tty 
reference counting and tty layer oopses.

The first is a tty_open vs. tty_close race in drivers/char/tty.io.c. 
Basically, from the time that the tty->count is deemed to be 1 and that we 
are going to free it to the time that TTY_CLOSING bit is set, needs to be 
atomic with respect to the manipulation of tty->count in init_dev(). This 
atomicity was previously guarded by the BKL. However, this is no longer 
true with the addition of a down() call in the middle of the 
release_dev()'s atomic path. So either the down() needs to be moved 
outside the atomic patch or dropped. I would vote for simply dropping it 
as i don't see why it is necessary.

The second race is tty_open vs. tty_open. This race I've seen when the 
virtual console is the tty driver. In con_open(),  vc_allocate() is called 
if the tty->count is 1. However, this check of the tty->count is not 
guarded by the 'tty_sem'. Thus, it is possible for con_open(), to never 
see the tty->count as 1, and thus never call vc_allocate(). This leads to 
a NULL filp->private_data, and an oops.

The test case below reproduces these problems, and the patch fixes it. The 
test case uses /dev/tty9, which is generally restricted to root for 
open(). It may be able to exploit these races using pseudo terminals, 
although i wasn't able to. A previous report of this issue, with an oops 
trace was: http://www.ussg.iu.edu/hypermail/linux/kernel/0503.2/0017.html

thanks,

-Jason


--- linux/drivers/char/tty_io.c.bak
+++ linux/drivers/char/tty_io.c
@@ -1596,14 +1596,9 @@ static void release_dev(struct file * fi
 	 * each iteration we avoid any problems.
 	 */
 	while (1) {
-		/* Guard against races with tty->count changes elsewhere and
-		   opens on /dev/tty */
-		   
-		down(&tty_sem);
 		tty_closing = tty->count <= 1;
 		o_tty_closing = o_tty &&
 			(o_tty->count <= (pty_master ? 1 : 0));
-		up(&tty_sem);
 		do_sleep = 0;
 
 		if (tty_closing) {
@@ -1640,7 +1635,6 @@ static void release_dev(struct file * fi
 	 * block, so it's safe to proceed with closing.
 	 */
 	 
-	down(&tty_sem);
 	if (pty_master) {
 		if (--o_tty->count < 0) {
 			printk(KERN_WARNING "release_dev: bad pty slave count "
@@ -1654,7 +1648,6 @@ static void release_dev(struct file * fi
 		       tty->count, tty_name(tty, buf));
 		tty->count = 0;
 	}
-	up(&tty_sem);
 	
 	/*
 	 * We've decremented tty->count, so we need to remove this file
@@ -1844,9 +1837,10 @@ retry_open:
 	}
 got_driver:
 	retval = init_dev(driver, index, &tty);
-	up(&tty_sem);
-	if (retval)
+	if (retval) {
+		up(&tty_sem);
 		return retval;
+	}
 
 	filp->private_data = tty;
 	file_move(filp, &tty->tty_files);
@@ -1863,6 +1857,7 @@ got_driver:
 		else
 			retval = -ENODEV;
 	}
+	up(&tty_sem);
 	filp->f_flags = saved_flags;
 
 	if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && !capable(CAP_SYS_ADMIN))



#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>
#include <linux/fb.h>
#include <linux/vt.h>
#include <linux/kd.h>

#define NTHREADS 300

void *thread_function();
int open_fail_num;
int open_success;

int
main(int argc, char *argv[])
{
  	int i, j;
  	pthread_t thread_id[NTHREADS];

  	for(;;) {
      		for(i=0; i < NTHREADS; i++) {
	  		pthread_create(&thread_id[i], NULL, &thread_function, NULL);
		}
      		for(j=0; j < NTHREADS; j++) {
	  		pthread_join(thread_id[j], NULL); 
		}
      		printf("open failures: %i\n", open_fail_num);
      		printf("open success: %i\n", open_success);
    	}
}

void *thread_function()
{
  	int fd;
  	time_t t;
  	int val;
  	int ret;

  	fd = open("/dev/tty9", O_RDWR);

  	val = 0;
  	//call an ioctl
	ret = ioctl(fd, KDGETMODE, &val);
  	if (ret != 0) {
		perror("ioctl error\n");
  	}

  	if (fd < 0) {
      		open_fail_num++;
    	} else {
      		open_success++;
    	}
  	/* just waste some random time */
  	t = (time((time_t *)0) &31L) << 6;
  	while (t-- > 0)
    		(void)time((time_t *)0);
  	close(fd);	
}




-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux