[PATCH v2 2/3] tty: Serialise racing tiocspgrp() callers

From: Earl Chew
Date: Thu Sep 28 2023 - 09:07:30 EST


Only lock tty->ctrl.lock once when processing requests
in tiocspgrp() to serialise concurrent changes. Since
the introduction of tty->ctrl.lock in commit 47f86834bbd4
("redo locking of tty->pgrp"), tiocspgrp() has acquired the
lock twice: first to check the process group, and next to
change the process group. In the rare case of multiple
callers, all can pass the process group check before each
taking turns to update the process group.

Reported-by: kernel test robot <lkp@xxxxxxxxx>
Closes: https://lore.kernel.org/oe-kbuild-all/202309012304.un7EAdgl-lkp@xxxxxxxxx/
Closes: https://lore.kernel.org/r/202309011252.ItlD27Mg-lkp@xxxxxxxxx/

Signed-off-by: Earl Chew <earl.chew@xxxxxxxx>
---
drivers/tty/tty_jobctrl.c | 43 +++++++++++++++++++++++++++++----------
1 file changed, 32 insertions(+), 11 deletions(-)

diff --git a/drivers/tty/tty_jobctrl.c b/drivers/tty/tty_jobctrl.c
index aba721a3c..da028aadf 100644
--- a/drivers/tty/tty_jobctrl.c
+++ b/drivers/tty/tty_jobctrl.c
@@ -20,9 +20,10 @@ static int is_ignored(int sig)
}

/**
- * __tty_check_change - check for POSIX terminal changes
+ * __tty_check_change_locked - check for POSIX terminal changes
* @tty: tty to check
* @sig: signal to send
+ * @ctrl_lock: &ctrl.lock if already acquired
*
* 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
@@ -30,19 +31,24 @@ static int is_ignored(int sig)
*
* Locking: ctrl.lock
*/
-int __tty_check_change(struct tty_struct *tty, int sig)
+static int __tty_check_change_locked(struct tty_struct *tty, int sig,
+ spinlock_t *ctrl_lock)
{
unsigned long flags;
struct pid *pgrp, *tty_pgrp;
int ret = 0;

+ BUG_ON(ctrl_lock && (
+ ctrl_lock != &tty->ctrl.lock || !spin_is_locked(ctrl_lock)));
+
if (current->signal->tty != tty)
return 0;

rcu_read_lock();
pgrp = task_pgrp(current);

- spin_lock_irqsave(&tty->ctrl.lock, flags);
+ if (!ctrl_lock)
+ spin_lock_irqsave(&tty->ctrl.lock, flags);
tty_pgrp = tty->ctrl.pgrp;

if (tty_pgrp && pgrp != tty_pgrp) {
@@ -57,7 +63,8 @@ int __tty_check_change(struct tty_struct *tty, int sig)
ret = -ERESTARTSYS;
}
}
- spin_unlock_irqrestore(&tty->ctrl.lock, flags);
+ if (!ctrl_lock)
+ spin_unlock_irqrestore(&tty->ctrl.lock, flags);
rcu_read_unlock();

if (!tty_pgrp)
@@ -66,9 +73,19 @@ int __tty_check_change(struct tty_struct *tty, int sig)
return ret;
}

+static int tty_check_change_locked(struct tty_struct *tty, spinlock_t *locked)
+{
+ return __tty_check_change_locked(tty, SIGTTOU, locked);
+}
+
+int __tty_check_change(struct tty_struct *tty, int sig)
+{
+ return __tty_check_change_locked(tty, sig, 0);
+}
+
int tty_check_change(struct tty_struct *tty)
{
- return __tty_check_change(tty, SIGTTOU);
+ return tty_check_change_locked(tty, 0);
}
EXPORT_SYMBOL(tty_check_change);

@@ -489,12 +506,7 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t
{
struct pid *pgrp;
pid_t pgrp_nr;
- int retval = tty_check_change(real_tty);
-
- if (retval == -EIO)
- return -ENOTTY;
- if (retval)
- return retval;
+ int retval;

if (get_user(pgrp_nr, p))
return -EFAULT;
@@ -502,6 +514,15 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t
return -EINVAL;

spin_lock_irq(&real_tty->ctrl.lock);
+ retval = tty_check_change_locked(real_tty, &real_tty->ctrl.lock);
+
+ if (retval == -EIO) {
+ retval = -ENOTTY;
+ goto out_unlock_ctrl;
+ }
+ if (retval)
+ goto out_unlock_ctrl;
+
if (!current->signal->tty ||
(current->signal->tty != real_tty) ||
(real_tty->ctrl.session != task_session(current))) {
--
2.39.1