mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-16 17:57:38 -04:00
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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user