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