Merge tag 'timers-urgent-2026-07-11' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull timer fix from Ingo Molnar:

 - Fix a subtle posix-cpu-timers vs. exec() race, which
   unearthed other races in the area (Thomas Gleixner)

* tag 'timers-urgent-2026-07-11' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  posix-cpu-timers: Prevent UAF caused by non-leader exec() race
This commit is contained in:
Linus Torvalds
2026-07-11 09:54:05 -07:00
3 changed files with 130 additions and 60 deletions

View File

@@ -212,7 +212,12 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts
__unhash_process(post, tsk, group_dead);
write_sequnlock(&sig->stats_lock);
tsk->sighand = NULL;
/*
* Ensure that all preceeding state is visible. Pairs with
* the smp_acquire__after_ctrl_dep() in the sighand == NULL
* path of lock_task_sighand().
*/
smp_store_release(&tsk->sighand, NULL);
spin_unlock(&sighand->siglock);
__cleanup_sighand(sighand);

View File

@@ -1362,8 +1362,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

View File

@@ -461,6 +461,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p)
trigger_base_recalc_expires(timer, p);
}
/*
* Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand.
*
* This can race with the reaping of the task:
*
* CPU0 CPU1
*
* // Finds task
* p = pid_task(pid, pid_type); __exit_signal(p)
* lock(p, sighand);
* posix_cpu_timers*_exit();
* sighand = lock_task_sighand(p); unhash_task(p);
* p->sighand = NULL;
* unlock(sighand);
*
* In this case sighand is NULL, which means the task and the associated timer
* queue cannot be longer accessed safely.
*
* __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is
* dead it also invokes posix_cpu_timers_group_exit(). These functions delete
* all pending timers from the related timer queues. The POSIX timers (k_itimer)
* themself are still accessible, but not longer connected to the task.
*
* exec() works slightly differently. The task which exec()'s terminates all
* other threads in the thread group and runs __exit_signal() on them. As the
* thread group is not dead they only clean up the per task timers via
* posix_cpu_timers_exit().
*
* As the TGID on exec() stays the same per process timers stay queued, if they
* are armed. This works without a problem when exec() is done by the thread
* group leader. If a non-leader thread exec()'s this can end up in the
* following scenario:
*
* CPU0 CPU1
* // Returns old leader
* p = pid_task(pid, pid_type); de_thread()
* switch_leader()
* release_task(old leader)
* __exit_signal()
* old_leader->sighand = NULL;
* // Returns NULL
* sighand = lock_task_sighand(p)
*
* That's problematic for several functions:
*
* - posix_cpu_timer_del(): If the timer is still enqueued on the task the
* underlying k_itimer will be freed which results in a UAF in
* run_posix_cpu_timers() or on timerqueue related add/delete operations.
* If the timer is not enqueued, the failure is harmless
*
* - posix_cpu_timer_set(): Independent of the enqueued state that results in a
* transient failure which is user space visible (-ESRCH) for regular posix
* timers. But for the use case in do_cpu_nanosleep() it's the same UAF
* problem just that the timer is allocated on the stack.
*
* - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this
* silently ignores the rearm request, which is a functional problem as the
* timer wont expire anymore.
*/
static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags)
{
enum pid_type type = clock_pid_type(timer->it_clock);
struct cpu_timer *ctmr = &timer->it.cpu;
guard(rcu)();
for (;;) {
struct task_struct *t = pid_task(timer->it.cpu.pid, type);
/* Fail if the task cannot be found. */
if (!t)
break;
/* Try to lock the task's sighand */
if (lock_task_sighand(t, flags))
return t;
/*
* The next PID lookup might either fail or return the new
* leader. This is correct for both exit() and exec().
*/
}
/*
* If the timer is still enqueued, warn. There is nothing safe to do
* here as there might be two timers in there which are removed in
* parallel and that will cause more damage than good. This should never
* happen!
*
* Ensure that the stores to the timer and timerqueue are visible:
*
* __exit_signal()
* posix_cpu_timers*_exit()
* write_seqlock(seqlock)
* smp_wmb(); <-------
* __unhash_process() | !pid_task()
* ----> smp_rmb();
* WARN_ON_ONCE(...)
*/
smp_rmb();
WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node));
return NULL;
}
/*
* Clean up a CPU-clock timer that is about to be destroyed.
@@ -470,29 +573,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p)
*/
static int posix_cpu_timer_del(struct k_itimer *timer)
{
struct cpu_timer *ctmr = &timer->it.cpu;
struct sighand_struct *sighand;
struct task_struct *p;
unsigned long flags;
int ret = 0;
rcu_read_lock();
p = cpu_timer_task_rcu(timer);
if (!p)
goto out;
p = timer_lock_sighand(timer, &flags);
/*
* Protect against sighand release/switch in exit/exec and process/
* thread timer list entry concurrent read/writes.
*/
sighand = lock_task_sighand(p, &flags);
if (unlikely(sighand == NULL)) {
/*
* This raced with the reaping of the task. The exit cleanup
* should have removed this timer from the timer queue.
*/
WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node));
} else {
if (likely(p)) {
if (timer->it.cpu.firing) {
/*
* Prevent signal delivery. The timer cannot be dequeued
@@ -508,11 +595,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer)
unlock_task_sighand(p, &flags);
}
out:
rcu_read_unlock();
if (!ret) {
put_pid(ctmr->pid);
put_pid(timer->it.cpu.pid);
timer->it_status = POSIX_TIMER_DISARMED;
}
return ret;
@@ -626,21 +710,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock);
struct cpu_timer *ctmr = &timer->it.cpu;
u64 old_expires, new_expires, now;
struct sighand_struct *sighand;
struct task_struct *p;
unsigned long flags;
int ret = 0;
rcu_read_lock();
p = cpu_timer_task_rcu(timer);
if (!p) {
/*
* If p has just been reaped, we can no
* longer get any information about it at all.
*/
rcu_read_unlock();
p = timer_lock_sighand(timer, &flags);
/*
* If p has just been reaped, we can no longer get any information about
* it at all.
*/
if (!p)
return -ESRCH;
}
/*
* Use the to_ktime conversion because that clamps the maximum
@@ -648,20 +728,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
*/
new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value));
/*
* Protect against sighand release/switch in exit/exec and p->cpu_timers
* and p->signal->cpu_timers read/write in arm_timer()
*/
sighand = lock_task_sighand(p, &flags);
/*
* If p has just been reaped, we can no
* longer get any information about it at all.
*/
if (unlikely(sighand == NULL)) {
rcu_read_unlock();
return -ESRCH;
}
/* Retrieve the current expiry time before disarming the timer */
old_expires = cpu_timer_getexpires(ctmr);
@@ -698,7 +764,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
/* Retry if the timer expiry is running concurrently */
if (unlikely(ret)) {
unlock_task_sighand(p, &flags);
goto out;
return ret;
}
/* Convert relative expiry time to absolute */
@@ -733,8 +799,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
*/
if (!sigev_none && new_expires && now >= new_expires)
cpu_timer_fire(timer);
out:
rcu_read_unlock();
return ret;
}
@@ -1018,19 +1082,12 @@ static void check_process_timers(struct task_struct *tsk,
static bool posix_cpu_timer_rearm(struct k_itimer *timer)
{
clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock);
struct sighand_struct *sighand;
struct task_struct *p;
unsigned long flags;
u64 now;
guard(rcu)();
p = cpu_timer_task_rcu(timer);
if (!p)
return true;
/* Protect timer list r/w in arm_timer() */
sighand = lock_task_sighand(p, &flags);
if (unlikely(sighand == NULL))
p = timer_lock_sighand(timer, &flags);
if (unlikely(!p))
return true;
/*