diff options
Diffstat (limited to 'drivers/tty/tty_jobctrl.c')
| -rw-r--r-- | drivers/tty/tty_jobctrl.c | 173 |
1 files changed, 106 insertions, 67 deletions
diff --git a/drivers/tty/tty_jobctrl.c b/drivers/tty/tty_jobctrl.c index e7032309ee87..ef8741c3e662 100644 --- a/drivers/tty/tty_jobctrl.c +++ b/drivers/tty/tty_jobctrl.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 1991, 1992 Linus Torvalds */ @@ -10,6 +11,7 @@ #include <linux/tty.h> #include <linux/fcntl.h> #include <linux/uaccess.h> +#include "tty.h" static int is_ignored(int sig) { @@ -18,14 +20,15 @@ static int is_ignored(int sig) } /** - * tty_check_change - check for POSIX terminal changes + * __tty_check_change - check for POSIX terminal changes * @tty: tty to check + * @sig: signal to send * * If we try to write to, or set the state of, a terminal and we're * not in the foreground, send a SIGTTOU. If the signal is blocked or * ignored, go ahead and perform the operation. (POSIX 7.2) * - * Locking: ctrl_lock + * Locking: ctrl.lock */ int __tty_check_change(struct tty_struct *tty, int sig) { @@ -39,11 +42,11 @@ int __tty_check_change(struct tty_struct *tty, int sig) rcu_read_lock(); pgrp = task_pgrp(current); - spin_lock_irqsave(&tty->ctrl_lock, flags); - tty_pgrp = tty->pgrp; - spin_unlock_irqrestore(&tty->ctrl_lock, flags); + spin_lock_irqsave(&tty->ctrl.lock, flags); + tty_pgrp = tty->ctrl.pgrp; + spin_unlock_irqrestore(&tty->ctrl.lock, flags); - if (tty_pgrp && pgrp != tty->pgrp) { + if (tty_pgrp && pgrp != tty_pgrp) { if (is_ignored(sig)) { if (sig == SIGTTIN) ret = -EIO; @@ -73,6 +76,7 @@ void proc_clear_tty(struct task_struct *p) { unsigned long flags; struct tty_struct *tty; + spin_lock_irqsave(&p->sighand->siglock, flags); tty = p->signal->tty; p->signal->tty = NULL; @@ -81,7 +85,8 @@ void proc_clear_tty(struct task_struct *p) } /** - * proc_set_tty - set the controlling terminal + * __proc_set_tty - set the controlling terminal + * @tty: tty structure * * Only callable by the session leader and only if it does not already have * a controlling terminal. @@ -94,16 +99,16 @@ static void __proc_set_tty(struct tty_struct *tty) { unsigned long flags; - spin_lock_irqsave(&tty->ctrl_lock, flags); + spin_lock_irqsave(&tty->ctrl.lock, flags); /* * The session and fg pgrp references will be non-NULL if * tiocsctty() is stealing the controlling tty */ - put_pid(tty->session); - put_pid(tty->pgrp); - tty->pgrp = get_pid(task_pgrp(current)); - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - tty->session = get_pid(task_session(current)); + put_pid(tty->ctrl.session); + put_pid(tty->ctrl.pgrp); + tty->ctrl.pgrp = get_pid(task_pgrp(current)); + tty->ctrl.session = get_pid(task_session(current)); + spin_unlock_irqrestore(&tty->ctrl.lock, flags); if (current->signal->tty) { tty_debug(tty, "current tty %s not NULL!!\n", current->signal->tty->name); @@ -130,7 +135,7 @@ void tty_open_proc_set_tty(struct file *filp, struct tty_struct *tty) spin_lock_irq(¤t->sighand->siglock); if (current->signal->leader && !current->signal->tty && - tty->session == NULL) { + tty->ctrl.session == NULL) { /* * Don't let a process that only has write access to the tty * obtain the privileges associated with having a tty as @@ -170,6 +175,7 @@ EXPORT_SYMBOL_GPL(get_current_tty); void session_clear_tty(struct pid *session) { struct task_struct *p; + do_each_pid_task(session, PIDTYPE_SID, p) { proc_clear_tty(p); } while_each_pid_task(session, PIDTYPE_SID, p); @@ -177,8 +183,8 @@ void session_clear_tty(struct pid *session) /** * tty_signal_session_leader - sends SIGHUP to session leader - * @tty controlling tty - * @exit_session if non-zero, signal all foreground group processes + * @tty: controlling tty + * @exit_session: if non-zero, signal all foreground group processes * * Send SIGHUP and SIGCONT to the session leader and its process group. * Optionally, signal all processes in the foreground process group. @@ -194,29 +200,32 @@ int tty_signal_session_leader(struct tty_struct *tty, int exit_session) struct pid *tty_pgrp = NULL; read_lock(&tasklist_lock); - if (tty->session) { - do_each_pid_task(tty->session, PIDTYPE_SID, p) { + if (tty->ctrl.session) { + do_each_pid_task(tty->ctrl.session, PIDTYPE_SID, p) { spin_lock_irq(&p->sighand->siglock); if (p->signal->tty == tty) { p->signal->tty = NULL; - /* We defer the dereferences outside fo - the tasklist lock */ + /* + * We defer the dereferences outside of + * the tasklist lock. + */ refs++; } 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); + send_signal_locked(SIGHUP, SEND_SIG_PRIV, p, PIDTYPE_TGID); + send_signal_locked(SIGCONT, SEND_SIG_PRIV, p, PIDTYPE_TGID); put_pid(p->signal->tty_old_pgrp); /* A noop */ - spin_lock(&tty->ctrl_lock); - tty_pgrp = get_pid(tty->pgrp); - if (tty->pgrp) - p->signal->tty_old_pgrp = get_pid(tty->pgrp); - spin_unlock(&tty->ctrl_lock); + spin_lock(&tty->ctrl.lock); + tty_pgrp = get_pid(tty->ctrl.pgrp); + if (tty->ctrl.pgrp) + p->signal->tty_old_pgrp = + get_pid(tty->ctrl.pgrp); + spin_unlock(&tty->ctrl.lock); spin_unlock_irq(&p->sighand->siglock); - } while_each_pid_task(tty->session, PIDTYPE_SID, p); + } while_each_pid_task(tty->ctrl.session, PIDTYPE_SID, p); } read_unlock(&tasklist_lock); @@ -237,10 +246,10 @@ int tty_signal_session_leader(struct tty_struct *tty, int exit_session) * it wants to disassociate itself from its controlling tty. * * It performs the following functions: - * (1) Sends a SIGHUP and SIGCONT to the foreground process group - * (2) Clears the tty from being controlling the session - * (3) Clears the controlling tty for all processes in the - * session group. + * (1) Sends a SIGHUP and SIGCONT to the foreground process group + * (2) Clears the tty from being controlling the session + * (3) Clears the controlling tty for all processes in the + * session group. * * The argument on_exit is set to 1 if called when a process is * exiting; it is 0 if called by the ioctl TIOCNOTTY. @@ -266,6 +275,7 @@ void disassociate_ctty(int on_exit) tty_vhangup_session(tty); } else { struct pid *tty_pgrp = tty_get_pgrp(tty); + if (tty_pgrp) { kill_pgrp(tty_pgrp, SIGHUP, on_exit); if (!on_exit) @@ -277,6 +287,7 @@ void disassociate_ctty(int on_exit) } else if (on_exit) { struct pid *old_pgrp; + spin_lock_irq(¤t->sighand->siglock); old_pgrp = current->signal->tty_old_pgrp; current->signal->tty_old_pgrp = NULL; @@ -289,39 +300,50 @@ void disassociate_ctty(int on_exit) return; } - spin_lock_irq(¤t->sighand->siglock); - put_pid(current->signal->tty_old_pgrp); - current->signal->tty_old_pgrp = NULL; - - tty = tty_kref_get(current->signal->tty); + tty = get_current_tty(); if (tty) { unsigned long flags; - spin_lock_irqsave(&tty->ctrl_lock, flags); - put_pid(tty->session); - put_pid(tty->pgrp); - tty->session = NULL; - tty->pgrp = NULL; - spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + tty_lock(tty); + spin_lock_irqsave(&tty->ctrl.lock, flags); + put_pid(tty->ctrl.session); + put_pid(tty->ctrl.pgrp); + tty->ctrl.session = NULL; + tty->ctrl.pgrp = NULL; + spin_unlock_irqrestore(&tty->ctrl.lock, flags); + tty_unlock(tty); tty_kref_put(tty); } + /* If tty->ctrl.pgrp is not NULL, it may be assigned to + * current->signal->tty_old_pgrp in a race condition, and + * cause pid memleak. Release current->signal->tty_old_pgrp + * after tty->ctrl.pgrp set to NULL. + */ + spin_lock_irq(¤t->sighand->siglock); + put_pid(current->signal->tty_old_pgrp); + current->signal->tty_old_pgrp = NULL; spin_unlock_irq(¤t->sighand->siglock); + /* Now clear signal->tty under the lock */ read_lock(&tasklist_lock); session_clear_tty(task_session(current)); read_unlock(&tasklist_lock); } -/** +/* * * no_tty - Ensure the current process does not have a controlling tty */ void no_tty(void) { - /* FIXME: Review locking here. The tty_lock never covered any race - between a new association and proc_clear_tty but possible we need - to protect against this anyway */ + /* + * FIXME: Review locking here. The tty_lock never covered any race + * between a new association and proc_clear_tty but possibly we need + * to protect against this anyway. + */ struct task_struct *tsk = current; + disassociate_ctty(0); proc_clear_tty(tsk); } @@ -329,6 +351,7 @@ void no_tty(void) /** * tiocsctty - set controlling tty * @tty: tty structure + * @file: file structure used to check permissions * @arg: user argument * * This ioctl is used to manage job control. It permits a session @@ -346,7 +369,8 @@ static int tiocsctty(struct tty_struct *tty, struct file *file, int arg) tty_lock(tty); read_lock(&tasklist_lock); - if (current->signal->leader && (task_session(current) == tty->session)) + if (current->signal->leader && + task_session(current) == tty->ctrl.session) goto unlock; /* @@ -358,7 +382,7 @@ static int tiocsctty(struct tty_struct *tty, struct file *file, int arg) goto unlock; } - if (tty->session) { + if (tty->ctrl.session) { /* * This tty is already the controlling * tty for another session group! @@ -367,7 +391,7 @@ static int tiocsctty(struct tty_struct *tty, struct file *file, int arg) /* * Steal it away */ - session_clear_tty(tty->session); + session_clear_tty(tty->ctrl.session); } else { ret = -EPERM; goto unlock; @@ -399,9 +423,9 @@ struct pid *tty_get_pgrp(struct tty_struct *tty) unsigned long flags; struct pid *pgrp; - spin_lock_irqsave(&tty->ctrl_lock, flags); - pgrp = get_pid(tty->pgrp); - spin_unlock_irqrestore(&tty->ctrl_lock, flags); + spin_lock_irqsave(&tty->ctrl.lock, flags); + pgrp = get_pid(tty->ctrl.pgrp); + spin_unlock_irqrestore(&tty->ctrl.lock, flags); return pgrp; } @@ -476,14 +500,19 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t return -ENOTTY; if (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)) return -EFAULT; if (pgrp_nr < 0) return -EINVAL; + + spin_lock_irq(&real_tty->ctrl.lock); + if (!current->signal->tty || + (current->signal->tty != real_tty) || + (real_tty->ctrl.session != task_session(current))) { + retval = -ENOTTY; + goto out_unlock_ctrl; + } rcu_read_lock(); pgrp = find_vpid(pgrp_nr); retval = -ESRCH; @@ -493,12 +522,12 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t if (session_of_pgrp(pgrp) != task_session(current)) goto out_unlock; retval = 0; - spin_lock_irq(&tty->ctrl_lock); - put_pid(real_tty->pgrp); - real_tty->pgrp = get_pid(pgrp); - spin_unlock_irq(&tty->ctrl_lock); + put_pid(real_tty->ctrl.pgrp); + real_tty->ctrl.pgrp = get_pid(pgrp); out_unlock: rcu_read_unlock(); +out_unlock_ctrl: + spin_unlock_irq(&real_tty->ctrl.lock); return retval; } @@ -510,20 +539,30 @@ out_unlock: * * Obtain the session id of the tty. If there is no session * 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) { + unsigned long flags; + pid_t sid; + /* * (tty == real_tty) is a cheap way of * testing if the tty is NOT a master pty. - */ + */ if (tty == real_tty && current->signal->tty != real_tty) return -ENOTTY; - if (!real_tty->session) - return -ENOTTY; - return put_user(pid_vnr(real_tty->session), p); + + spin_lock_irqsave(&real_tty->ctrl.lock, flags); + if (!real_tty->ctrl.session) + goto err; + sid = pid_vnr(real_tty->ctrl.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; } /* |
