mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	tty: Fix ->session locking
Currently, locking of ->session is very inconsistent; most places protect it using the legacy tty mutex, but disassociate_ctty(), __do_SAK(), tiocspgrp() and tiocgsid() don't. Two of the writers hold the ctrl_lock (because they already need it for ->pgrp), but __proc_set_tty() doesn't do that yet. On a PREEMPT=y system, an unprivileged user can theoretically abuse this broken locking to read 4 bytes of freed memory via TIOCGSID if tiocgsid() is preempted long enough at the right point. (Other things might also go wrong, especially if root-only ioctls are involved; I'm not sure about that.) Change the locking on ->session such that: - tty_lock() is held by all writers: By making disassociate_ctty() hold it. This should be fine because the same lock can already be taken through the call to tty_vhangup_session(). The tricky part is that we need to shorten the area covered by siglock to be able to take tty_lock() without ugly retry logic; as far as I can tell, this should be fine, since nothing in the signal_struct is touched in the `if (tty)` branch. - ctrl_lock is held by all writers: By changing __proc_set_tty() to hold the lock a little longer. - All readers that aren't holding tty_lock() hold ctrl_lock: By adding locking to tiocgsid() and __do_SAK(), and expanding the area covered by ctrl_lock in tiocspgrp(). Cc: stable@kernel.org Signed-off-by: Jann Horn <jannh@google.com> Reviewed-by: Jiri Slaby <jirislaby@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									54ffccbf05
								
							
						
					
					
						commit
						c8bcd9c5be
					
				
					 3 changed files with 41 additions and 14 deletions
				
			
		| 
						 | 
					@ -2897,10 +2897,14 @@ void __do_SAK(struct tty_struct *tty)
 | 
				
			||||||
	struct task_struct *g, *p;
 | 
						struct task_struct *g, *p;
 | 
				
			||||||
	struct pid *session;
 | 
						struct pid *session;
 | 
				
			||||||
	int		i;
 | 
						int		i;
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!tty)
 | 
						if (!tty)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	session = tty->session;
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irqsave(&tty->ctrl_lock, flags);
 | 
				
			||||||
 | 
						session = get_pid(tty->session);
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&tty->ctrl_lock, flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tty_ldisc_flush(tty);
 | 
						tty_ldisc_flush(tty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2932,6 +2936,7 @@ void __do_SAK(struct tty_struct *tty)
 | 
				
			||||||
		task_unlock(p);
 | 
							task_unlock(p);
 | 
				
			||||||
	} while_each_thread(g, p);
 | 
						} while_each_thread(g, p);
 | 
				
			||||||
	read_unlock(&tasklist_lock);
 | 
						read_unlock(&tasklist_lock);
 | 
				
			||||||
 | 
						put_pid(session);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -103,8 +103,8 @@ static void __proc_set_tty(struct tty_struct *tty)
 | 
				
			||||||
	put_pid(tty->session);
 | 
						put_pid(tty->session);
 | 
				
			||||||
	put_pid(tty->pgrp);
 | 
						put_pid(tty->pgrp);
 | 
				
			||||||
	tty->pgrp = get_pid(task_pgrp(current));
 | 
						tty->pgrp = get_pid(task_pgrp(current));
 | 
				
			||||||
	spin_unlock_irqrestore(&tty->ctrl_lock, flags);
 | 
					 | 
				
			||||||
	tty->session = get_pid(task_session(current));
 | 
						tty->session = get_pid(task_session(current));
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&tty->ctrl_lock, flags);
 | 
				
			||||||
	if (current->signal->tty) {
 | 
						if (current->signal->tty) {
 | 
				
			||||||
		tty_debug(tty, "current tty %s not NULL!!\n",
 | 
							tty_debug(tty, "current tty %s not NULL!!\n",
 | 
				
			||||||
			  current->signal->tty->name);
 | 
								  current->signal->tty->name);
 | 
				
			||||||
| 
						 | 
					@ -293,20 +293,23 @@ void disassociate_ctty(int on_exit)
 | 
				
			||||||
	spin_lock_irq(¤t->sighand->siglock);
 | 
						spin_lock_irq(¤t->sighand->siglock);
 | 
				
			||||||
	put_pid(current->signal->tty_old_pgrp);
 | 
						put_pid(current->signal->tty_old_pgrp);
 | 
				
			||||||
	current->signal->tty_old_pgrp = NULL;
 | 
						current->signal->tty_old_pgrp = NULL;
 | 
				
			||||||
 | 
					 | 
				
			||||||
	tty = tty_kref_get(current->signal->tty);
 | 
						tty = tty_kref_get(current->signal->tty);
 | 
				
			||||||
 | 
						spin_unlock_irq(¤t->sighand->siglock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (tty) {
 | 
						if (tty) {
 | 
				
			||||||
		unsigned long flags;
 | 
							unsigned long flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tty_lock(tty);
 | 
				
			||||||
		spin_lock_irqsave(&tty->ctrl_lock, flags);
 | 
							spin_lock_irqsave(&tty->ctrl_lock, flags);
 | 
				
			||||||
		put_pid(tty->session);
 | 
							put_pid(tty->session);
 | 
				
			||||||
		put_pid(tty->pgrp);
 | 
							put_pid(tty->pgrp);
 | 
				
			||||||
		tty->session = NULL;
 | 
							tty->session = NULL;
 | 
				
			||||||
		tty->pgrp = NULL;
 | 
							tty->pgrp = NULL;
 | 
				
			||||||
		spin_unlock_irqrestore(&tty->ctrl_lock, flags);
 | 
							spin_unlock_irqrestore(&tty->ctrl_lock, flags);
 | 
				
			||||||
 | 
							tty_unlock(tty);
 | 
				
			||||||
		tty_kref_put(tty);
 | 
							tty_kref_put(tty);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	spin_unlock_irq(¤t->sighand->siglock);
 | 
					 | 
				
			||||||
	/* Now clear signal->tty under the lock */
 | 
						/* Now clear signal->tty under the lock */
 | 
				
			||||||
	read_lock(&tasklist_lock);
 | 
						read_lock(&tasklist_lock);
 | 
				
			||||||
	session_clear_tty(task_session(current));
 | 
						session_clear_tty(task_session(current));
 | 
				
			||||||
| 
						 | 
					@ -477,14 +480,19 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t
 | 
				
			||||||
		return -ENOTTY;
 | 
							return -ENOTTY;
 | 
				
			||||||
	if (retval)
 | 
						if (retval)
 | 
				
			||||||
		return retval;
 | 
							return retval;
 | 
				
			||||||
	if (!current->signal->tty ||
 | 
					
 | 
				
			||||||
	    (current->signal->tty != real_tty) ||
 | 
					 | 
				
			||||||
	    (real_tty->session != task_session(current)))
 | 
					 | 
				
			||||||
		return -ENOTTY;
 | 
					 | 
				
			||||||
	if (get_user(pgrp_nr, p))
 | 
						if (get_user(pgrp_nr, p))
 | 
				
			||||||
		return -EFAULT;
 | 
							return -EFAULT;
 | 
				
			||||||
	if (pgrp_nr < 0)
 | 
						if (pgrp_nr < 0)
 | 
				
			||||||
		return -EINVAL;
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irq(&real_tty->ctrl_lock);
 | 
				
			||||||
 | 
						if (!current->signal->tty ||
 | 
				
			||||||
 | 
						    (current->signal->tty != real_tty) ||
 | 
				
			||||||
 | 
						    (real_tty->session != task_session(current))) {
 | 
				
			||||||
 | 
							retval = -ENOTTY;
 | 
				
			||||||
 | 
							goto out_unlock_ctrl;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	rcu_read_lock();
 | 
						rcu_read_lock();
 | 
				
			||||||
	pgrp = find_vpid(pgrp_nr);
 | 
						pgrp = find_vpid(pgrp_nr);
 | 
				
			||||||
	retval = -ESRCH;
 | 
						retval = -ESRCH;
 | 
				
			||||||
| 
						 | 
					@ -494,12 +502,12 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t
 | 
				
			||||||
	if (session_of_pgrp(pgrp) != task_session(current))
 | 
						if (session_of_pgrp(pgrp) != task_session(current))
 | 
				
			||||||
		goto out_unlock;
 | 
							goto out_unlock;
 | 
				
			||||||
	retval = 0;
 | 
						retval = 0;
 | 
				
			||||||
	spin_lock_irq(&real_tty->ctrl_lock);
 | 
					 | 
				
			||||||
	put_pid(real_tty->pgrp);
 | 
						put_pid(real_tty->pgrp);
 | 
				
			||||||
	real_tty->pgrp = get_pid(pgrp);
 | 
						real_tty->pgrp = get_pid(pgrp);
 | 
				
			||||||
	spin_unlock_irq(&real_tty->ctrl_lock);
 | 
					 | 
				
			||||||
out_unlock:
 | 
					out_unlock:
 | 
				
			||||||
	rcu_read_unlock();
 | 
						rcu_read_unlock();
 | 
				
			||||||
 | 
					out_unlock_ctrl:
 | 
				
			||||||
 | 
						spin_unlock_irq(&real_tty->ctrl_lock);
 | 
				
			||||||
	return retval;
 | 
						return retval;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -511,20 +519,30 @@ 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
 | 
					 *	Obtain the session id of the tty. If there is no session
 | 
				
			||||||
 *	return an error.
 | 
					 *	return an error.
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 *	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)
 | 
					static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						pid_t sid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * (tty == real_tty) is a cheap way of
 | 
						 * (tty == real_tty) is a cheap way of
 | 
				
			||||||
	 * testing if the tty is NOT a master pty.
 | 
						 * testing if the tty is NOT a master pty.
 | 
				
			||||||
	*/
 | 
						*/
 | 
				
			||||||
	if (tty == real_tty && current->signal->tty != real_tty)
 | 
						if (tty == real_tty && current->signal->tty != real_tty)
 | 
				
			||||||
		return -ENOTTY;
 | 
							return -ENOTTY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irqsave(&real_tty->ctrl_lock, flags);
 | 
				
			||||||
	if (!real_tty->session)
 | 
						if (!real_tty->session)
 | 
				
			||||||
		return -ENOTTY;
 | 
							goto err;
 | 
				
			||||||
	return put_user(pid_vnr(real_tty->session), p);
 | 
						sid = pid_vnr(real_tty->session);
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&real_tty->ctrl_lock, flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return put_user(sid, p);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err:
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&real_tty->ctrl_lock, flags);
 | 
				
			||||||
 | 
						return -ENOTTY;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -306,6 +306,10 @@ struct tty_struct {
 | 
				
			||||||
	struct termiox *termiox;	/* May be NULL for unsupported */
 | 
						struct termiox *termiox;	/* May be NULL for unsupported */
 | 
				
			||||||
	char name[64];
 | 
						char name[64];
 | 
				
			||||||
	struct pid *pgrp;		/* Protected by ctrl lock */
 | 
						struct pid *pgrp;		/* Protected by ctrl lock */
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Writes protected by both ctrl lock and legacy mutex, readers must use
 | 
				
			||||||
 | 
						 * at least one of them.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	struct pid *session;
 | 
						struct pid *session;
 | 
				
			||||||
	unsigned long flags;
 | 
						unsigned long flags;
 | 
				
			||||||
	int count;
 | 
						int count;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue