summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/CVE-2020-29660/0001-tty-Fix-session-locking.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/CVE-2020-29660/0001-tty-Fix-session-locking.patch')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/CVE-2020-29660/0001-tty-Fix-session-locking.patch204
1 files changed, 204 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/CVE-2020-29660/0001-tty-Fix-session-locking.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/CVE-2020-29660/0001-tty-Fix-session-locking.patch
new file mode 100644
index 000000000..a00ce2a95
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/CVE-2020-29660/0001-tty-Fix-session-locking.patch
@@ -0,0 +1,204 @@
+From 35ee9ac513280f46eeb1196bac82ed5320380412 Mon Sep 17 00:00:00 2001
+From: Jann Horn <jannh@google.com>
+Date: Thu, 3 Dec 2020 02:25:05 +0100
+Subject: [PATCH] tty: Fix ->session locking
+
+commit c8bcd9c5be24fb9e6132e97da5a35e55a83e36b9 upstream.
+
+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>
+---
+ drivers/tty/tty_io.c | 7 ++++++-
+ drivers/tty/tty_jobctrl.c | 44 +++++++++++++++++++++++++++------------
+ include/linux/tty.h | 4 ++++
+ 3 files changed, 41 insertions(+), 14 deletions(-)
+
+diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
+index 36c1c59cc72a..642765bf1023 100644
+--- a/drivers/tty/tty_io.c
++++ b/drivers/tty/tty_io.c
+@@ -2894,10 +2894,14 @@ void __do_SAK(struct tty_struct *tty)
+ struct task_struct *g, *p;
+ struct pid *session;
+ int i;
++ unsigned long flags;
+
+ if (!tty)
+ 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);
+
+@@ -2929,6 +2933,7 @@ void __do_SAK(struct tty_struct *tty)
+ task_unlock(p);
+ } while_each_thread(g, p);
+ read_unlock(&tasklist_lock);
++ put_pid(session);
+ #endif
+ }
+
+diff --git a/drivers/tty/tty_jobctrl.c b/drivers/tty/tty_jobctrl.c
+index af508957ff05..813be2c05262 100644
+--- a/drivers/tty/tty_jobctrl.c
++++ b/drivers/tty/tty_jobctrl.c
+@@ -103,8 +103,8 @@ static void __proc_set_tty(struct tty_struct *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));
++ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ if (current->signal->tty) {
+ tty_debug(tty, "current tty %s not NULL!!\n",
+ current->signal->tty->name);
+@@ -293,20 +293,23 @@ void disassociate_ctty(int on_exit)
+ spin_lock_irq(&current->sighand->siglock);
+ put_pid(current->signal->tty_old_pgrp);
+ current->signal->tty_old_pgrp = NULL;
+-
+ tty = tty_kref_get(current->signal->tty);
++ spin_unlock_irq(&current->sighand->siglock);
++
+ if (tty) {
+ unsigned long flags;
++
++ tty_lock(tty);
+ 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_unlock(tty);
+ tty_kref_put(tty);
+ }
+
+- spin_unlock_irq(&current->sighand->siglock);
+ /* Now clear signal->tty under the lock */
+ read_lock(&tasklist_lock);
+ 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;
+ 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->session != task_session(current))) {
++ retval = -ENOTTY;
++ goto out_unlock_ctrl;
++ }
+ rcu_read_lock();
+ pgrp = find_vpid(pgrp_nr);
+ 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))
+ goto out_unlock;
+ retval = 0;
+- spin_lock_irq(&real_tty->ctrl_lock);
+ put_pid(real_tty->pgrp);
+ real_tty->pgrp = get_pid(pgrp);
+- spin_unlock_irq(&real_tty->ctrl_lock);
+ out_unlock:
+ rcu_read_unlock();
++out_unlock_ctrl:
++ spin_unlock_irq(&real_tty->ctrl_lock);
+ 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
+ * 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;
++
++ spin_lock_irqsave(&real_tty->ctrl_lock, flags);
+ if (!real_tty->session)
+- return -ENOTTY;
+- return put_user(pid_vnr(real_tty->session), p);
++ goto err;
++ 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;
+ }
+
+ /*
+diff --git a/include/linux/tty.h b/include/linux/tty.h
+index a99e9b8e4e31..eb33d948788c 100644
+--- a/include/linux/tty.h
++++ b/include/linux/tty.h
+@@ -306,6 +306,10 @@ struct tty_struct {
+ struct termiox *termiox; /* May be NULL for unsupported */
+ char name[64];
+ 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;
+ unsigned long flags;
+ int count;
+--
+2.17.1
+