From: Greg Kroah-Hartman Date: Thu, 30 Jul 2026 10:00:43 +0000 (+0200) Subject: add some patches back into the queues... X-Git-Tag: v5.10.262~1 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=36399a39df7e787dfe4709ea332be1ff10c66005;p=thirdparty%2Fkernel%2Fstable-queue.git add some patches back into the queues... --- 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 new file mode 100644 index 0000000000..780d9ac7fa --- /dev/null +++ b/queue-5.10/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,449 @@ +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(-) + +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -200,7 +200,13 @@ static void __exit_signal(struct task_st + * 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); +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1375,8 +1375,16 @@ struct sighand_struct *__lock_task_sigha + 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 +--- a/kernel/time/posix-cpu-timers.c ++++ b/kernel/time/posix-cpu-timers.c +@@ -406,6 +406,110 @@ static int posix_cpu_timer_create(struct + } + + /* ++ * 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. + * If we return TIMER_RETRY, it's necessary to release the timer's lock +@@ -414,28 +518,13 @@ static int posix_cpu_timer_create(struct + 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_ + 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_ + 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 +@@ -598,20 +681,6 @@ static int posix_cpu_timer_set(struct k_ + 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. + */ + old_incr = timer->it_interval; +@@ -659,6 +728,8 @@ static int posix_cpu_timer_set(struct k_ + 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_ + * 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_ + 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_ + 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_ + 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 + { + 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 + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** diff --git a/queue-5.10/series b/queue-5.10/series new file mode 100644 index 0000000000..98ac9690f6 --- /dev/null +++ b/queue-5.10/series @@ -0,0 +1 @@ +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.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 new file mode 100644 index 0000000000..bdf9e10d4a --- /dev/null +++ b/queue-5.15/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,477 @@ +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(-) + +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -200,7 +200,13 @@ static void __exit_signal(struct task_st + * 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); +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1388,8 +1388,16 @@ struct sighand_struct *__lock_task_sigha + 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 +--- 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 + 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 + */ + 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_ + 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_ + 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 +@@ -643,20 +724,6 @@ static int posix_cpu_timer_set(struct k_ + 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. + */ + old_incr = timer->it_interval; +@@ -704,6 +771,7 @@ static int posix_cpu_timer_set(struct k_ + 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_ + * 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_ + * 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_ + 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 + { + 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 + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** diff --git a/queue-5.15/series b/queue-5.15/series new file mode 100644 index 0000000000..98ac9690f6 --- /dev/null +++ b/queue-5.15/series @@ -0,0 +1 @@ +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.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 new file mode 100644 index 0000000000..b2a4fa0bd4 --- /dev/null +++ b/queue-6.1/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,477 @@ +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(-) + +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -203,7 +203,13 @@ static void __exit_signal(struct task_st + * 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); +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1395,8 +1395,16 @@ struct sighand_struct *__lock_task_sigha + 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 +--- 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 + 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 + */ + 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_ + 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_ + 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 +@@ -650,20 +731,6 @@ static int posix_cpu_timer_set(struct k_ + 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. + */ + old_incr = timer->it_interval; +@@ -711,6 +778,7 @@ static int posix_cpu_timer_set(struct k_ + 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_ + * 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_ + * 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_ + 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 + { + 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 + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** diff --git a/queue-6.1/series b/queue-6.1/series new file mode 100644 index 0000000000..98ac9690f6 --- /dev/null +++ b/queue-6.1/series @@ -0,0 +1 @@ +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.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 new file mode 100644 index 0000000000..1cbdae9f44 --- /dev/null +++ b/queue-6.12/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,420 @@ +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(-) + +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -205,7 +205,13 @@ static void __exit_signal(struct task_st + * 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); +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1395,8 +1395,16 @@ struct sighand_struct *__lock_task_sigha + 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 +--- 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 + 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 + */ + 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_ + 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_ + 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_ + */ + 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_ + /* 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_ + */ + 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 + { + 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 + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** diff --git a/queue-6.12/series b/queue-6.12/series new file mode 100644 index 0000000000..98ac9690f6 --- /dev/null +++ b/queue-6.12/series @@ -0,0 +1 @@ +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.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 new file mode 100644 index 0000000000..736b89fa97 --- /dev/null +++ b/queue-6.18/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,410 @@ +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(-) + +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -212,7 +212,12 @@ static void __exit_signal(struct release + __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); +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1364,8 +1364,16 @@ struct sighand_struct *__lock_task_sigha + 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 +--- 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 + 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 + */ + 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_ + 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_ + 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_ + */ + 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_ + /* 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_ + */ + 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 + 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; + + /* 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 new file mode 100644 index 0000000000..b3f5103300 --- /dev/null +++ b/queue-6.18/posix-timers-expand-timer_-re-arm-callbacks-with-a-b.patch @@ -0,0 +1,172 @@ +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(-) + +--- a/kernel/time/alarmtimer.c ++++ b/kernel/time/alarmtimer.c +@@ -521,12 +521,13 @@ static void alarm_handle_timer(struct al + * 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(str + * @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_iti + alarm->node.expires = expires; + else + alarm_start(&timr->it.alarm.alarmtimer, expires); ++ return true; + } + + /** +--- 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 + /* + * 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 + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); ++ return true; + } + + /** +--- a/kernel/time/posix-timers.c ++++ b/kernel/time/posix-timers.c +@@ -295,12 +295,13 @@ static inline int timer_overrun_to_int(s + 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_ + 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_ + + if (!sigev_none) + hrtimer_start_expires(timer, HRTIMER_MODE_ABS); ++ return true; + } + + static int common_hrtimer_try_to_cancel(struct k_itimer *timr) +--- 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); + }; diff --git a/queue-6.18/series b/queue-6.18/series new file mode 100644 index 0000000000..1485f2e958 --- /dev/null +++ b/queue-6.18/series @@ -0,0 +1,2 @@ +posix-timers-expand-timer_-re-arm-callbacks-with-a-b.patch +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.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 new file mode 100644 index 0000000000..402452380d --- /dev/null +++ b/queue-6.6/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,486 @@ +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 new file mode 100644 index 0000000000..98ac9690f6 --- /dev/null +++ b/queue-6.6/series @@ -0,0 +1 @@ +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch