]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
add some patches back into the queues...
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Jul 2026 10:00:43 +0000 (12:00 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Jul 2026 10:00:43 +0000 (12:00 +0200)
13 files changed:
queue-5.10/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch [new file with mode: 0644]
queue-5.10/series [new file with mode: 0644]
queue-5.15/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch [new file with mode: 0644]
queue-5.15/series [new file with mode: 0644]
queue-6.1/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch [new file with mode: 0644]
queue-6.1/series [new file with mode: 0644]
queue-6.12/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch [new file with mode: 0644]
queue-6.12/series [new file with mode: 0644]
queue-6.18/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch [new file with mode: 0644]
queue-6.18/posix-timers-expand-timer_-re-arm-callbacks-with-a-b.patch [new file with mode: 0644]
queue-6.18/series [new file with mode: 0644]
queue-6.6/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch [new file with mode: 0644]
queue-6.6/series [new file with mode: 0644]

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 (file)
index 0000000..780d9ac
--- /dev/null
@@ -0,0 +1,449 @@
+From aad70180e7c856978814bb3ca066df392cbfef15 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:13:08 +0200
+Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race
+
+From: Thomas Gleixner <tglx@kernel.org>
+
+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 <qw3rtyp0@gmail.com>
+Reported-by: Jungwoo Lee <jwlee2217@gmail.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Reviewed-by: Oleg Nesterov <oleg@redhat.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 (file)
index 0000000..98ac969
--- /dev/null
@@ -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 (file)
index 0000000..bdf9e10
--- /dev/null
@@ -0,0 +1,477 @@
+From 09486fef145315e0221c496ec7af5fc66c785357 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:10:14 +0200
+Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race
+
+From: Thomas Gleixner <tglx@kernel.org>
+
+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 <qw3rtyp0@gmail.com>
+Reported-by: Jungwoo Lee <jwlee2217@gmail.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Reviewed-by: Oleg Nesterov <oleg@redhat.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 (file)
index 0000000..98ac969
--- /dev/null
@@ -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 (file)
index 0000000..b2a4fa0
--- /dev/null
@@ -0,0 +1,477 @@
+From 30d9395052ea8c37d41b2f1329d6fadca3e801f7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:10:14 +0200
+Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race
+
+From: Thomas Gleixner <tglx@kernel.org>
+
+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 <qw3rtyp0@gmail.com>
+Reported-by: Jungwoo Lee <jwlee2217@gmail.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Reviewed-by: Oleg Nesterov <oleg@redhat.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 (file)
index 0000000..98ac969
--- /dev/null
@@ -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 (file)
index 0000000..1cbdae9
--- /dev/null
@@ -0,0 +1,420 @@
+From cf7b5a3c34e508b49afdda31e7e32ab0d99c7ef9 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 22:10:32 +0200
+Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race
+
+From: Thomas Gleixner <tglx@kernel.org>
+
+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 <qw3rtyp0@gmail.com>
+Reported-by: Jungwoo Lee <jwlee2217@gmail.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Reviewed-by: Oleg Nesterov <oleg@redhat.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 (file)
index 0000000..98ac969
--- /dev/null
@@ -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 (file)
index 0000000..736b89f
--- /dev/null
@@ -0,0 +1,410 @@
+From 0ad28e2e40b0d6eb34c62a357edc992edf33f338 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 3 Jul 2026 12:02:38 +0200
+Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race
+
+From: Thomas Gleixner <tglx@kernel.org>
+
+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 <qw3rtyp0@gmail.com>
+Reported-by: Jungwoo Lee <jwlee2217@gmail.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Reviewed-by: Oleg Nesterov <oleg@redhat.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Salvatore Bonaccorso <carnil@debian.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 (file)
index 0000000..b3f5103
--- /dev/null
@@ -0,0 +1,172 @@
+From a28e4c85d2a7b518fc6c697fe2bd2e8caebb00e3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+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 <tglx@kernel.org>
+
+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 <tglx@kernel.org>
+Reviewed-by: Frederic Weisbecker <frederic@kernel.org>
+Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
+Acked-by: John Stultz <jstultz@google.com>
+Link: https://patch.msgid.link/20260408114952.130222296@kernel.org
+Signed-off-by: Salvatore Bonaccorso <carnil@debian.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 (file)
index 0000000..1485f2e
--- /dev/null
@@ -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 (file)
index 0000000..4024523
--- /dev/null
@@ -0,0 +1,486 @@
+From df66f7b592d359455c4054a628f3ea06b1b3d806 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 11:10:14 +0200
+Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race
+
+From: Thomas Gleixner <tglx@kernel.org>
+
+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 <qw3rtyp0@gmail.com>
+Reported-by: Jungwoo Lee <jwlee2217@gmail.com>
+Signed-off-by: Thomas Gleixner <tglx@kernel.org>
+Reviewed-by: Oleg Nesterov <oleg@redhat.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 (file)
index 0000000..98ac969
--- /dev/null
@@ -0,0 +1 @@
+posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch