Blob Blame History Raw
From 3b7e363b1d5f3ca329a7056b509a54d081923cd1 Mon Sep 17 00:00:00 2001
From: Yannick Cote <ycote@redhat.com>
Date: Thu, 13 Oct 2022 10:36:30 -0400
Subject: [KPATCH 9.0] kpatch fixes for CVE-2022-2585

Kernels:
5.14.0-70.13.1.el9_0
5.14.0-70.17.1.el9_0
5.14.0-70.22.1.el9_0
5.14.0-70.26.1.el9_0

Changes since last build:
arches: x86_64 ppc64le
exec.o: changed function: begin_new_exec
exit.o: changed function: do_exit
posix-timers.o: new function: kpatch_cve_2022_2585_exit_itimers
---------------------------

Modifications:
- removed changes to .h files, for kpatch locality
- redefine exit_itimers instead of changing it
  (kpatch_cve_2022_2585_exit_itimers())
- use kpatch defined exit_itimers() everywhere it's called in the kernel

commit 84013659b0e8a6965e79f1e7d6108aa2be2230ab
Author: Wander Lairson Costa <wander@redhat.com>
Date:   Thu Aug 25 09:27:13 2022 -0300

    fix race between exit_itimers() and /proc/pid/timers

    Bugzilla: https://bugzilla.redhat.com/2116967
    CVE: CVE-2022-2585
    Y-Commit: a532f4903a5d24d1e5e692628458d3a10184f615

    O-Bugzilla: https://bugzilla.redhat.com/2116968

    commit d5b36a4dbd06c5e8e36ca8ccc552f679069e2946
    Author: Oleg Nesterov <oleg@redhat.com>
    Date:   Mon Jul 11 18:16:25 2022 +0200

        fix race between exit_itimers() and /proc/pid/timers

        As Chris explains, the comment above exit_itimers() is not correct,
        we can race with proc_timers_seq_ops. Change exit_itimers() to clear
        signal->posix_timers with ->siglock held.

        Cc: <stable@vger.kernel.org>
        Reported-by: chris@accessvector.net
        Signed-off-by: Oleg Nesterov <oleg@redhat.com>
        Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

    Signed-off-by: Wander Lairson Costa <wander@redhat.com>
    Signed-off-by: Herton R. Krzesinski <herton@redhat.com>

commit d91003cca875f4b0ca290db6e686ee960125da3a
Author: Wander Lairson Costa <wander@redhat.com>
Date:   Thu Aug 25 09:27:22 2022 -0300

    posix-cpu-timers: Cleanup CPU timers before freeing them during exec

    Bugzilla: https://bugzilla.redhat.com/2116967
    CVE: CVE-2022-2585
    Y-Commit: b89dd8173ef086055a00bd606813e370efe7f0d7

    O-Bugzilla: https://bugzilla.redhat.com/2116968

    commit e362359ace6f87c201531872486ff295df306d13
    Author: Thadeu Lima de Souza Cascardo <cascardo@canonical.com>
    Date:   Tue Aug 9 14:07:51 2022 -0300

        posix-cpu-timers: Cleanup CPU timers before freeing them during exec

        Commit 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a
        task") started looking up tasks by PID when deleting a CPU timer.

        When a non-leader thread calls execve, it will switch PIDs with the leader
        process. Then, as it calls exit_itimers, posix_cpu_timer_del cannot find
        the task because the timer still points out to the old PID.

        That means that armed timers won't be disarmed, that is, they won't be
        removed from the timerqueue_list. exit_itimers will still release their
        memory, and when that list is later processed, it leads to a
        use-after-free.

        Clean up the timers from the de-threaded task before freeing them. This
        prevents a reported use-after-free.

        Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task")
        Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@canonical.com>
        Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
        Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
        Cc: <stable@vger.kernel.org>
        Link: https://lore.kernel.org/r/20220809170751.164716-1-cascardo@canonical.com

    Signed-off-by: Wander Lairson Costa <wander@redhat.com>
    Signed-off-by: Herton R. Krzesinski <herton@redhat.com>

Signed-off-by: Yannick Cote <ycote@redhat.com>
---
 fs/exec.c                  |  6 +++++-
 kernel/exit.c              |  4 +++-
 kernel/time/posix-timers.c | 26 +++++++++++++++++++++++++-
 3 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index 2bb8dd6a4e2a..decf9ccef49d 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1229,6 +1229,7 @@ void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec)
 	perf_event_comm(tsk, exec);
 }
 
+void kpatch_cve_2022_2585_exit_itimers(struct task_struct *tsk);
 /*
  * Calling this is the point of no return. None of the failures will be
  * seen by userspace since either the process is already taking a fatal
@@ -1292,7 +1293,10 @@ int begin_new_exec(struct linux_binprm * bprm)
 	bprm->mm = NULL;
 
 #ifdef CONFIG_POSIX_TIMERS
-	exit_itimers(me->signal);
+	spin_lock_irq(&me->sighand->siglock);
+	posix_cpu_timers_exit(me);
+	spin_unlock_irq(&me->sighand->siglock);
+	kpatch_cve_2022_2585_exit_itimers(me);
 	flush_itimer_signals();
 #endif
 
diff --git a/kernel/exit.c b/kernel/exit.c
index 1731f60de259..d1e618505b7d 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -725,6 +725,7 @@ static void check_stack_usage(void)
 static inline void check_stack_usage(void) {}
 #endif
 
+void kpatch_cve_2022_2585_exit_itimers(struct task_struct *tsk);
 void __noreturn do_exit(long code)
 {
 	struct task_struct *tsk = current;
@@ -797,7 +798,8 @@ void __noreturn do_exit(long code)
 
 #ifdef CONFIG_POSIX_TIMERS
 		hrtimer_cancel(&tsk->signal->real_timer);
-		exit_itimers(tsk->signal);
+		kpatch_cve_2022_2585_exit_itimers(tsk);
+
 #endif
 		if (tsk->mm)
 			setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm);
diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c
index 1cd10b102c51..8a1f61232282 100644
--- a/kernel/time/posix-timers.c
+++ b/kernel/time/posix-timers.c
@@ -1035,7 +1035,7 @@ SYSCALL_DEFINE1(timer_delete, timer_t, timer_id)
 /*
  * return timer owned by the process, used by exit_itimers
  */
-static void itimer_delete(struct k_itimer *timer)
+static __always_inline void itimer_delete(struct k_itimer *timer)
 {
 retry_delete:
 	spin_lock_irq(&timer->it_lock);
@@ -1050,6 +1050,30 @@ static void itimer_delete(struct k_itimer *timer)
 	release_posix_timer(timer, IT_ID_SET);
 }
 
+/*
+ * CVE-2022-2585 - kpatch exit_itimers() redefinition
+ * This is called by do_exit or de_thread, only when nobody else can
+ * modify the signal->posix_timers list. Yet we need sighand->siglock
+ * to prevent the race with /proc/pid/timers.
+ */
+void kpatch_cve_2022_2585_exit_itimers(struct task_struct *tsk)
+{
+	struct list_head timers;
+	struct k_itimer *tmr;
+
+	if (list_empty(&tsk->signal->posix_timers))
+		return;
+
+	spin_lock_irq(&tsk->sighand->siglock);
+	list_replace_init(&tsk->signal->posix_timers, &timers);
+	spin_unlock_irq(&tsk->sighand->siglock);
+
+	while (!list_empty(&timers)) {
+		tmr = list_first_entry(&timers, struct k_itimer, list);
+		itimer_delete(tmr);
+	}
+}
+
 /*
  * This is called by do_exit or de_thread, only when there are no more
  * references to the shared signal_struct.
-- 
2.37.3