Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 24ec839c authored by Peter Zijlstra's avatar Peter Zijlstra Committed by Linus Torvalds
Browse files

[PATCH] tty: ->signal->tty locking



Fix the locking of signal->tty.

Use ->sighand->siglock to protect ->signal->tty; this lock is already used
by most other members of ->signal/->sighand.  And unless we are 'current'
or the tasklist_lock is held we need ->siglock to access ->signal anyway.

(NOTE: sys_unshare() is broken wrt ->sighand locking rules)

Note that tty_mutex is held over tty destruction, so while holding
tty_mutex any tty pointer remains valid.  Otherwise the lifetime of ttys
are governed by their open file handles.  This leaves some holes for tty
access from signal->tty (or any other non file related tty access).

It solves the tty SLAB scribbles we were seeing.

(NOTE: the change from group_send_sig_info to __group_send_sig_info needs to
       be examined by someone familiar with the security framework, I think
       it is safe given the SEND_SIG_PRIV from other __group_send_sig_info
       invocations)

[schwidefsky@de.ibm.com: 3270 fix]
[akpm@osdl.org: various post-viro fixes]
Signed-off-by: default avatarPeter Zijlstra <a.p.zijlstra@chello.nl>
Acked-by: default avatarAlan Cox <alan@redhat.com>
Cc: Oleg Nesterov <oleg@tv-sign.ru>
Cc: Prarit Bhargava <prarit@redhat.com>
Cc: Chris Wright <chrisw@sous-sol.org>
Cc: Roland McGrath <roland@redhat.com>
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Cc: James Morris <jmorris@namei.org>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Jeff Dike <jdike@addtoit.com>
Cc: Martin Schwidefsky <schwidefsky@de.ibm.com>
Cc: Jan Kara <jack@ucw.cz>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 562f9c57
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -423,9 +423,7 @@ asmlinkage int solaris_procids(int cmd, s32 pid, s32 pgid)
			   Solaris setpgrp and setsid? */
			ret = sys_setpgid(0, 0);
			if (ret) return ret;
			mutex_lock(&tty_mutex);
			current->signal->tty = NULL;
			mutex_unlock(&tty_mutex);
			proc_clear_tty(current);
			return process_group(current);
		}
	case 2: /* getsid */
+4 −3
Original line number Diff line number Diff line
@@ -39,12 +39,13 @@ static long execve1(char *file, char __user * __user *argv,
		    char __user *__user *env)
{
        long error;
	struct tty_struct *tty;

#ifdef CONFIG_TTY_LOG
	mutex_lock(&tty_mutex);
	task_lock(current);	/* FIXME:  is this needed ? */
	log_exec(argv, current->signal->tty);
	task_unlock(current);
	tty = get_current_tty();
	if (tty)
		log_exec(argv, tty);
	mutex_unlock(&tty_mutex);
#endif
        error = do_execve(file, argv, env, &current->thread.regs);
+141 −96
Original line number Diff line number Diff line
@@ -126,7 +126,7 @@ EXPORT_SYMBOL(tty_std_termios);
   
LIST_HEAD(tty_drivers);			/* linked list of tty drivers */

/* Semaphore to protect creating and releasing a tty. This is shared with
/* Mutex to protect creating and releasing a tty. This is shared with
   vt.c for deeply disgusting hack reasons */
DEFINE_MUTEX(tty_mutex);
EXPORT_SYMBOL(tty_mutex);
@@ -259,18 +259,6 @@ static int check_tty_count(struct tty_struct *tty, const char *routine)
 * Tty buffer allocation management
 */


/**
 *	tty_buffer_free_all		-	free buffers used by a tty
 *	@tty: tty to free from
 *
 *	Remove all the buffers pending on a tty whether queued with data
 *	or in the free ring. Must be called when the tty is no longer in use
 *
 *	Locking: none
 */


/**
 *	tty_buffer_free_all		-	free buffers used by a tty
 *	@tty: tty to free from
@@ -614,7 +602,7 @@ EXPORT_SYMBOL_GPL(tty_prepare_flip_string_flags);
 *	they are not on hot paths so a little discipline won't do 
 *	any harm.
 *
 *	Locking: takes termios_sem
 *	Locking: takes termios_mutex
 */
 
static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
@@ -915,7 +903,7 @@ static void tty_ldisc_enable(struct tty_struct *tty)
 *	context.
 *
 *	Locking: takes tty_ldisc_lock.
 *		called functions take termios_sem
 *		 called functions take termios_mutex
 */
 
static int tty_set_ldisc(struct tty_struct *tty, int ldisc)
@@ -1270,9 +1258,9 @@ EXPORT_SYMBOL_GPL(tty_ldisc_flush);
 *		  redirect lock for undoing redirection
 *		  file list lock for manipulating list of ttys
 *		  tty_ldisc_lock from called functions
 *		termios_sem resetting termios data
 *		  termios_mutex resetting termios data
 *		  tasklist_lock to walk task list for hangup event
 *
 *		    ->siglock to protect ->signal/->sighand
 */
static void do_tty_hangup(struct work_struct *work)
{
@@ -1354,14 +1342,18 @@ static void do_tty_hangup(struct work_struct *work)
	read_lock(&tasklist_lock);
	if (tty->session > 0) {
		do_each_task_pid(tty->session, PIDTYPE_SID, p) {
			spin_lock_irq(&p->sighand->siglock);
			if (p->signal->tty == tty)
				p->signal->tty = NULL;
			if (!p->signal->leader)
			if (!p->signal->leader) {
				spin_unlock_irq(&p->sighand->siglock);
				continue;
			group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
			group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
			}
			__group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
			__group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
			if (tty->pgrp > 0)
				p->signal->tty_old_pgrp = tty->pgrp;
			spin_unlock_irq(&p->sighand->siglock);
		} while_each_task_pid(tty->session, PIDTYPE_SID, p);
	}
	read_unlock(&tasklist_lock);
@@ -1453,6 +1445,14 @@ int tty_hung_up_p(struct file * filp)

EXPORT_SYMBOL(tty_hung_up_p);

static void session_clear_tty(pid_t session)
{
	struct task_struct *p;
	do_each_task_pid(session, PIDTYPE_SID, p) {
		proc_clear_tty(p);
	} while_each_task_pid(session, PIDTYPE_SID, p);
}

/**
 *	disassociate_ctty	-	disconnect controlling tty
 *	@on_exit: true if exiting so need to "hang up" the session
@@ -1469,31 +1469,35 @@ EXPORT_SYMBOL(tty_hung_up_p);
 *	The argument on_exit is set to 1 if called when a process is
 *	exiting; it is 0 if called by the ioctl TIOCNOTTY.
 *
 *	Locking: tty_mutex is taken to protect current->signal->tty
 *	Locking:
 *		BKL is taken for hysterical raisins
 *		Tasklist lock is taken (under tty_mutex) to walk process
 *		lists for the session.
 *		  tty_mutex is taken to protect tty
 *		  ->siglock is taken to protect ->signal/->sighand
 *		  tasklist_lock is taken to walk process list for sessions
 *		    ->siglock is taken to protect ->signal/->sighand
 */

void disassociate_ctty(int on_exit)
{
	struct tty_struct *tty;
	struct task_struct *p;
	int tty_pgrp = -1;
	int session;

	lock_kernel();

	mutex_lock(&tty_mutex);
	tty = current->signal->tty;
	tty = get_current_tty();
	if (tty) {
		tty_pgrp = tty->pgrp;
		mutex_unlock(&tty_mutex);
		/* XXX: here we race, there is nothing protecting tty */
		if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY)
			tty_vhangup(tty);
	} else {
		if (current->signal->tty_old_pgrp) {
			kill_pg(current->signal->tty_old_pgrp, SIGHUP, on_exit);
			kill_pg(current->signal->tty_old_pgrp, SIGCONT, on_exit);
		pid_t old_pgrp = current->signal->tty_old_pgrp;
		if (old_pgrp) {
			kill_pg(old_pgrp, SIGHUP, on_exit);
			kill_pg(old_pgrp, SIGCONT, on_exit);
		}
		mutex_unlock(&tty_mutex);
		unlock_kernel();	
@@ -1505,19 +1509,29 @@ void disassociate_ctty(int on_exit)
			kill_pg(tty_pgrp, SIGCONT, on_exit);
	}

	/* Must lock changes to tty_old_pgrp */
	mutex_lock(&tty_mutex);
	spin_lock_irq(&current->sighand->siglock);
	current->signal->tty_old_pgrp = 0;
	session = current->signal->session;
	spin_unlock_irq(&current->sighand->siglock);

	mutex_lock(&tty_mutex);
	/* It is possible that do_tty_hangup has free'd this tty */
	tty = get_current_tty();
	if (tty) {
		tty->session = 0;
	tty->pgrp = -1;
		tty->pgrp = 0;
	} else {
#ifdef TTY_DEBUG_HANGUP
		printk(KERN_DEBUG "error attempted to write to tty [0x%p]"
		       " = NULL", tty);
#endif
	}
	mutex_unlock(&tty_mutex);

	/* Now clear signal->tty under the lock */
	read_lock(&tasklist_lock);
	do_each_task_pid(current->signal->session, PIDTYPE_SID, p) {
		p->signal->tty = NULL;
	} while_each_task_pid(current->signal->session, PIDTYPE_SID, p);
	session_clear_tty(session);
	read_unlock(&tasklist_lock);
	mutex_unlock(&tty_mutex);
	unlock_kernel();
}

@@ -2337,16 +2351,10 @@ static void release_dev(struct file * filp)
	 * tty.
	 */
	if (tty_closing || o_tty_closing) {
		struct task_struct *p;

		read_lock(&tasklist_lock);
		do_each_task_pid(tty->session, PIDTYPE_SID, p) {
			p->signal->tty = NULL;
		} while_each_task_pid(tty->session, PIDTYPE_SID, p);
		session_clear_tty(tty->session);
		if (o_tty)
			do_each_task_pid(o_tty->session, PIDTYPE_SID, p) {
				p->signal->tty = NULL;
			} while_each_task_pid(o_tty->session, PIDTYPE_SID, p);
			session_clear_tty(o_tty->session);
		read_unlock(&tasklist_lock);
	}

@@ -2443,9 +2451,9 @@ static void release_dev(struct file * filp)
 *	The termios state of a pty is reset on first open so that
 *	settings don't persist across reuse.
 *
 *	Locking: tty_mutex protects current->signal->tty, get_tty_driver and
 *		init_dev work. tty->count should protect the rest.
 *		task_lock is held to update task details for sessions
 *	Locking: tty_mutex protects tty, get_tty_driver and init_dev work.
 *		 tty->count should protect the rest.
 *		 ->siglock protects ->signal/->sighand
 */

static int tty_open(struct inode * inode, struct file * filp)
@@ -2467,12 +2475,13 @@ retry_open:
	mutex_lock(&tty_mutex);

	if (device == MKDEV(TTYAUX_MAJOR,0)) {
		if (!current->signal->tty) {
		tty = get_current_tty();
		if (!tty) {
			mutex_unlock(&tty_mutex);
			return -ENXIO;
		}
		driver = current->signal->tty->driver;
		index = current->signal->tty->index;
		driver = tty->driver;
		index = tty->index;
		filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
		/* noctty = 1; */
		goto got_driver;
@@ -2547,17 +2556,16 @@ got_driver:
			filp->f_op = &tty_fops;
		goto retry_open;
	}

	mutex_lock(&tty_mutex);
	spin_lock_irq(&current->sighand->siglock);
	if (!noctty &&
	    current->signal->leader &&
	    !current->signal->tty &&
	    tty->session == 0) {
	    	task_lock(current);
		current->signal->tty = tty;
		task_unlock(current);
		current->signal->tty_old_pgrp = 0;
		tty->session = current->signal->session;
		tty->pgrp = process_group(current);
	}
	    tty->session == 0)
		__proc_set_tty(current, tty);
	spin_unlock_irq(&current->sighand->siglock);
	mutex_unlock(&tty_mutex);
	return 0;
}

@@ -2747,7 +2755,7 @@ static int tiocsti(struct tty_struct *tty, char __user *p)
 *
 *	Copies the kernel idea of the window size into the user buffer.
 *
 *	Locking: tty->termios_sem is taken to ensure the winsize data
 *	Locking: tty->termios_mutex is taken to ensure the winsize data
 *		is consistent.
 */

@@ -2774,8 +2782,8 @@ static int tiocgwinsz(struct tty_struct *tty, struct winsize __user * arg)
 *	Locking:
 *		Called function use the console_sem is used to ensure we do
 *	not try and resize the console twice at once.
 *		The tty->termios_sem is used to ensure we don't double
 *	resize and get confused. Lock order - tty->termios.sem before
 *		The tty->termios_mutex is used to ensure we don't double
 *	resize and get confused. Lock order - tty->termios_mutex before
 *	console sem
 */

@@ -2880,25 +2888,28 @@ static int fionbio(struct file *file, int __user *p)
 *	leader to set this tty as the controlling tty for the session.
 *
 *	Locking:
 *		Takes tasklist lock internally to walk sessions
 *		Takes task_lock() when updating signal->tty
 *		Takes tty_mutex() to protect tty instance
 *
 *		Takes tasklist_lock internally to walk sessions
 *		Takes ->siglock() when updating signal->tty
 */

static int tiocsctty(struct tty_struct *tty, int arg)
{
	struct task_struct *p;

	int ret = 0;
	if (current->signal->leader &&
	    (current->signal->session == tty->session))
		return 0;
		return ret;

	mutex_lock(&tty_mutex);
	/*
	 * The process must be a session leader and
	 * not have a controlling tty already.
	 */
	if (!current->signal->leader || current->signal->tty)
		return -EPERM;
	if (!current->signal->leader || current->signal->tty) {
		ret = -EPERM;
		goto unlock;
	}

	if (tty->session > 0) {
		/*
		 * This tty is already the controlling
@@ -2908,24 +2919,18 @@ static int tiocsctty(struct tty_struct *tty, int arg)
			/*
			 * Steal it away
			 */

			read_lock(&tasklist_lock);
			do_each_task_pid(tty->session, PIDTYPE_SID, p) {
				p->signal->tty = NULL;
			} while_each_task_pid(tty->session, PIDTYPE_SID, p);
			session_clear_tty(tty->session);
			read_unlock(&tasklist_lock);
		} else
			return -EPERM;
		} else {
			ret = -EPERM;
			goto unlock;
		}
	mutex_lock(&tty_mutex);
	task_lock(current);
	current->signal->tty = tty;
	task_unlock(current);
	}
	proc_set_tty(current, tty);
unlock:
	mutex_unlock(&tty_mutex);
	current->signal->tty_old_pgrp = 0;
	tty->session = current->signal->session;
	tty->pgrp = process_group(current);
	return 0;
	return ret;
}

/**
@@ -2937,7 +2942,7 @@ static int tiocsctty(struct tty_struct *tty, int arg)
 *	Obtain the process group of the tty. If there is no process group
 *	return an error.
 *
 *	Locking: none. Reference to ->signal->tty is safe.
 *	Locking: none. Reference to current->signal->tty is safe.
 */

static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
@@ -2995,7 +3000,7 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t
 *	Obtain the session id of the tty. If there is no session
 *	return an error.
 *
 *	Locking: none. Reference to ->signal->tty is safe.
 *	Locking: none. Reference to current->signal->tty is safe.
 */

static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
@@ -3214,14 +3219,11 @@ int tty_ioctl(struct inode * inode, struct file * file,
			clear_bit(TTY_EXCLUSIVE, &tty->flags);
			return 0;
		case TIOCNOTTY:
			/* FIXME: taks lock or tty_mutex ? */
			if (current->signal->tty != tty)
				return -ENOTTY;
			if (current->signal->leader)
				disassociate_ctty(0);
			task_lock(current);
			current->signal->tty = NULL;
			task_unlock(current);
			proc_clear_tty(current);
			return 0;
		case TIOCSCTTY:
			return tiocsctty(tty, arg);
@@ -3834,9 +3836,52 @@ int tty_unregister_driver(struct tty_driver *driver)
	cdev_del(&driver->cdev);
	return 0;
}

EXPORT_SYMBOL(tty_unregister_driver);

dev_t tty_devnum(struct tty_struct *tty)
{
	return MKDEV(tty->driver->major, tty->driver->minor_start) + tty->index;
}
EXPORT_SYMBOL(tty_devnum);

void proc_clear_tty(struct task_struct *p)
{
	spin_lock_irq(&p->sighand->siglock);
	p->signal->tty = NULL;
	spin_unlock_irq(&p->sighand->siglock);
}
EXPORT_SYMBOL(proc_clear_tty);

void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty)
{
	if (tty) {
		tty->session = tsk->signal->session;
		tty->pgrp = process_group(tsk);
	}
	tsk->signal->tty = tty;
	tsk->signal->tty_old_pgrp = 0;
}

void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty)
{
	spin_lock_irq(&tsk->sighand->siglock);
	__proc_set_tty(tsk, tty);
	spin_unlock_irq(&tsk->sighand->siglock);
}

struct tty_struct *get_current_tty(void)
{
	struct tty_struct *tty;
	WARN_ON_ONCE(!mutex_is_locked(&tty_mutex));
	tty = current->signal->tty;
	/*
	 * session->tty can be changed/cleared from under us, make sure we
	 * issue the load. The obtained pointer, when not NULL, is valid as
	 * long as we hold tty_mutex.
	 */
	barrier();
	return tty;
}

/*
 * Initialize the console device. This is called *early*, so
+8 −4
Original line number Diff line number Diff line
@@ -424,11 +424,15 @@ fs3270_open(struct inode *inode, struct file *filp)
	minor = iminor(filp->f_dentry->d_inode);
	/* Check for minor 0 multiplexer. */
	if (minor == 0) {
		if (!current->signal->tty)
		struct tty_struct *tty;
		mutex_lock(&tty_mutex);
		tty = get_current_tty();
		if (!tty || tty->driver->major != IBM_TTY3270_MAJOR) {
			mutex_unlock(&tty_mutex);
			return -ENODEV;
		if (current->signal->tty->driver->major != IBM_TTY3270_MAJOR)
			return -ENODEV;
		minor = current->signal->tty->index + RAW3270_FIRSTMINOR;
		}
		minor = tty->index + RAW3270_FIRSTMINOR;
		mutex_unlock(&tty_mutex);
	}
	/* Check if some other program is already using fullscreen mode. */
	fp = (struct fs3270 *) raw3270_find_view(&fs3270_fn, minor);
+8 −6
Original line number Diff line number Diff line
@@ -828,6 +828,7 @@ static inline int need_print_warning(struct dquot *dquot)
static void print_warning(struct dquot *dquot, const char warntype)
{
	char *msg = NULL;
	struct tty_struct *tty;
	int flag = (warntype == BHARDWARN || warntype == BSOFTLONGWARN) ? DQ_BLKS_B :
	  ((warntype == IHARDWARN || warntype == ISOFTLONGWARN) ? DQ_INODES_B : 0);

@@ -835,14 +836,15 @@ static void print_warning(struct dquot *dquot, const char warntype)
		return;

	mutex_lock(&tty_mutex);
	if (!current->signal->tty)
	tty = get_current_tty();
	if (!tty)
		goto out_lock;
	tty_write_message(current->signal->tty, dquot->dq_sb->s_id);
	tty_write_message(tty, dquot->dq_sb->s_id);
	if (warntype == ISOFTWARN || warntype == BSOFTWARN)
		tty_write_message(current->signal->tty, ": warning, ");
		tty_write_message(tty, ": warning, ");
	else
		tty_write_message(current->signal->tty, ": write failed, ");
	tty_write_message(current->signal->tty, quotatypes[dquot->dq_type]);
		tty_write_message(tty, ": write failed, ");
	tty_write_message(tty, quotatypes[dquot->dq_type]);
	switch (warntype) {
		case IHARDWARN:
			msg = " file limit reached.\r\n";
@@ -863,7 +865,7 @@ static void print_warning(struct dquot *dquot, const char warntype)
			msg = " block quota exceeded.\r\n";
			break;
	}
	tty_write_message(current->signal->tty, msg);
	tty_write_message(tty, msg);
out_lock:
	mutex_unlock(&tty_mutex);
}
Loading