From: Greg Kroah-Hartman Date: Thu, 30 Jul 2026 11:19:26 +0000 (+0200) Subject: drop patches already applied X-Git-Tag: v6.6.148~39 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=27e52355af0178120e2a087562f32b9cd7b4b9f0;p=thirdparty%2Fkernel%2Fstable-queue.git drop patches already applied --- diff --git a/queue-5.10/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-5.10/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch deleted file mode 100644 index 33dbf29540..0000000000 --- a/queue-5.10/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +++ /dev/null @@ -1,458 +0,0 @@ -From aad70180e7c856978814bb3ca066df392cbfef15 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Mon, 27 Jul 2026 11:13:08 +0200 -Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race - -From: Thomas Gleixner - -commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. - -Wongi and Jungwoo decoded and reported a non-leader exec() related race -which can result in an UAF: - - sys_timer_delete() exec() - posix_cpu_timer_del() - // Observes old leader - p = pid_task(pid, pid_type); de_thread() - switch_leader(); - release_task(old_leader) - __exit_signal(old_leader) - sighand = lock(old_leader, sighand); - posix_cpu_timers*_exit(); - sighand = lock_task_sighand(p) unhash_task(old_leader); - sh = lock(p, sighand) old_leader->sighand = NULL; - unlock(sighand); - (p->sighand == NULL) - unlock(sh) - return NULL; - - // Returns without action - if(!sighand) - return 0; - free_posix_timer(); - -This is "harmless" unless the deleted timer was armed and enqueued in -p->signal because on exec() a TGID targeted timer is inherited. - -As sys_timer_delete() freed the underlying posix timer object -run_posix_cpu_timers() or any timerqueue related add/delete operations on -other timers will access the freed object's timerqueue node, which results -in an UAF. - -There is a similar problem vs. posix_cpu_timer_set(). For regular posix -timers it just transiently returns -ESRCH to user space, but for the use -case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is -allocated on the stack. - -Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops -to expire. - -While debating solutions Frederic pointed out another problem: - - posix_cpu_timer_del(tmr) - __exit_signal(p) - posix_cpu_timers*_exit(p); - unhash_task(p); - p->sighand = NULL; - sh = lock_task_sighand(p) - sighand = p->sighand; - if (!sighand) - return NULL; - lock(sighand); - - if (!sh) - WARN_ON_ONCE(timer_queued(tmr)); - -On weakly ordered architectures it is not guaranteed that -posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() -when p->sighand is observed as NULL, which means the WARN() can be a false -positive. - -Solve these issues by: - - 1) Changing the store in __exit_signal() to smp_store_release(). - - 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path - of lock_task_sighand(). - - 3) Creating a helper function for looking up the task and locking sighand - which does not return when sighand == NULL. Instead it retries the - task lookup and only if that fails it gives up. - - 4) Using that helper in the three affected functions. - -#1/#2 ensures that the reader side which observes sighand == NULL also -observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() -and the ones in unhash_task(). - -#3 ensures that the above described non-leader exec() situation is handled -gracefully. When the task lookup returns the old leader, but sighand == -NULL then it retries. In the non-leader exec() case the subsequent task -lookup will observe the new leader due to #1/#2. In normal exit() scenarios -the subsequent lookup fails. - -When the task lookup fails, the function also checks whether the timer is -still enqueued and issues a warning if that's the case. Unfortunately there -is nothing which can be done about it, but as the task is already not -longer visible the timer should not be accessed anymore. This check also -requires memory ordering, which is not provided when the first lookup -fails. To achieve that the check is preceeded by a smp_rmb() which pairs -with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that -the stores in posix_cpu_timers*_exit() are visible. - -The history of the non-leader exec() issue goes back to the early days of -posix CPU timers, which stored a pointer to the group leader task in the -timer. That obviously fails when a non-leader exec() switches the leader. -commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems -with mt exec") added a temporary workaround for that in 2010 which survived -about 10 years. The fix for the workaround changed the task pointer to a -pid pointer, but failed to see the subtle race described above. So the -Fixes tag picks that commit, which seems to be halfways accurate. - -Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for -review, feedback and suggestions and to Wongi and Jungwoo for the excellent -bug report and analysis! - -Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") -Reported-by: Wongi Lee -Reported-by: Jungwoo Lee -Signed-off-by: Thomas Gleixner -Reviewed-by: Oleg Nesterov -Cc: stable@vger.kernel.org -Signed-off-by: Sasha Levin ---- - kernel/exit.c | 8 +- - kernel/signal.c | 10 +- - kernel/time/posix-cpu-timers.c | 185 +++++++++++++++++++++------------ - 3 files changed, 137 insertions(+), 66 deletions(-) - -diff --git a/kernel/exit.c b/kernel/exit.c -index 99ab6d7a09cee6..675a0f4c89e19b 100644 ---- a/kernel/exit.c -+++ b/kernel/exit.c -@@ -200,7 +200,13 @@ static void __exit_signal(struct task_struct *tsk) - * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. - */ - flush_sigqueue(&tsk->pending); -- tsk->sighand = NULL; -+ -+ /* -+ * Ensure that all preceeding state is visible. Pairs with -+ * the smp_acquire__after_ctrl_dep() in the sighand == NULL -+ * path of lock_task_sighand(). -+ */ -+ smp_store_release(&tsk->sighand, NULL); - spin_unlock(&sighand->siglock); - - __cleanup_sighand(sighand); -diff --git a/kernel/signal.c b/kernel/signal.c -index 463b798651b6a6..e0fb44e57a3166 100644 ---- a/kernel/signal.c -+++ b/kernel/signal.c -@@ -1375,8 +1375,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, - rcu_read_lock(); - for (;;) { - sighand = rcu_dereference(tsk->sighand); -- if (unlikely(sighand == NULL)) -+ if (unlikely(sighand == NULL)) { -+ /* -+ * Pairs with the smp_store_release() in -+ * __exit_signal(). It ensures that all state -+ * modifications to the task preceeding the store are -+ * visible to the callers of lock_task_sighand(). -+ */ -+ smp_acquire__after_ctrl_dep(); - break; -+ } - - /* - * This sighand can be already freed and even reused, but -diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c -index ded4a0edaa04e9..7cf6f083a54968 100644 ---- a/kernel/time/posix-cpu-timers.c -+++ b/kernel/time/posix-cpu-timers.c -@@ -405,6 +405,110 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer) - return 0; - } - -+/* -+ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. -+ * -+ * This can race with the reaping of the task: -+ * -+ * CPU0 CPU1 -+ * -+ * // Finds task -+ * p = pid_task(pid, pid_type); __exit_signal(p) -+ * lock(p, sighand); -+ * posix_cpu_timers*_exit(); -+ * sighand = lock_task_sighand(p); unhash_task(p); -+ * p->sighand = NULL; -+ * unlock(sighand); -+ * -+ * In this case sighand is NULL, which means the task and the associated timer -+ * queue cannot be longer accessed safely. -+ * -+ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is -+ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete -+ * all pending timers from the related timer queues. The POSIX timers (k_itimer) -+ * themself are still accessible, but not longer connected to the task. -+ * -+ * exec() works slightly differently. The task which exec()'s terminates all -+ * other threads in the thread group and runs __exit_signal() on them. As the -+ * thread group is not dead they only clean up the per task timers via -+ * posix_cpu_timers_exit(). -+ * -+ * As the TGID on exec() stays the same per process timers stay queued, if they -+ * are armed. This works without a problem when exec() is done by the thread -+ * group leader. If a non-leader thread exec()'s this can end up in the -+ * following scenario: -+ * -+ * CPU0 CPU1 -+ * // Returns old leader -+ * p = pid_task(pid, pid_type); de_thread() -+ * switch_leader() -+ * release_task(old leader) -+ * __exit_signal() -+ * old_leader->sighand = NULL; -+ * // Returns NULL -+ * sighand = lock_task_sighand(p) -+ * -+ * That's problematic for several functions: -+ * -+ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the -+ * underlying k_itimer will be freed which results in a UAF in -+ * run_posix_cpu_timers() or on timerqueue related add/delete operations. -+ * If the timer is not enqueued, the failure is harmless -+ * -+ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a -+ * transient failure which is user space visible (-ESRCH) for regular posix -+ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF -+ * problem just that the timer is allocated on the stack. -+ * -+ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this -+ * silently ignores the rearm request, which is a functional problem as the -+ * timer wont expire anymore. -+ */ -+static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) -+{ -+ enum pid_type type = clock_pid_type(timer->it_clock); -+ struct cpu_timer *ctmr = &timer->it.cpu; -+ -+ guard(rcu)(); -+ -+ for (;;) { -+ struct task_struct *t = pid_task(timer->it.cpu.pid, type); -+ -+ /* Fail if the task cannot be found. */ -+ if (!t) -+ break; -+ -+ /* Try to lock the task's sighand */ -+ if (lock_task_sighand(t, flags)) -+ return t; -+ -+ /* -+ * The next PID lookup might either fail or return the new -+ * leader. This is correct for both exit() and exec(). -+ */ -+ } -+ -+ /* -+ * If the timer is still enqueued, warn. There is nothing safe to do -+ * here as there might be two timers in there which are removed in -+ * parallel and that will cause more damage than good. This should never -+ * happen! -+ * -+ * Ensure that the stores to the timer and timerqueue are visible: -+ * -+ * __exit_signal() -+ * posix_cpu_timers*_exit() -+ * write_seqlock(seqlock) -+ * smp_wmb(); <------- -+ * __unhash_process() | !pid_task() -+ * ----> smp_rmb(); -+ * WARN_ON_ONCE(...) -+ */ -+ smp_rmb(); -+ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -+ return NULL; -+} -+ - /* - * Clean up a CPU-clock timer that is about to be destroyed. - * This is called from timer deletion with the timer already locked. -@@ -414,28 +518,13 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer) - static int posix_cpu_timer_del(struct k_itimer *timer) - { - struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -+ p = timer_lock_sighand(timer, &flags); - -- /* -- * Protect against sighand release/switch in exit/exec and process/ -- * thread timer list entry concurrent read/writes. -- */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) { -- /* -- * This raced with the reaping of the task. The exit cleanup -- * should have removed this timer from the timer queue. -- */ -- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -- } else { -+ if (likely(p)) { - if (timer->it.cpu.firing) - ret = TIMER_RETRY; - else -@@ -444,8 +533,6 @@ static int posix_cpu_timer_del(struct k_itimer *timer) - unlock_task_sighand(p, &flags); - } - --out: -- rcu_read_unlock(); - if (!ret) - put_pid(ctmr->pid); - -@@ -575,21 +662,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - u64 old_expires, new_expires, old_incr, val; - struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) { -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- rcu_read_unlock(); -+ p = timer_lock_sighand(timer, &flags); -+ /* -+ * If p has just been reaped, we can no longer get any information about -+ * it at all. -+ */ -+ if (!p) - return -ESRCH; -- } - - /* - * Use the to_ktime conversion because that clamps the maximum -@@ -597,20 +680,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - */ - new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); - -- /* -- * Protect against sighand release/switch in exit/exec and p->cpu_timers -- * and p->signal->cpu_timers read/write in arm_timer() -- */ -- sighand = lock_task_sighand(p, &flags); -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- if (unlikely(sighand == NULL)) { -- rcu_read_unlock(); -- return -ESRCH; -- } -- - /* - * Disarm any old timer after extracting its expiry time. - */ -@@ -659,6 +728,8 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - old->it_value.tv_sec = 0; - } - } -+ -+ old->it_interval = ns_to_timespec64(old_incr); - } - - if (unlikely(ret)) { -@@ -669,7 +740,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - * it as an overrun (thanks to bump_cpu_timer above). - */ - unlock_task_sighand(p, &flags); -- goto out; -+ return ret; - } - - if (new_expires != 0 && !(timer_flags & TIMER_ABSTIME)) { -@@ -686,7 +757,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - arm_timer(timer, p); - } - -- unlock_task_sighand(p, &flags); - /* - * Install the new reload setting, and - * set up the signal and overrun bookkeeping. -@@ -703,6 +773,8 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - timer->it_overrun_last = 0; - timer->it_overrun = -1; - -+ unlock_task_sighand(p, &flags); -+ - if (new_expires != 0 && !(val < new_expires)) { - /* - * The designated time already passed, so we notify -@@ -712,13 +784,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - cpu_timer_fire(timer); - } - -- ret = 0; -- out: -- rcu_read_unlock(); -- if (old) -- old->it_interval = ns_to_timespec64(old_incr); -- -- return ret; -+ return 0; - } - - static void posix_cpu_timer_get(struct k_itimer *timer, struct itimerspec64 *itp) -@@ -984,19 +1050,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - { - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - struct task_struct *p; -- struct sighand_struct *sighand; - unsigned long flags; - u64 now; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -- -- /* Protect timer list r/w in arm_timer() */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) -- goto out; -+ p = timer_lock_sighand(timer, &flags); -+ if (unlikely(!p)) -+ return; - - /* - * Fetch the current sample and update the timer's expiry time. -@@ -1013,8 +1072,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - */ - arm_timer(timer, p); - unlock_task_sighand(p, &flags); --out: -- rcu_read_unlock(); - } - - /** --- -2.53.0 - diff --git a/queue-5.10/series b/queue-5.10/series index 339f1739a3..276166d83a 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -55,7 +55,6 @@ usb-gadget-uvc-clamp-send_response-length-to-the-response-buffer.patch usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-option-add-tdtech-mt5710-cn.patch -posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch bpf-fix-ld_-abs-ind-failure-path-analysis-in-subprog.patch usb-xhci-pci-limit-via-vl805-dma-addressing-to-36-bi.patch diff --git a/queue-5.15/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-5.15/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch deleted file mode 100644 index feb8a25c84..0000000000 --- a/queue-5.15/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +++ /dev/null @@ -1,486 +0,0 @@ -From 09486fef145315e0221c496ec7af5fc66c785357 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Mon, 27 Jul 2026 11:10:14 +0200 -Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race - -From: Thomas Gleixner - -commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. - -Wongi and Jungwoo decoded and reported a non-leader exec() related race -which can result in an UAF: - - sys_timer_delete() exec() - posix_cpu_timer_del() - // Observes old leader - p = pid_task(pid, pid_type); de_thread() - switch_leader(); - release_task(old_leader) - __exit_signal(old_leader) - sighand = lock(old_leader, sighand); - posix_cpu_timers*_exit(); - sighand = lock_task_sighand(p) unhash_task(old_leader); - sh = lock(p, sighand) old_leader->sighand = NULL; - unlock(sighand); - (p->sighand == NULL) - unlock(sh) - return NULL; - - // Returns without action - if(!sighand) - return 0; - free_posix_timer(); - -This is "harmless" unless the deleted timer was armed and enqueued in -p->signal because on exec() a TGID targeted timer is inherited. - -As sys_timer_delete() freed the underlying posix timer object -run_posix_cpu_timers() or any timerqueue related add/delete operations on -other timers will access the freed object's timerqueue node, which results -in an UAF. - -There is a similar problem vs. posix_cpu_timer_set(). For regular posix -timers it just transiently returns -ESRCH to user space, but for the use -case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is -allocated on the stack. - -Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops -to expire. - -While debating solutions Frederic pointed out another problem: - - posix_cpu_timer_del(tmr) - __exit_signal(p) - posix_cpu_timers*_exit(p); - unhash_task(p); - p->sighand = NULL; - sh = lock_task_sighand(p) - sighand = p->sighand; - if (!sighand) - return NULL; - lock(sighand); - - if (!sh) - WARN_ON_ONCE(timer_queued(tmr)); - -On weakly ordered architectures it is not guaranteed that -posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() -when p->sighand is observed as NULL, which means the WARN() can be a false -positive. - -Solve these issues by: - - 1) Changing the store in __exit_signal() to smp_store_release(). - - 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path - of lock_task_sighand(). - - 3) Creating a helper function for looking up the task and locking sighand - which does not return when sighand == NULL. Instead it retries the - task lookup and only if that fails it gives up. - - 4) Using that helper in the three affected functions. - -#1/#2 ensures that the reader side which observes sighand == NULL also -observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() -and the ones in unhash_task(). - -#3 ensures that the above described non-leader exec() situation is handled -gracefully. When the task lookup returns the old leader, but sighand == -NULL then it retries. In the non-leader exec() case the subsequent task -lookup will observe the new leader due to #1/#2. In normal exit() scenarios -the subsequent lookup fails. - -When the task lookup fails, the function also checks whether the timer is -still enqueued and issues a warning if that's the case. Unfortunately there -is nothing which can be done about it, but as the task is already not -longer visible the timer should not be accessed anymore. This check also -requires memory ordering, which is not provided when the first lookup -fails. To achieve that the check is preceeded by a smp_rmb() which pairs -with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that -the stores in posix_cpu_timers*_exit() are visible. - -The history of the non-leader exec() issue goes back to the early days of -posix CPU timers, which stored a pointer to the group leader task in the -timer. That obviously fails when a non-leader exec() switches the leader. -commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems -with mt exec") added a temporary workaround for that in 2010 which survived -about 10 years. The fix for the workaround changed the task pointer to a -pid pointer, but failed to see the subtle race described above. So the -Fixes tag picks that commit, which seems to be halfways accurate. - -Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for -review, feedback and suggestions and to Wongi and Jungwoo for the excellent -bug report and analysis! - -Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") -Reported-by: Wongi Lee -Reported-by: Jungwoo Lee -Signed-off-by: Thomas Gleixner -Reviewed-by: Oleg Nesterov -Cc: stable@vger.kernel.org -Signed-off-by: Sasha Levin ---- - kernel/exit.c | 8 +- - kernel/signal.c | 10 +- - kernel/time/posix-cpu-timers.c | 212 ++++++++++++++++++++------------- - 3 files changed, 143 insertions(+), 87 deletions(-) - -diff --git a/kernel/exit.c b/kernel/exit.c -index 3c6ddf7320fae6..776994c28887a6 100644 ---- a/kernel/exit.c -+++ b/kernel/exit.c -@@ -200,7 +200,13 @@ static void __exit_signal(struct task_struct *tsk) - * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. - */ - flush_sigqueue(&tsk->pending); -- tsk->sighand = NULL; -+ -+ /* -+ * Ensure that all preceeding state is visible. Pairs with -+ * the smp_acquire__after_ctrl_dep() in the sighand == NULL -+ * path of lock_task_sighand(). -+ */ -+ smp_store_release(&tsk->sighand, NULL); - spin_unlock(&sighand->siglock); - - __cleanup_sighand(sighand); -diff --git a/kernel/signal.c b/kernel/signal.c -index 10a315e461d34b..662377d23c95d0 100644 ---- a/kernel/signal.c -+++ b/kernel/signal.c -@@ -1388,8 +1388,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, - rcu_read_lock(); - for (;;) { - sighand = rcu_dereference(tsk->sighand); -- if (unlikely(sighand == NULL)) -+ if (unlikely(sighand == NULL)) { -+ /* -+ * Pairs with the smp_store_release() in -+ * __exit_signal(). It ensures that all state -+ * modifications to the task preceeding the store are -+ * visible to the callers of lock_task_sighand(). -+ */ -+ smp_acquire__after_ctrl_dep(); - break; -+ } - - /* - * This sighand can be already freed and even reused, but -diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c -index c99e11da83e85e..52deada50891ab 100644 ---- a/kernel/time/posix-cpu-timers.c -+++ b/kernel/time/posix-cpu-timers.c -@@ -455,6 +455,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - trigger_base_recalc_expires(timer, p); - } - -+/* -+ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. -+ * -+ * This can race with the reaping of the task: -+ * -+ * CPU0 CPU1 -+ * -+ * // Finds task -+ * p = pid_task(pid, pid_type); __exit_signal(p) -+ * lock(p, sighand); -+ * posix_cpu_timers*_exit(); -+ * sighand = lock_task_sighand(p); unhash_task(p); -+ * p->sighand = NULL; -+ * unlock(sighand); -+ * -+ * In this case sighand is NULL, which means the task and the associated timer -+ * queue cannot be longer accessed safely. -+ * -+ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is -+ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete -+ * all pending timers from the related timer queues. The POSIX timers (k_itimer) -+ * themself are still accessible, but not longer connected to the task. -+ * -+ * exec() works slightly differently. The task which exec()'s terminates all -+ * other threads in the thread group and runs __exit_signal() on them. As the -+ * thread group is not dead they only clean up the per task timers via -+ * posix_cpu_timers_exit(). -+ * -+ * As the TGID on exec() stays the same per process timers stay queued, if they -+ * are armed. This works without a problem when exec() is done by the thread -+ * group leader. If a non-leader thread exec()'s this can end up in the -+ * following scenario: -+ * -+ * CPU0 CPU1 -+ * // Returns old leader -+ * p = pid_task(pid, pid_type); de_thread() -+ * switch_leader() -+ * release_task(old leader) -+ * __exit_signal() -+ * old_leader->sighand = NULL; -+ * // Returns NULL -+ * sighand = lock_task_sighand(p) -+ * -+ * That's problematic for several functions: -+ * -+ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the -+ * underlying k_itimer will be freed which results in a UAF in -+ * run_posix_cpu_timers() or on timerqueue related add/delete operations. -+ * If the timer is not enqueued, the failure is harmless -+ * -+ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a -+ * transient failure which is user space visible (-ESRCH) for regular posix -+ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF -+ * problem just that the timer is allocated on the stack. -+ * -+ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this -+ * silently ignores the rearm request, which is a functional problem as the -+ * timer wont expire anymore. -+ */ -+static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) -+{ -+ enum pid_type type = clock_pid_type(timer->it_clock); -+ struct cpu_timer *ctmr = &timer->it.cpu; -+ -+ guard(rcu)(); -+ -+ for (;;) { -+ struct task_struct *t = pid_task(timer->it.cpu.pid, type); -+ -+ /* Fail if the task cannot be found. */ -+ if (!t) -+ break; -+ -+ /* Try to lock the task's sighand */ -+ if (lock_task_sighand(t, flags)) -+ return t; -+ -+ /* -+ * The next PID lookup might either fail or return the new -+ * leader. This is correct for both exit() and exec(). -+ */ -+ } -+ -+ /* -+ * If the timer is still enqueued, warn. There is nothing safe to do -+ * here as there might be two timers in there which are removed in -+ * parallel and that will cause more damage than good. This should never -+ * happen! -+ * -+ * Ensure that the stores to the timer and timerqueue are visible: -+ * -+ * __exit_signal() -+ * posix_cpu_timers*_exit() -+ * write_seqlock(seqlock) -+ * smp_wmb(); <------- -+ * __unhash_process() | !pid_task() -+ * ----> smp_rmb(); -+ * WARN_ON_ONCE(...) -+ */ -+ smp_rmb(); -+ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -+ return NULL; -+} - - /* - * Clean up a CPU-clock timer that is about to be destroyed. -@@ -464,29 +567,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - */ - static int posix_cpu_timer_del(struct k_itimer *timer) - { -- struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -+ p = timer_lock_sighand(timer, &flags); - -- /* -- * Protect against sighand release/switch in exit/exec and process/ -- * thread timer list entry concurrent read/writes. -- */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) { -- /* -- * This raced with the reaping of the task. The exit cleanup -- * should have removed this timer from the timer queue. -- */ -- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -- } else { -+ if (likely(p)) { - if (timer->it.cpu.firing) - ret = TIMER_RETRY; - else -@@ -495,10 +582,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) - unlock_task_sighand(p, &flags); - } - --out: -- rcu_read_unlock(); - if (!ret) -- put_pid(ctmr->pid); -+ put_pid(timer->it.cpu.pid); - - return ret; - } -@@ -620,21 +705,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - u64 old_expires, new_expires, old_incr, val; - struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) { -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- rcu_read_unlock(); -+ p = timer_lock_sighand(timer, &flags); -+ /* -+ * If p has just been reaped, we can no longer get any information about -+ * it at all. -+ */ -+ if (!p) - return -ESRCH; -- } - - /* - * Use the to_ktime conversion because that clamps the maximum -@@ -642,20 +723,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - */ - new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); - -- /* -- * Protect against sighand release/switch in exit/exec and p->cpu_timers -- * and p->signal->cpu_timers read/write in arm_timer() -- */ -- sighand = lock_task_sighand(p, &flags); -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- if (unlikely(sighand == NULL)) { -- rcu_read_unlock(); -- return -ESRCH; -- } -- - /* - * Disarm any old timer after extracting its expiry time. - */ -@@ -704,6 +771,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - old->it_value.tv_sec = 0; - } - } -+ old->it_interval = ns_to_timespec64(old_incr); - } - - if (unlikely(ret)) { -@@ -714,7 +782,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - * it as an overrun (thanks to bump_cpu_timer above). - */ - unlock_task_sighand(p, &flags); -- goto out; -+ return ret; - } - - if (new_expires != 0 && !(timer_flags & TIMER_ABSTIME)) { -@@ -727,11 +795,11 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - * arm the timer (we'll just fake it for timer_gettime). - */ - cpu_timer_setexpires(ctmr, new_expires); -- if (new_expires != 0 && val < new_expires) { -+ if (new_expires != 0 && val < new_expires) - arm_timer(timer, p); -- } -+ else -+ trigger_base_recalc_expires(timer, p); - -- unlock_task_sighand(p, &flags); - /* - * Install the new reload setting, and - * set up the signal and overrun bookkeeping. -@@ -748,35 +816,18 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - timer->it_overrun_last = 0; - timer->it_overrun = -1; - -- if (val >= new_expires) { -- if (new_expires != 0) { -- /* -- * The designated time already passed, so we notify -- * immediately, even if the thread never runs to -- * accumulate more time on this clock. -- */ -- cpu_timer_fire(timer); -- } -+ unlock_task_sighand(p, &flags); - -+ if (new_expires && val >= new_expires) { - /* -- * Make sure we don't keep around the process wide cputime -- * counter or the tick dependency if they are not necessary. -+ * The designated time already passed, so we notify immediately, -+ * even if the thread never runs to accumulate more time on this -+ * clock. - */ -- sighand = lock_task_sighand(p, &flags); -- if (!sighand) -- goto out; -- -- if (!cpu_timer_queued(ctmr)) -- trigger_base_recalc_expires(timer, p); -- -- unlock_task_sighand(p, &flags); -+ cpu_timer_fire(timer); - } -- out: -- rcu_read_unlock(); -- if (old) -- old->it_interval = ns_to_timespec64(old_incr); - -- return ret; -+ return 0; - } - - static void posix_cpu_timer_get(struct k_itimer *timer, struct itimerspec64 *itp) -@@ -1042,19 +1093,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - { - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - struct task_struct *p; -- struct sighand_struct *sighand; - unsigned long flags; - u64 now; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -- -- /* Protect timer list r/w in arm_timer() */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) -- goto out; -+ p = timer_lock_sighand(timer, &flags); -+ if (unlikely(!p)) -+ return; - - /* - * Fetch the current sample and update the timer's expiry time. -@@ -1071,8 +1115,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - */ - arm_timer(timer, p); - unlock_task_sighand(p, &flags); --out: -- rcu_read_unlock(); - } - - /** --- -2.53.0 - diff --git a/queue-5.15/series b/queue-5.15/series index 7259ea0136..74221b65e6 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -71,7 +71,6 @@ usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch -posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch bpf-fix-ld_-abs-ind-failure-path-analysis-in-subprog.patch diff --git a/queue-6.1/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-6.1/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch deleted file mode 100644 index b7bafc9f02..0000000000 --- a/queue-6.1/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +++ /dev/null @@ -1,486 +0,0 @@ -From 30d9395052ea8c37d41b2f1329d6fadca3e801f7 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Mon, 27 Jul 2026 11:10:14 +0200 -Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race - -From: Thomas Gleixner - -commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. - -Wongi and Jungwoo decoded and reported a non-leader exec() related race -which can result in an UAF: - - sys_timer_delete() exec() - posix_cpu_timer_del() - // Observes old leader - p = pid_task(pid, pid_type); de_thread() - switch_leader(); - release_task(old_leader) - __exit_signal(old_leader) - sighand = lock(old_leader, sighand); - posix_cpu_timers*_exit(); - sighand = lock_task_sighand(p) unhash_task(old_leader); - sh = lock(p, sighand) old_leader->sighand = NULL; - unlock(sighand); - (p->sighand == NULL) - unlock(sh) - return NULL; - - // Returns without action - if(!sighand) - return 0; - free_posix_timer(); - -This is "harmless" unless the deleted timer was armed and enqueued in -p->signal because on exec() a TGID targeted timer is inherited. - -As sys_timer_delete() freed the underlying posix timer object -run_posix_cpu_timers() or any timerqueue related add/delete operations on -other timers will access the freed object's timerqueue node, which results -in an UAF. - -There is a similar problem vs. posix_cpu_timer_set(). For regular posix -timers it just transiently returns -ESRCH to user space, but for the use -case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is -allocated on the stack. - -Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops -to expire. - -While debating solutions Frederic pointed out another problem: - - posix_cpu_timer_del(tmr) - __exit_signal(p) - posix_cpu_timers*_exit(p); - unhash_task(p); - p->sighand = NULL; - sh = lock_task_sighand(p) - sighand = p->sighand; - if (!sighand) - return NULL; - lock(sighand); - - if (!sh) - WARN_ON_ONCE(timer_queued(tmr)); - -On weakly ordered architectures it is not guaranteed that -posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() -when p->sighand is observed as NULL, which means the WARN() can be a false -positive. - -Solve these issues by: - - 1) Changing the store in __exit_signal() to smp_store_release(). - - 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path - of lock_task_sighand(). - - 3) Creating a helper function for looking up the task and locking sighand - which does not return when sighand == NULL. Instead it retries the - task lookup and only if that fails it gives up. - - 4) Using that helper in the three affected functions. - -#1/#2 ensures that the reader side which observes sighand == NULL also -observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() -and the ones in unhash_task(). - -#3 ensures that the above described non-leader exec() situation is handled -gracefully. When the task lookup returns the old leader, but sighand == -NULL then it retries. In the non-leader exec() case the subsequent task -lookup will observe the new leader due to #1/#2. In normal exit() scenarios -the subsequent lookup fails. - -When the task lookup fails, the function also checks whether the timer is -still enqueued and issues a warning if that's the case. Unfortunately there -is nothing which can be done about it, but as the task is already not -longer visible the timer should not be accessed anymore. This check also -requires memory ordering, which is not provided when the first lookup -fails. To achieve that the check is preceeded by a smp_rmb() which pairs -with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that -the stores in posix_cpu_timers*_exit() are visible. - -The history of the non-leader exec() issue goes back to the early days of -posix CPU timers, which stored a pointer to the group leader task in the -timer. That obviously fails when a non-leader exec() switches the leader. -commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems -with mt exec") added a temporary workaround for that in 2010 which survived -about 10 years. The fix for the workaround changed the task pointer to a -pid pointer, but failed to see the subtle race described above. So the -Fixes tag picks that commit, which seems to be halfways accurate. - -Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for -review, feedback and suggestions and to Wongi and Jungwoo for the excellent -bug report and analysis! - -Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") -Reported-by: Wongi Lee -Reported-by: Jungwoo Lee -Signed-off-by: Thomas Gleixner -Reviewed-by: Oleg Nesterov -Cc: stable@vger.kernel.org -Signed-off-by: Sasha Levin ---- - kernel/exit.c | 8 +- - kernel/signal.c | 10 +- - kernel/time/posix-cpu-timers.c | 212 ++++++++++++++++++++------------- - 3 files changed, 143 insertions(+), 87 deletions(-) - -diff --git a/kernel/exit.c b/kernel/exit.c -index fef87ddfa84150..413540df3e1855 100644 ---- a/kernel/exit.c -+++ b/kernel/exit.c -@@ -203,7 +203,13 @@ static void __exit_signal(struct task_struct *tsk) - * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. - */ - flush_sigqueue(&tsk->pending); -- tsk->sighand = NULL; -+ -+ /* -+ * Ensure that all preceeding state is visible. Pairs with -+ * the smp_acquire__after_ctrl_dep() in the sighand == NULL -+ * path of lock_task_sighand(). -+ */ -+ smp_store_release(&tsk->sighand, NULL); - spin_unlock(&sighand->siglock); - - __cleanup_sighand(sighand); -diff --git a/kernel/signal.c b/kernel/signal.c -index 98dbe713829ec3..3dd8c93c2dcc0b 100644 ---- a/kernel/signal.c -+++ b/kernel/signal.c -@@ -1395,8 +1395,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, - rcu_read_lock(); - for (;;) { - sighand = rcu_dereference(tsk->sighand); -- if (unlikely(sighand == NULL)) -+ if (unlikely(sighand == NULL)) { -+ /* -+ * Pairs with the smp_store_release() in -+ * __exit_signal(). It ensures that all state -+ * modifications to the task preceeding the store are -+ * visible to the callers of lock_task_sighand(). -+ */ -+ smp_acquire__after_ctrl_dep(); - break; -+ } - - /* - * This sighand can be already freed and even reused, but -diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c -index c77796da8bbce5..c5f7ca683e7eba 100644 ---- a/kernel/time/posix-cpu-timers.c -+++ b/kernel/time/posix-cpu-timers.c -@@ -462,6 +462,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - trigger_base_recalc_expires(timer, p); - } - -+/* -+ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. -+ * -+ * This can race with the reaping of the task: -+ * -+ * CPU0 CPU1 -+ * -+ * // Finds task -+ * p = pid_task(pid, pid_type); __exit_signal(p) -+ * lock(p, sighand); -+ * posix_cpu_timers*_exit(); -+ * sighand = lock_task_sighand(p); unhash_task(p); -+ * p->sighand = NULL; -+ * unlock(sighand); -+ * -+ * In this case sighand is NULL, which means the task and the associated timer -+ * queue cannot be longer accessed safely. -+ * -+ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is -+ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete -+ * all pending timers from the related timer queues. The POSIX timers (k_itimer) -+ * themself are still accessible, but not longer connected to the task. -+ * -+ * exec() works slightly differently. The task which exec()'s terminates all -+ * other threads in the thread group and runs __exit_signal() on them. As the -+ * thread group is not dead they only clean up the per task timers via -+ * posix_cpu_timers_exit(). -+ * -+ * As the TGID on exec() stays the same per process timers stay queued, if they -+ * are armed. This works without a problem when exec() is done by the thread -+ * group leader. If a non-leader thread exec()'s this can end up in the -+ * following scenario: -+ * -+ * CPU0 CPU1 -+ * // Returns old leader -+ * p = pid_task(pid, pid_type); de_thread() -+ * switch_leader() -+ * release_task(old leader) -+ * __exit_signal() -+ * old_leader->sighand = NULL; -+ * // Returns NULL -+ * sighand = lock_task_sighand(p) -+ * -+ * That's problematic for several functions: -+ * -+ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the -+ * underlying k_itimer will be freed which results in a UAF in -+ * run_posix_cpu_timers() or on timerqueue related add/delete operations. -+ * If the timer is not enqueued, the failure is harmless -+ * -+ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a -+ * transient failure which is user space visible (-ESRCH) for regular posix -+ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF -+ * problem just that the timer is allocated on the stack. -+ * -+ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this -+ * silently ignores the rearm request, which is a functional problem as the -+ * timer wont expire anymore. -+ */ -+static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) -+{ -+ enum pid_type type = clock_pid_type(timer->it_clock); -+ struct cpu_timer *ctmr = &timer->it.cpu; -+ -+ guard(rcu)(); -+ -+ for (;;) { -+ struct task_struct *t = pid_task(timer->it.cpu.pid, type); -+ -+ /* Fail if the task cannot be found. */ -+ if (!t) -+ break; -+ -+ /* Try to lock the task's sighand */ -+ if (lock_task_sighand(t, flags)) -+ return t; -+ -+ /* -+ * The next PID lookup might either fail or return the new -+ * leader. This is correct for both exit() and exec(). -+ */ -+ } -+ -+ /* -+ * If the timer is still enqueued, warn. There is nothing safe to do -+ * here as there might be two timers in there which are removed in -+ * parallel and that will cause more damage than good. This should never -+ * happen! -+ * -+ * Ensure that the stores to the timer and timerqueue are visible: -+ * -+ * __exit_signal() -+ * posix_cpu_timers*_exit() -+ * write_seqlock(seqlock) -+ * smp_wmb(); <------- -+ * __unhash_process() | !pid_task() -+ * ----> smp_rmb(); -+ * WARN_ON_ONCE(...) -+ */ -+ smp_rmb(); -+ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -+ return NULL; -+} - - /* - * Clean up a CPU-clock timer that is about to be destroyed. -@@ -471,29 +574,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - */ - static int posix_cpu_timer_del(struct k_itimer *timer) - { -- struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -+ p = timer_lock_sighand(timer, &flags); - -- /* -- * Protect against sighand release/switch in exit/exec and process/ -- * thread timer list entry concurrent read/writes. -- */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) { -- /* -- * This raced with the reaping of the task. The exit cleanup -- * should have removed this timer from the timer queue. -- */ -- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -- } else { -+ if (likely(p)) { - if (timer->it.cpu.firing) - ret = TIMER_RETRY; - else -@@ -502,10 +589,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) - unlock_task_sighand(p, &flags); - } - --out: -- rcu_read_unlock(); - if (!ret) -- put_pid(ctmr->pid); -+ put_pid(timer->it.cpu.pid); - - return ret; - } -@@ -627,21 +712,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - u64 old_expires, new_expires, old_incr, val; - struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) { -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- rcu_read_unlock(); -+ p = timer_lock_sighand(timer, &flags); -+ /* -+ * If p has just been reaped, we can no longer get any information about -+ * it at all. -+ */ -+ if (!p) - return -ESRCH; -- } - - /* - * Use the to_ktime conversion because that clamps the maximum -@@ -649,20 +730,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - */ - new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); - -- /* -- * Protect against sighand release/switch in exit/exec and p->cpu_timers -- * and p->signal->cpu_timers read/write in arm_timer() -- */ -- sighand = lock_task_sighand(p, &flags); -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- if (unlikely(sighand == NULL)) { -- rcu_read_unlock(); -- return -ESRCH; -- } -- - /* - * Disarm any old timer after extracting its expiry time. - */ -@@ -711,6 +778,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - old->it_value.tv_sec = 0; - } - } -+ old->it_interval = ns_to_timespec64(old_incr); - } - - if (unlikely(ret)) { -@@ -721,7 +789,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - * it as an overrun (thanks to bump_cpu_timer above). - */ - unlock_task_sighand(p, &flags); -- goto out; -+ return ret; - } - - if (new_expires != 0 && !(timer_flags & TIMER_ABSTIME)) { -@@ -734,11 +802,11 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - * arm the timer (we'll just fake it for timer_gettime). - */ - cpu_timer_setexpires(ctmr, new_expires); -- if (new_expires != 0 && val < new_expires) { -+ if (new_expires != 0 && val < new_expires) - arm_timer(timer, p); -- } -+ else -+ trigger_base_recalc_expires(timer, p); - -- unlock_task_sighand(p, &flags); - /* - * Install the new reload setting, and - * set up the signal and overrun bookkeeping. -@@ -755,35 +823,18 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - timer->it_overrun_last = 0; - timer->it_overrun = -1; - -- if (val >= new_expires) { -- if (new_expires != 0) { -- /* -- * The designated time already passed, so we notify -- * immediately, even if the thread never runs to -- * accumulate more time on this clock. -- */ -- cpu_timer_fire(timer); -- } -+ unlock_task_sighand(p, &flags); - -+ if (new_expires && val >= new_expires) { - /* -- * Make sure we don't keep around the process wide cputime -- * counter or the tick dependency if they are not necessary. -+ * The designated time already passed, so we notify immediately, -+ * even if the thread never runs to accumulate more time on this -+ * clock. - */ -- sighand = lock_task_sighand(p, &flags); -- if (!sighand) -- goto out; -- -- if (!cpu_timer_queued(ctmr)) -- trigger_base_recalc_expires(timer, p); -- -- unlock_task_sighand(p, &flags); -+ cpu_timer_fire(timer); - } -- out: -- rcu_read_unlock(); -- if (old) -- old->it_interval = ns_to_timespec64(old_incr); - -- return ret; -+ return 0; - } - - static void posix_cpu_timer_get(struct k_itimer *timer, struct itimerspec64 *itp) -@@ -1049,19 +1100,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - { - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - struct task_struct *p; -- struct sighand_struct *sighand; - unsigned long flags; - u64 now; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -- -- /* Protect timer list r/w in arm_timer() */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) -- goto out; -+ p = timer_lock_sighand(timer, &flags); -+ if (unlikely(!p)) -+ return; - - /* - * Fetch the current sample and update the timer's expiry time. -@@ -1078,8 +1122,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - */ - arm_timer(timer, p); - unlock_task_sighand(p, &flags); --out: -- rcu_read_unlock(); - } - - /** --- -2.53.0 - diff --git a/queue-6.1/series b/queue-6.1/series index 4e274c9486..d48bad259b 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -85,7 +85,6 @@ usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch -posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch bpf-support-for-hardening-against-jit-spraying.patch diff --git a/queue-6.12/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-6.12/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch deleted file mode 100644 index 56080dc573..0000000000 --- a/queue-6.12/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +++ /dev/null @@ -1,429 +0,0 @@ -From cf7b5a3c34e508b49afdda31e7e32ab0d99c7ef9 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Sat, 25 Jul 2026 22:10:32 +0200 -Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race - -From: Thomas Gleixner - -commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. - -Wongi and Jungwoo decoded and reported a non-leader exec() related race -which can result in an UAF: - - sys_timer_delete() exec() - posix_cpu_timer_del() - // Observes old leader - p = pid_task(pid, pid_type); de_thread() - switch_leader(); - release_task(old_leader) - __exit_signal(old_leader) - sighand = lock(old_leader, sighand); - posix_cpu_timers*_exit(); - sighand = lock_task_sighand(p) unhash_task(old_leader); - sh = lock(p, sighand) old_leader->sighand = NULL; - unlock(sighand); - (p->sighand == NULL) - unlock(sh) - return NULL; - - // Returns without action - if(!sighand) - return 0; - free_posix_timer(); - -This is "harmless" unless the deleted timer was armed and enqueued in -p->signal because on exec() a TGID targeted timer is inherited. - -As sys_timer_delete() freed the underlying posix timer object -run_posix_cpu_timers() or any timerqueue related add/delete operations on -other timers will access the freed object's timerqueue node, which results -in an UAF. - -There is a similar problem vs. posix_cpu_timer_set(). For regular posix -timers it just transiently returns -ESRCH to user space, but for the use -case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is -allocated on the stack. - -Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops -to expire. - -While debating solutions Frederic pointed out another problem: - - posix_cpu_timer_del(tmr) - __exit_signal(p) - posix_cpu_timers*_exit(p); - unhash_task(p); - p->sighand = NULL; - sh = lock_task_sighand(p) - sighand = p->sighand; - if (!sighand) - return NULL; - lock(sighand); - - if (!sh) - WARN_ON_ONCE(timer_queued(tmr)); - -On weakly ordered architectures it is not guaranteed that -posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() -when p->sighand is observed as NULL, which means the WARN() can be a false -positive. - -Solve these issues by: - - 1) Changing the store in __exit_signal() to smp_store_release(). - - 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path - of lock_task_sighand(). - - 3) Creating a helper function for looking up the task and locking sighand - which does not return when sighand == NULL. Instead it retries the - task lookup and only if that fails it gives up. - - 4) Using that helper in the three affected functions. - -#1/#2 ensures that the reader side which observes sighand == NULL also -observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() -and the ones in unhash_task(). - -#3 ensures that the above described non-leader exec() situation is handled -gracefully. When the task lookup returns the old leader, but sighand == -NULL then it retries. In the non-leader exec() case the subsequent task -lookup will observe the new leader due to #1/#2. In normal exit() scenarios -the subsequent lookup fails. - -When the task lookup fails, the function also checks whether the timer is -still enqueued and issues a warning if that's the case. Unfortunately there -is nothing which can be done about it, but as the task is already not -longer visible the timer should not be accessed anymore. This check also -requires memory ordering, which is not provided when the first lookup -fails. To achieve that the check is preceeded by a smp_rmb() which pairs -with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that -the stores in posix_cpu_timers*_exit() are visible. - -The history of the non-leader exec() issue goes back to the early days of -posix CPU timers, which stored a pointer to the group leader task in the -timer. That obviously fails when a non-leader exec() switches the leader. -commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems -with mt exec") added a temporary workaround for that in 2010 which survived -about 10 years. The fix for the workaround changed the task pointer to a -pid pointer, but failed to see the subtle race described above. So the -Fixes tag picks that commit, which seems to be halfways accurate. - -Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for -review, feedback and suggestions and to Wongi and Jungwoo for the excellent -bug report and analysis! - -Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") -Reported-by: Wongi Lee -Reported-by: Jungwoo Lee -Signed-off-by: Thomas Gleixner -Reviewed-by: Oleg Nesterov -Cc: stable@vger.kernel.org -Signed-off-by: Sasha Levin ---- - kernel/exit.c | 8 +- - kernel/signal.c | 10 +- - kernel/time/posix-cpu-timers.c | 176 ++++++++++++++++++++++----------- - 3 files changed, 132 insertions(+), 62 deletions(-) - -diff --git a/kernel/exit.c b/kernel/exit.c -index e798078f958c89..8ae45b2b8cf948 100644 ---- a/kernel/exit.c -+++ b/kernel/exit.c -@@ -205,7 +205,13 @@ static void __exit_signal(struct task_struct *tsk) - * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. - */ - flush_sigqueue(&tsk->pending); -- tsk->sighand = NULL; -+ -+ /* -+ * Ensure that all preceeding state is visible. Pairs with -+ * the smp_acquire__after_ctrl_dep() in the sighand == NULL -+ * path of lock_task_sighand(). -+ */ -+ smp_store_release(&tsk->sighand, NULL); - spin_unlock(&sighand->siglock); - - __cleanup_sighand(sighand); -diff --git a/kernel/signal.c b/kernel/signal.c -index b832158a9c4608..e6e812f8b80ffc 100644 ---- a/kernel/signal.c -+++ b/kernel/signal.c -@@ -1395,8 +1395,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, - rcu_read_lock(); - for (;;) { - sighand = rcu_dereference(tsk->sighand); -- if (unlikely(sighand == NULL)) -+ if (unlikely(sighand == NULL)) { -+ /* -+ * Pairs with the smp_store_release() in -+ * __exit_signal(). It ensures that all state -+ * modifications to the task preceeding the store are -+ * visible to the callers of lock_task_sighand(). -+ */ -+ smp_acquire__after_ctrl_dep(); - break; -+ } - - /* - * This sighand can be already freed and even reused, but -diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c -index 635fac5f5fbd58..f0471fbe87288b 100644 ---- a/kernel/time/posix-cpu-timers.c -+++ b/kernel/time/posix-cpu-timers.c -@@ -462,6 +462,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - trigger_base_recalc_expires(timer, p); - } - -+/* -+ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. -+ * -+ * This can race with the reaping of the task: -+ * -+ * CPU0 CPU1 -+ * -+ * // Finds task -+ * p = pid_task(pid, pid_type); __exit_signal(p) -+ * lock(p, sighand); -+ * posix_cpu_timers*_exit(); -+ * sighand = lock_task_sighand(p); unhash_task(p); -+ * p->sighand = NULL; -+ * unlock(sighand); -+ * -+ * In this case sighand is NULL, which means the task and the associated timer -+ * queue cannot be longer accessed safely. -+ * -+ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is -+ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete -+ * all pending timers from the related timer queues. The POSIX timers (k_itimer) -+ * themself are still accessible, but not longer connected to the task. -+ * -+ * exec() works slightly differently. The task which exec()'s terminates all -+ * other threads in the thread group and runs __exit_signal() on them. As the -+ * thread group is not dead they only clean up the per task timers via -+ * posix_cpu_timers_exit(). -+ * -+ * As the TGID on exec() stays the same per process timers stay queued, if they -+ * are armed. This works without a problem when exec() is done by the thread -+ * group leader. If a non-leader thread exec()'s this can end up in the -+ * following scenario: -+ * -+ * CPU0 CPU1 -+ * // Returns old leader -+ * p = pid_task(pid, pid_type); de_thread() -+ * switch_leader() -+ * release_task(old leader) -+ * __exit_signal() -+ * old_leader->sighand = NULL; -+ * // Returns NULL -+ * sighand = lock_task_sighand(p) -+ * -+ * That's problematic for several functions: -+ * -+ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the -+ * underlying k_itimer will be freed which results in a UAF in -+ * run_posix_cpu_timers() or on timerqueue related add/delete operations. -+ * If the timer is not enqueued, the failure is harmless -+ * -+ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a -+ * transient failure which is user space visible (-ESRCH) for regular posix -+ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF -+ * problem just that the timer is allocated on the stack. -+ * -+ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this -+ * silently ignores the rearm request, which is a functional problem as the -+ * timer wont expire anymore. -+ */ -+static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) -+{ -+ enum pid_type type = clock_pid_type(timer->it_clock); -+ struct cpu_timer *ctmr = &timer->it.cpu; -+ -+ guard(rcu)(); -+ -+ for (;;) { -+ struct task_struct *t = pid_task(timer->it.cpu.pid, type); -+ -+ /* Fail if the task cannot be found. */ -+ if (!t) -+ break; -+ -+ /* Try to lock the task's sighand */ -+ if (lock_task_sighand(t, flags)) -+ return t; -+ -+ /* -+ * The next PID lookup might either fail or return the new -+ * leader. This is correct for both exit() and exec(). -+ */ -+ } -+ -+ /* -+ * If the timer is still enqueued, warn. There is nothing safe to do -+ * here as there might be two timers in there which are removed in -+ * parallel and that will cause more damage than good. This should never -+ * happen! -+ * -+ * Ensure that the stores to the timer and timerqueue are visible: -+ * -+ * __exit_signal() -+ * posix_cpu_timers*_exit() -+ * write_seqlock(seqlock) -+ * smp_wmb(); <------- -+ * __unhash_process() | !pid_task() -+ * ----> smp_rmb(); -+ * WARN_ON_ONCE(...) -+ */ -+ smp_rmb(); -+ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -+ return NULL; -+} - - /* - * Clean up a CPU-clock timer that is about to be destroyed. -@@ -471,29 +574,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - */ - static int posix_cpu_timer_del(struct k_itimer *timer) - { -- struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -+ p = timer_lock_sighand(timer, &flags); - -- /* -- * Protect against sighand release/switch in exit/exec and process/ -- * thread timer list entry concurrent read/writes. -- */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) { -- /* -- * This raced with the reaping of the task. The exit cleanup -- * should have removed this timer from the timer queue. -- */ -- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -- } else { -+ if (likely(p)) { - if (timer->it.cpu.firing) - ret = TIMER_RETRY; - else -@@ -502,10 +589,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) - unlock_task_sighand(p, &flags); - } - --out: -- rcu_read_unlock(); - if (!ret) -- put_pid(ctmr->pid); -+ put_pid(timer->it.cpu.pid); - - return ret; - } -@@ -627,21 +712,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - struct cpu_timer *ctmr = &timer->it.cpu; - u64 old_expires, new_expires, now; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) { -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- rcu_read_unlock(); -+ p = timer_lock_sighand(timer, &flags); -+ /* -+ * If p has just been reaped, we can no longer get any information about -+ * it at all. -+ */ -+ if (!p) - return -ESRCH; -- } - - /* - * Use the to_ktime conversion because that clamps the maximum -@@ -649,20 +730,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - */ - new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); - -- /* -- * Protect against sighand release/switch in exit/exec and p->cpu_timers -- * and p->signal->cpu_timers read/write in arm_timer() -- */ -- sighand = lock_task_sighand(p, &flags); -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- if (unlikely(sighand == NULL)) { -- rcu_read_unlock(); -- return -ESRCH; -- } -- - /* Retrieve the current expiry time before disarming the timer */ - old_expires = cpu_timer_getexpires(ctmr); - -@@ -693,7 +760,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - /* Retry if the timer expiry is running concurrently */ - if (unlikely(ret)) { - unlock_task_sighand(p, &flags); -- goto out; -+ return ret; - } - - /* Convert relative expiry time to absolute */ -@@ -728,8 +795,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - */ - if (!sigev_none && new_expires && now >= new_expires) - cpu_timer_fire(timer); --out: -- rcu_read_unlock(); - return ret; - } - -@@ -1011,19 +1076,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - { - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - struct task_struct *p; -- struct sighand_struct *sighand; - unsigned long flags; - u64 now; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -- -- /* Protect timer list r/w in arm_timer() */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) -- goto out; -+ p = timer_lock_sighand(timer, &flags); -+ if (unlikely(!p)) -+ return; - - /* - * Fetch the current sample and update the timer's expiry time. -@@ -1040,8 +1098,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - */ - arm_timer(timer, p); - unlock_task_sighand(p, &flags); --out: -- rcu_read_unlock(); - } - - /** --- -2.53.0 - diff --git a/queue-6.12/series b/queue-6.12/series index f9017c1670..dabd02514d 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -33,7 +33,6 @@ xprtrdma-clear-receive-side-ownership-pointers-on-re.patch input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch input-ims-pcu-fix-logic-error-in-packet-reset.patch soc-qcom-ice-allow-explicit-votes-on-iface-clock-for.patch -posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch arm64-tegra-fix-cpu-compatible-string-to-cortex-a78a.patch firmware-arm_ffa-respect-firmware-advertised-rx-tx-b.patch ib-mad-drop-unmatched-rmpp-responses-before-reassemb.patch diff --git a/queue-6.18/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-6.18/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch deleted file mode 100644 index 2fb0cdbd65..0000000000 --- a/queue-6.18/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +++ /dev/null @@ -1,419 +0,0 @@ -From 0ad28e2e40b0d6eb34c62a357edc992edf33f338 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Fri, 3 Jul 2026 12:02:38 +0200 -Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race - -From: Thomas Gleixner - -commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. - -Wongi and Jungwoo decoded and reported a non-leader exec() related race -which can result in an UAF: - - sys_timer_delete() exec() - posix_cpu_timer_del() - // Observes old leader - p = pid_task(pid, pid_type); de_thread() - switch_leader(); - release_task(old_leader) - __exit_signal(old_leader) - sighand = lock(old_leader, sighand); - posix_cpu_timers*_exit(); - sighand = lock_task_sighand(p) unhash_task(old_leader); - sh = lock(p, sighand) old_leader->sighand = NULL; - unlock(sighand); - (p->sighand == NULL) - unlock(sh) - return NULL; - - // Returns without action - if(!sighand) - return 0; - free_posix_timer(); - -This is "harmless" unless the deleted timer was armed and enqueued in -p->signal because on exec() a TGID targeted timer is inherited. - -As sys_timer_delete() freed the underlying posix timer object -run_posix_cpu_timers() or any timerqueue related add/delete operations on -other timers will access the freed object's timerqueue node, which results -in an UAF. - -There is a similar problem vs. posix_cpu_timer_set(). For regular posix -timers it just transiently returns -ESRCH to user space, but for the use -case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is -allocated on the stack. - -Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops -to expire. - -While debating solutions Frederic pointed out another problem: - - posix_cpu_timer_del(tmr) - __exit_signal(p) - posix_cpu_timers*_exit(p); - unhash_task(p); - p->sighand = NULL; - sh = lock_task_sighand(p) - sighand = p->sighand; - if (!sighand) - return NULL; - lock(sighand); - - if (!sh) - WARN_ON_ONCE(timer_queued(tmr)); - -On weakly ordered architectures it is not guaranteed that -posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() -when p->sighand is observed as NULL, which means the WARN() can be a false -positive. - -Solve these issues by: - - 1) Changing the store in __exit_signal() to smp_store_release(). - - 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path - of lock_task_sighand(). - - 3) Creating a helper function for looking up the task and locking sighand - which does not return when sighand == NULL. Instead it retries the - task lookup and only if that fails it gives up. - - 4) Using that helper in the three affected functions. - -observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() -and the ones in unhash_task(). - -gracefully. When the task lookup returns the old leader, but sighand == -NULL then it retries. In the non-leader exec() case the subsequent task -lookup will observe the new leader due to #1/#2. In normal exit() scenarios -the subsequent lookup fails. - -When the task lookup fails, the function also checks whether the timer is -still enqueued and issues a warning if that's the case. Unfortunately there -is nothing which can be done about it, but as the task is already not -longer visible the timer should not be accessed anymore. This check also -requires memory ordering, which is not provided when the first lookup -fails. To achieve that the check is preceeded by a smp_rmb() which pairs -with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that -the stores in posix_cpu_timers*_exit() are visible. - -The history of the non-leader exec() issue goes back to the early days of -posix CPU timers, which stored a pointer to the group leader task in the -timer. That obviously fails when a non-leader exec() switches the leader. -commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems -with mt exec") added a temporary workaround for that in 2010 which survived -about 10 years. The fix for the workaround changed the task pointer to a -pid pointer, but failed to see the subtle race described above. So the -Fixes tag picks that commit, which seems to be halfways accurate. - -Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for -review, feedback and suggestions and to Wongi and Jungwoo for the excellent -bug report and analysis! - -Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") -Reported-by: Wongi Lee -Reported-by: Jungwoo Lee -Signed-off-by: Thomas Gleixner -Reviewed-by: Oleg Nesterov -Cc: stable@vger.kernel.org -Signed-off-by: Salvatore Bonaccorso -Signed-off-by: Greg Kroah-Hartman -Signed-off-by: Sasha Levin ---- - kernel/exit.c | 7 +- - kernel/signal.c | 10 +- - kernel/time/posix-cpu-timers.c | 173 ++++++++++++++++++++++----------- - 3 files changed, 130 insertions(+), 60 deletions(-) - -diff --git a/kernel/exit.c b/kernel/exit.c -index c832946823f488..a32bc65569a508 100644 ---- a/kernel/exit.c -+++ b/kernel/exit.c -@@ -212,7 +212,12 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts - __unhash_process(post, tsk, group_dead); - write_sequnlock(&sig->stats_lock); - -- tsk->sighand = NULL; -+ /* -+ * Ensure that all preceeding state is visible. Pairs with -+ * the smp_acquire__after_ctrl_dep() in the sighand == NULL -+ * path of lock_task_sighand(). -+ */ -+ smp_store_release(&tsk->sighand, NULL); - spin_unlock(&sighand->siglock); - - __cleanup_sighand(sighand); -diff --git a/kernel/signal.c b/kernel/signal.c -index 810098300ecdcb..e1843b71efff13 100644 ---- a/kernel/signal.c -+++ b/kernel/signal.c -@@ -1364,8 +1364,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, - rcu_read_lock(); - for (;;) { - sighand = rcu_dereference(tsk->sighand); -- if (unlikely(sighand == NULL)) -+ if (unlikely(sighand == NULL)) { -+ /* -+ * Pairs with the smp_store_release() in -+ * __exit_signal(). It ensures that all state -+ * modifications to the task preceeding the store are -+ * visible to the callers of lock_task_sighand(). -+ */ -+ smp_acquire__after_ctrl_dep(); - break; -+ } - - /* - * This sighand can be already freed and even reused, but -diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c -index d14523fbc146cd..2d64972fd0804b 100644 ---- a/kernel/time/posix-cpu-timers.c -+++ b/kernel/time/posix-cpu-timers.c -@@ -461,6 +461,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - trigger_base_recalc_expires(timer, p); - } - -+/* -+ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. -+ * -+ * This can race with the reaping of the task: -+ * -+ * CPU0 CPU1 -+ * -+ * // Finds task -+ * p = pid_task(pid, pid_type); __exit_signal(p) -+ * lock(p, sighand); -+ * posix_cpu_timers*_exit(); -+ * sighand = lock_task_sighand(p); unhash_task(p); -+ * p->sighand = NULL; -+ * unlock(sighand); -+ * -+ * In this case sighand is NULL, which means the task and the associated timer -+ * queue cannot be longer accessed safely. -+ * -+ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is -+ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete -+ * all pending timers from the related timer queues. The POSIX timers (k_itimer) -+ * themself are still accessible, but not longer connected to the task. -+ * -+ * exec() works slightly differently. The task which exec()'s terminates all -+ * other threads in the thread group and runs __exit_signal() on them. As the -+ * thread group is not dead they only clean up the per task timers via -+ * posix_cpu_timers_exit(). -+ * -+ * As the TGID on exec() stays the same per process timers stay queued, if they -+ * are armed. This works without a problem when exec() is done by the thread -+ * group leader. If a non-leader thread exec()'s this can end up in the -+ * following scenario: -+ * -+ * CPU0 CPU1 -+ * // Returns old leader -+ * p = pid_task(pid, pid_type); de_thread() -+ * switch_leader() -+ * release_task(old leader) -+ * __exit_signal() -+ * old_leader->sighand = NULL; -+ * // Returns NULL -+ * sighand = lock_task_sighand(p) -+ * -+ * That's problematic for several functions: -+ * -+ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the -+ * underlying k_itimer will be freed which results in a UAF in -+ * run_posix_cpu_timers() or on timerqueue related add/delete operations. -+ * If the timer is not enqueued, the failure is harmless -+ * -+ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a -+ * transient failure which is user space visible (-ESRCH) for regular posix -+ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF -+ * problem just that the timer is allocated on the stack. -+ * -+ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this -+ * silently ignores the rearm request, which is a functional problem as the -+ * timer wont expire anymore. -+ */ -+static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) -+{ -+ enum pid_type type = clock_pid_type(timer->it_clock); -+ struct cpu_timer *ctmr = &timer->it.cpu; -+ -+ guard(rcu)(); -+ -+ for (;;) { -+ struct task_struct *t = pid_task(timer->it.cpu.pid, type); -+ -+ /* Fail if the task cannot be found. */ -+ if (!t) -+ break; -+ -+ /* Try to lock the task's sighand */ -+ if (lock_task_sighand(t, flags)) -+ return t; -+ -+ /* -+ * The next PID lookup might either fail or return the new -+ * leader. This is correct for both exit() and exec(). -+ */ -+ } -+ -+ /* -+ * If the timer is still enqueued, warn. There is nothing safe to do -+ * here as there might be two timers in there which are removed in -+ * parallel and that will cause more damage than good. This should never -+ * happen! -+ * -+ * Ensure that the stores to the timer and timerqueue are visible: -+ * -+ * __exit_signal() -+ * posix_cpu_timers*_exit() -+ * write_seqlock(seqlock) -+ * smp_wmb(); <------- -+ * __unhash_process() | !pid_task() -+ * ----> smp_rmb(); -+ * WARN_ON_ONCE(...) -+ */ -+ smp_rmb(); -+ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -+ return NULL; -+} - - /* - * Clean up a CPU-clock timer that is about to be destroyed. -@@ -470,29 +573,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - */ - static int posix_cpu_timer_del(struct k_itimer *timer) - { -- struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -+ p = timer_lock_sighand(timer, &flags); - -- /* -- * Protect against sighand release/switch in exit/exec and process/ -- * thread timer list entry concurrent read/writes. -- */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) { -- /* -- * This raced with the reaping of the task. The exit cleanup -- * should have removed this timer from the timer queue. -- */ -- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -- } else { -+ if (likely(p)) { - if (timer->it.cpu.firing) { - /* - * Prevent signal delivery. The timer cannot be dequeued -@@ -508,11 +595,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) - unlock_task_sighand(p, &flags); - } - --out: -- rcu_read_unlock(); -- - if (!ret) { -- put_pid(ctmr->pid); -+ put_pid(timer->it.cpu.pid); - timer->it_status = POSIX_TIMER_DISARMED; - } - return ret; -@@ -626,21 +710,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - struct cpu_timer *ctmr = &timer->it.cpu; - u64 old_expires, new_expires, now; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) { -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- rcu_read_unlock(); -+ p = timer_lock_sighand(timer, &flags); -+ /* -+ * If p has just been reaped, we can no longer get any information about -+ * it at all. -+ */ -+ if (!p) - return -ESRCH; -- } - - /* - * Use the to_ktime conversion because that clamps the maximum -@@ -648,20 +728,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - */ - new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); - -- /* -- * Protect against sighand release/switch in exit/exec and p->cpu_timers -- * and p->signal->cpu_timers read/write in arm_timer() -- */ -- sighand = lock_task_sighand(p, &flags); -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- if (unlikely(sighand == NULL)) { -- rcu_read_unlock(); -- return -ESRCH; -- } -- - /* Retrieve the current expiry time before disarming the timer */ - old_expires = cpu_timer_getexpires(ctmr); - -@@ -698,7 +764,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - /* Retry if the timer expiry is running concurrently */ - if (unlikely(ret)) { - unlock_task_sighand(p, &flags); -- goto out; -+ return ret; - } - - /* Convert relative expiry time to absolute */ -@@ -733,8 +799,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - */ - if (!sigev_none && new_expires && now >= new_expires) - cpu_timer_fire(timer); --out: -- rcu_read_unlock(); - return ret; - } - -@@ -1018,19 +1082,12 @@ static void check_process_timers(struct task_struct *tsk, - static bool posix_cpu_timer_rearm(struct k_itimer *timer) - { - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - u64 now; - -- guard(rcu)(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- return true; -- -- /* Protect timer list r/w in arm_timer() */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) -+ p = timer_lock_sighand(timer, &flags); -+ if (unlikely(!p)) - return true; - - /* --- -2.53.0 - diff --git a/queue-6.18/posix-timers-expand-timer_-re-arm-callbacks-with-a-b.patch b/queue-6.18/posix-timers-expand-timer_-re-arm-callbacks-with-a-b.patch deleted file mode 100644 index bbd9307ed9..0000000000 --- a/queue-6.18/posix-timers-expand-timer_-re-arm-callbacks-with-a-b.patch +++ /dev/null @@ -1,183 +0,0 @@ -From a28e4c85d2a7b518fc6c697fe2bd2e8caebb00e3 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Wed, 8 Apr 2026 13:53:56 +0200 -Subject: posix-timers: Expand timer_[re]arm() callbacks with a boolean return - value - -From: Thomas Gleixner - -commit 6fdb2677a594ab38eade927919bbd4d9688bfa1c upstream. - -In order to catch expiry times which are already in the past the -timer_arm() and timer_rearm() callbacks need to be able to report back to -the caller whether the timer has been queued or not. - -Change the function signature and let all implementations return true for -now. While at it simplify posix_cpu_timer_rearm(). - -No functional change intended. - -Signed-off-by: Thomas Gleixner -Reviewed-by: Frederic Weisbecker -Acked-by: Peter Zijlstra (Intel) -Acked-by: John Stultz -Link: https://patch.msgid.link/20260408114952.130222296@kernel.org -Signed-off-by: Salvatore Bonaccorso -Signed-off-by: Greg Kroah-Hartman -Signed-off-by: Sasha Levin ---- - kernel/time/alarmtimer.c | 6 ++++-- - kernel/time/posix-cpu-timers.c | 18 ++++++++++-------- - kernel/time/posix-timers.c | 6 ++++-- - kernel/time/posix-timers.h | 4 ++-- - 4 files changed, 20 insertions(+), 14 deletions(-) - -diff --git a/kernel/time/alarmtimer.c b/kernel/time/alarmtimer.c -index 189ee8bbc3e650..525df334b15a79 100644 ---- a/kernel/time/alarmtimer.c -+++ b/kernel/time/alarmtimer.c -@@ -521,12 +521,13 @@ static void alarm_handle_timer(struct alarm *alarm, ktime_t now) - * alarm_timer_rearm - Posix timer callback for rearming timer - * @timr: Pointer to the posixtimer data struct - */ --static void alarm_timer_rearm(struct k_itimer *timr) -+static bool alarm_timer_rearm(struct k_itimer *timr) - { - struct alarm *alarm = &timr->it.alarm.alarmtimer; - - timr->it_overrun += alarm_forward_now(alarm, timr->it_interval); - alarm_start(alarm, alarm->node.expires); -+ return true; - } - - /** -@@ -582,7 +583,7 @@ static void alarm_timer_wait_running(struct k_itimer *timr) - * @absolute: Expiry value is absolute time - * @sigev_none: Posix timer does not deliver signals - */ --static void alarm_timer_arm(struct k_itimer *timr, ktime_t expires, -+static bool alarm_timer_arm(struct k_itimer *timr, ktime_t expires, - bool absolute, bool sigev_none) - { - struct alarm *alarm = &timr->it.alarm.alarmtimer; -@@ -594,6 +595,7 @@ static void alarm_timer_arm(struct k_itimer *timr, ktime_t expires, - alarm->node.expires = expires; - else - alarm_start(&timr->it.alarm.alarmtimer, expires); -+ return true; - } - - /** -diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c -index 99affd9e228ec5..d14523fbc146cd 100644 ---- a/kernel/time/posix-cpu-timers.c -+++ b/kernel/time/posix-cpu-timers.c -@@ -19,7 +19,7 @@ - - #include "posix-timers.h" - --static void posix_cpu_timer_rearm(struct k_itimer *timer); -+static bool posix_cpu_timer_rearm(struct k_itimer *timer); - - void posix_cputimers_group_init(struct posix_cputimers *pct, u64 cpu_limit) - { -@@ -1011,24 +1011,27 @@ static void check_process_timers(struct task_struct *tsk, - /* - * This is called from the signal code (via posixtimer_rearm) - * when the last timer signal was delivered and we have to reload the timer. -+ * -+ * Return true unconditionally so the core code assumes the timer to be -+ * armed. Otherwise it would requeue the signal. - */ --static void posix_cpu_timer_rearm(struct k_itimer *timer) -+static bool posix_cpu_timer_rearm(struct k_itimer *timer) - { - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); -- struct task_struct *p; - struct sighand_struct *sighand; -+ struct task_struct *p; - unsigned long flags; - u64 now; - -- rcu_read_lock(); -+ guard(rcu)(); - p = cpu_timer_task_rcu(timer); - if (!p) -- goto out; -+ return true; - - /* Protect timer list r/w in arm_timer() */ - sighand = lock_task_sighand(p, &flags); - if (unlikely(sighand == NULL)) -- goto out; -+ return true; - - /* - * Fetch the current sample and update the timer's expiry time. -@@ -1045,8 +1048,7 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - */ - arm_timer(timer, p); - unlock_task_sighand(p, &flags); --out: -- rcu_read_unlock(); -+ return true; - } - - /** -diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c -index 56e17b625c72e8..3082129d836c24 100644 ---- a/kernel/time/posix-timers.c -+++ b/kernel/time/posix-timers.c -@@ -295,12 +295,13 @@ static inline int timer_overrun_to_int(struct k_itimer *timr) - return (int)timr->it_overrun_last; - } - --static void common_hrtimer_rearm(struct k_itimer *timr) -+static bool common_hrtimer_rearm(struct k_itimer *timr) - { - struct hrtimer *timer = &timr->it.real.timer; - - timr->it_overrun += hrtimer_forward_now(timer, timr->it_interval); - hrtimer_restart(timer); -+ return true; - } - - static bool __posixtimer_deliver_signal(struct kernel_siginfo *info, struct k_itimer *timr) -@@ -802,7 +803,7 @@ SYSCALL_DEFINE1(timer_getoverrun, timer_t, timer_id) - return timer_overrun_to_int(scoped_timer); - } - --static void common_hrtimer_arm(struct k_itimer *timr, ktime_t expires, -+static bool common_hrtimer_arm(struct k_itimer *timr, ktime_t expires, - bool absolute, bool sigev_none) - { - struct hrtimer *timer = &timr->it.real.timer; -@@ -829,6 +830,7 @@ static void common_hrtimer_arm(struct k_itimer *timr, ktime_t expires, - - if (!sigev_none) - hrtimer_start_expires(timer, HRTIMER_MODE_ABS); -+ return true; - } - - static int common_hrtimer_try_to_cancel(struct k_itimer *timr) -diff --git a/kernel/time/posix-timers.h b/kernel/time/posix-timers.h -index 7f259e845d240c..4ea9611dd7165c 100644 ---- a/kernel/time/posix-timers.h -+++ b/kernel/time/posix-timers.h -@@ -27,11 +27,11 @@ struct k_clock { - int (*timer_del)(struct k_itimer *timr); - void (*timer_get)(struct k_itimer *timr, - struct itimerspec64 *cur_setting); -- void (*timer_rearm)(struct k_itimer *timr); -+ bool (*timer_rearm)(struct k_itimer *timr); - s64 (*timer_forward)(struct k_itimer *timr, ktime_t now); - ktime_t (*timer_remaining)(struct k_itimer *timr, ktime_t now); - int (*timer_try_to_cancel)(struct k_itimer *timr); -- void (*timer_arm)(struct k_itimer *timr, ktime_t expires, -+ bool (*timer_arm)(struct k_itimer *timr, ktime_t expires, - bool absolute, bool sigev_none); - void (*timer_wait_running)(struct k_itimer *timr); - }; --- -2.53.0 - diff --git a/queue-6.18/series b/queue-6.18/series index 1d45ec6d6c..8846b64317 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -1,5 +1,3 @@ -posix-timers-expand-timer_-re-arm-callbacks-with-a-b.patch -posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch platform-x86-intel-uncore-freq-fix-current_freq_khz-.patch bpf-fix-ld_-abs-ind-failure-path-analysis-in-subprog.patch selftests-bpf-add-tests-for-ld_-abs-ind-failure-path.patch diff --git a/queue-6.6/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-6.6/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch deleted file mode 100644 index 402452380d..0000000000 --- a/queue-6.6/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +++ /dev/null @@ -1,486 +0,0 @@ -From df66f7b592d359455c4054a628f3ea06b1b3d806 Mon Sep 17 00:00:00 2001 -From: Sasha Levin -Date: Mon, 27 Jul 2026 11:10:14 +0200 -Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race - -From: Thomas Gleixner - -commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. - -Wongi and Jungwoo decoded and reported a non-leader exec() related race -which can result in an UAF: - - sys_timer_delete() exec() - posix_cpu_timer_del() - // Observes old leader - p = pid_task(pid, pid_type); de_thread() - switch_leader(); - release_task(old_leader) - __exit_signal(old_leader) - sighand = lock(old_leader, sighand); - posix_cpu_timers*_exit(); - sighand = lock_task_sighand(p) unhash_task(old_leader); - sh = lock(p, sighand) old_leader->sighand = NULL; - unlock(sighand); - (p->sighand == NULL) - unlock(sh) - return NULL; - - // Returns without action - if(!sighand) - return 0; - free_posix_timer(); - -This is "harmless" unless the deleted timer was armed and enqueued in -p->signal because on exec() a TGID targeted timer is inherited. - -As sys_timer_delete() freed the underlying posix timer object -run_posix_cpu_timers() or any timerqueue related add/delete operations on -other timers will access the freed object's timerqueue node, which results -in an UAF. - -There is a similar problem vs. posix_cpu_timer_set(). For regular posix -timers it just transiently returns -ESRCH to user space, but for the use -case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is -allocated on the stack. - -Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops -to expire. - -While debating solutions Frederic pointed out another problem: - - posix_cpu_timer_del(tmr) - __exit_signal(p) - posix_cpu_timers*_exit(p); - unhash_task(p); - p->sighand = NULL; - sh = lock_task_sighand(p) - sighand = p->sighand; - if (!sighand) - return NULL; - lock(sighand); - - if (!sh) - WARN_ON_ONCE(timer_queued(tmr)); - -On weakly ordered architectures it is not guaranteed that -posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() -when p->sighand is observed as NULL, which means the WARN() can be a false -positive. - -Solve these issues by: - - 1) Changing the store in __exit_signal() to smp_store_release(). - - 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path - of lock_task_sighand(). - - 3) Creating a helper function for looking up the task and locking sighand - which does not return when sighand == NULL. Instead it retries the - task lookup and only if that fails it gives up. - - 4) Using that helper in the three affected functions. - -#1/#2 ensures that the reader side which observes sighand == NULL also -observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() -and the ones in unhash_task(). - -#3 ensures that the above described non-leader exec() situation is handled -gracefully. When the task lookup returns the old leader, but sighand == -NULL then it retries. In the non-leader exec() case the subsequent task -lookup will observe the new leader due to #1/#2. In normal exit() scenarios -the subsequent lookup fails. - -When the task lookup fails, the function also checks whether the timer is -still enqueued and issues a warning if that's the case. Unfortunately there -is nothing which can be done about it, but as the task is already not -longer visible the timer should not be accessed anymore. This check also -requires memory ordering, which is not provided when the first lookup -fails. To achieve that the check is preceeded by a smp_rmb() which pairs -with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that -the stores in posix_cpu_timers*_exit() are visible. - -The history of the non-leader exec() issue goes back to the early days of -posix CPU timers, which stored a pointer to the group leader task in the -timer. That obviously fails when a non-leader exec() switches the leader. -commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems -with mt exec") added a temporary workaround for that in 2010 which survived -about 10 years. The fix for the workaround changed the task pointer to a -pid pointer, but failed to see the subtle race described above. So the -Fixes tag picks that commit, which seems to be halfways accurate. - -Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for -review, feedback and suggestions and to Wongi and Jungwoo for the excellent -bug report and analysis! - -Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") -Reported-by: Wongi Lee -Reported-by: Jungwoo Lee -Signed-off-by: Thomas Gleixner -Reviewed-by: Oleg Nesterov -Cc: stable@vger.kernel.org -Signed-off-by: Sasha Levin ---- - kernel/exit.c | 8 +- - kernel/signal.c | 10 +- - kernel/time/posix-cpu-timers.c | 212 ++++++++++++++++++++------------- - 3 files changed, 143 insertions(+), 87 deletions(-) - -diff --git a/kernel/exit.c b/kernel/exit.c -index 5ebe01e8f37e15..55a8aa868298f0 100644 ---- a/kernel/exit.c -+++ b/kernel/exit.c -@@ -204,7 +204,13 @@ static void __exit_signal(struct task_struct *tsk) - * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. - */ - flush_sigqueue(&tsk->pending); -- tsk->sighand = NULL; -+ -+ /* -+ * Ensure that all preceeding state is visible. Pairs with -+ * the smp_acquire__after_ctrl_dep() in the sighand == NULL -+ * path of lock_task_sighand(). -+ */ -+ smp_store_release(&tsk->sighand, NULL); - spin_unlock(&sighand->siglock); - - __cleanup_sighand(sighand); -diff --git a/kernel/signal.c b/kernel/signal.c -index 3a484ea4bab658..9fe386e6badfb0 100644 ---- a/kernel/signal.c -+++ b/kernel/signal.c -@@ -1408,8 +1408,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, - rcu_read_lock(); - for (;;) { - sighand = rcu_dereference(tsk->sighand); -- if (unlikely(sighand == NULL)) -+ if (unlikely(sighand == NULL)) { -+ /* -+ * Pairs with the smp_store_release() in -+ * __exit_signal(). It ensures that all state -+ * modifications to the task preceeding the store are -+ * visible to the callers of lock_task_sighand(). -+ */ -+ smp_acquire__after_ctrl_dep(); - break; -+ } - - /* - * This sighand can be already freed and even reused, but -diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c -index b99c386c50e432..fa730130fe154b 100644 ---- a/kernel/time/posix-cpu-timers.c -+++ b/kernel/time/posix-cpu-timers.c -@@ -461,6 +461,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - trigger_base_recalc_expires(timer, p); - } - -+/* -+ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. -+ * -+ * This can race with the reaping of the task: -+ * -+ * CPU0 CPU1 -+ * -+ * // Finds task -+ * p = pid_task(pid, pid_type); __exit_signal(p) -+ * lock(p, sighand); -+ * posix_cpu_timers*_exit(); -+ * sighand = lock_task_sighand(p); unhash_task(p); -+ * p->sighand = NULL; -+ * unlock(sighand); -+ * -+ * In this case sighand is NULL, which means the task and the associated timer -+ * queue cannot be longer accessed safely. -+ * -+ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is -+ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete -+ * all pending timers from the related timer queues. The POSIX timers (k_itimer) -+ * themself are still accessible, but not longer connected to the task. -+ * -+ * exec() works slightly differently. The task which exec()'s terminates all -+ * other threads in the thread group and runs __exit_signal() on them. As the -+ * thread group is not dead they only clean up the per task timers via -+ * posix_cpu_timers_exit(). -+ * -+ * As the TGID on exec() stays the same per process timers stay queued, if they -+ * are armed. This works without a problem when exec() is done by the thread -+ * group leader. If a non-leader thread exec()'s this can end up in the -+ * following scenario: -+ * -+ * CPU0 CPU1 -+ * // Returns old leader -+ * p = pid_task(pid, pid_type); de_thread() -+ * switch_leader() -+ * release_task(old leader) -+ * __exit_signal() -+ * old_leader->sighand = NULL; -+ * // Returns NULL -+ * sighand = lock_task_sighand(p) -+ * -+ * That's problematic for several functions: -+ * -+ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the -+ * underlying k_itimer will be freed which results in a UAF in -+ * run_posix_cpu_timers() or on timerqueue related add/delete operations. -+ * If the timer is not enqueued, the failure is harmless -+ * -+ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a -+ * transient failure which is user space visible (-ESRCH) for regular posix -+ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF -+ * problem just that the timer is allocated on the stack. -+ * -+ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this -+ * silently ignores the rearm request, which is a functional problem as the -+ * timer wont expire anymore. -+ */ -+static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) -+{ -+ enum pid_type type = clock_pid_type(timer->it_clock); -+ struct cpu_timer *ctmr = &timer->it.cpu; -+ -+ guard(rcu)(); -+ -+ for (;;) { -+ struct task_struct *t = pid_task(timer->it.cpu.pid, type); -+ -+ /* Fail if the task cannot be found. */ -+ if (!t) -+ break; -+ -+ /* Try to lock the task's sighand */ -+ if (lock_task_sighand(t, flags)) -+ return t; -+ -+ /* -+ * The next PID lookup might either fail or return the new -+ * leader. This is correct for both exit() and exec(). -+ */ -+ } -+ -+ /* -+ * If the timer is still enqueued, warn. There is nothing safe to do -+ * here as there might be two timers in there which are removed in -+ * parallel and that will cause more damage than good. This should never -+ * happen! -+ * -+ * Ensure that the stores to the timer and timerqueue are visible: -+ * -+ * __exit_signal() -+ * posix_cpu_timers*_exit() -+ * write_seqlock(seqlock) -+ * smp_wmb(); <------- -+ * __unhash_process() | !pid_task() -+ * ----> smp_rmb(); -+ * WARN_ON_ONCE(...) -+ */ -+ smp_rmb(); -+ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -+ return NULL; -+} - - /* - * Clean up a CPU-clock timer that is about to be destroyed. -@@ -470,29 +573,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) - */ - static int posix_cpu_timer_del(struct k_itimer *timer) - { -- struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -+ p = timer_lock_sighand(timer, &flags); - -- /* -- * Protect against sighand release/switch in exit/exec and process/ -- * thread timer list entry concurrent read/writes. -- */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) { -- /* -- * This raced with the reaping of the task. The exit cleanup -- * should have removed this timer from the timer queue. -- */ -- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); -- } else { -+ if (likely(p)) { - if (timer->it.cpu.firing) - ret = TIMER_RETRY; - else -@@ -501,10 +588,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) - unlock_task_sighand(p, &flags); - } - --out: -- rcu_read_unlock(); - if (!ret) -- put_pid(ctmr->pid); -+ put_pid(timer->it.cpu.pid); - - return ret; - } -@@ -626,21 +711,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - u64 old_expires, new_expires, old_incr, val; - struct cpu_timer *ctmr = &timer->it.cpu; -- struct sighand_struct *sighand; - struct task_struct *p; - unsigned long flags; - int ret = 0; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) { -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- rcu_read_unlock(); -+ p = timer_lock_sighand(timer, &flags); -+ /* -+ * If p has just been reaped, we can no longer get any information about -+ * it at all. -+ */ -+ if (!p) - return -ESRCH; -- } - - /* - * Use the to_ktime conversion because that clamps the maximum -@@ -648,20 +729,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - */ - new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); - -- /* -- * Protect against sighand release/switch in exit/exec and p->cpu_timers -- * and p->signal->cpu_timers read/write in arm_timer() -- */ -- sighand = lock_task_sighand(p, &flags); -- /* -- * If p has just been reaped, we can no -- * longer get any information about it at all. -- */ -- if (unlikely(sighand == NULL)) { -- rcu_read_unlock(); -- return -ESRCH; -- } -- - /* - * Disarm any old timer after extracting its expiry time. - */ -@@ -710,6 +777,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - old->it_value.tv_sec = 0; - } - } -+ old->it_interval = ns_to_timespec64(old_incr); - } - - if (unlikely(ret)) { -@@ -720,7 +788,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - * it as an overrun (thanks to bump_cpu_timer above). - */ - unlock_task_sighand(p, &flags); -- goto out; -+ return ret; - } - - if (new_expires != 0 && !(timer_flags & TIMER_ABSTIME)) { -@@ -733,11 +801,11 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - * arm the timer (we'll just fake it for timer_gettime). - */ - cpu_timer_setexpires(ctmr, new_expires); -- if (new_expires != 0 && val < new_expires) { -+ if (new_expires != 0 && val < new_expires) - arm_timer(timer, p); -- } -+ else -+ trigger_base_recalc_expires(timer, p); - -- unlock_task_sighand(p, &flags); - /* - * Install the new reload setting, and - * set up the signal and overrun bookkeeping. -@@ -754,35 +822,18 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, - timer->it_overrun_last = 0; - timer->it_overrun = -1; - -- if (val >= new_expires) { -- if (new_expires != 0) { -- /* -- * The designated time already passed, so we notify -- * immediately, even if the thread never runs to -- * accumulate more time on this clock. -- */ -- cpu_timer_fire(timer); -- } -+ unlock_task_sighand(p, &flags); - -+ if (new_expires && val >= new_expires) { - /* -- * Make sure we don't keep around the process wide cputime -- * counter or the tick dependency if they are not necessary. -+ * The designated time already passed, so we notify immediately, -+ * even if the thread never runs to accumulate more time on this -+ * clock. - */ -- sighand = lock_task_sighand(p, &flags); -- if (!sighand) -- goto out; -- -- if (!cpu_timer_queued(ctmr)) -- trigger_base_recalc_expires(timer, p); -- -- unlock_task_sighand(p, &flags); -+ cpu_timer_fire(timer); - } -- out: -- rcu_read_unlock(); -- if (old) -- old->it_interval = ns_to_timespec64(old_incr); - -- return ret; -+ return 0; - } - - static void posix_cpu_timer_get(struct k_itimer *timer, struct itimerspec64 *itp) -@@ -1048,19 +1099,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - { - clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - struct task_struct *p; -- struct sighand_struct *sighand; - unsigned long flags; - u64 now; - -- rcu_read_lock(); -- p = cpu_timer_task_rcu(timer); -- if (!p) -- goto out; -- -- /* Protect timer list r/w in arm_timer() */ -- sighand = lock_task_sighand(p, &flags); -- if (unlikely(sighand == NULL)) -- goto out; -+ p = timer_lock_sighand(timer, &flags); -+ if (unlikely(!p)) -+ return; - - /* - * Fetch the current sample and update the timer's expiry time. -@@ -1077,8 +1121,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) - */ - arm_timer(timer, p); - unlock_task_sighand(p, &flags); --out: -- rcu_read_unlock(); - } - - /** --- -2.53.0 - diff --git a/queue-6.6/series b/queue-6.6/series index b836ba01ac..a5afa24d6f 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -108,7 +108,6 @@ usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch -posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch risc-v-kvm-serialize-virtual-interrupt-pending-state.patch