FAILED: patch "[PATCH] posix-cpu-timers: Prevent UAF caused by non-leader exec()" failed to apply to 5.15-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.15-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.15.y
git checkout FETCH_HEAD
git cherry-pick -x 920f893f735e92ba3a1cd9256899a186b161928d
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026072122-pulp-rejoin-81db@gregkh' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 920f893f735e92ba3a1cd9256899a186b161928d Mon Sep 17 00:00:00 2001
From: Thomas Gleixner <[email protected]>
Date: Fri, 3 Jul 2026 12:02:38 +0200
Subject: [PATCH] posix-cpu-timers: Prevent UAF caused by non-leader exec()
race
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!
kernel/exit.c +6 -1
@@ -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);
kernel/signal.c +9 -1
@@ -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
kernel/time/posix-cpu-timers.c +115 -58
236 lines not on this page · raw message
Thomas Gleixner
maintainerPATCH 1/1 posix-cpu-timers: Prevent UAF caused by non-leader exec()
From: Thomas Gleixner <[email protected]>
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!
--- kernel/exit.c | 8 + kernel/signal.c | 10 + kernel/time/posix-cpu-timers.c | 214 ++++++++++++++++++++++++----------------- 3 files changed, 145 insertions(+), 87 deletions(-)
---
kernel/exit.c +7 -1
@@ -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);
kernel/signal.c +9 -1
@@ -1388,8 +1388,16 @@ struct sighand_struct *__lock_task_sigharcu_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
kernel/time/posix-cpu-timers.c +129 -85
295 lines not on this page · raw message
Sasha Levin
maintainerRe: [PATCH 5.15.y, 6.1.y, 6.6.y] posix-cpu-timers: Prevent UAF caused by non-leader exec()
Thanks for doing these. I have queued the 6.12 one, but I would like to ask for a v2 of this one before taking 6.6, 6.1 and 5.15.
Quoted 7 lines
kernel/time/posix-cpu-timers.c +1
-1@@ -714,7 +781,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; }
[...]
Quoted 3 lines
kernel/time/posix-cpu-timers.c +3
-0+if (old) + old->it_interval =ns_to_timespec64 (old_incr); +
[...]
kernel/time/posix-cpu-timers.c static int posix_cpu_timer_set(struct k_ · line 777
Quoted 4 lines
- out:
kernel/time/posix-cpu-timers.c +0
-3-rcu_read_unlock (); -if (old) - old->it_interval =ns_to_timespec64 (old_incr);
On these trees that changes user-visible behaviour on the TIMER_RETRY path. Before the patch the "goto out" fell through the shared out:
label, which filled in old->it_interval; afterwards the early return skips it and the assignment only runs on the success path.
Upstream that is harmless because bfa408f03fc7 ("posix-timers: Retrieve interval in common timer_settime() code") moved the interval retrieval into the common code, but that landed in v6.12 and 6.6, 6.1 and 5.15 do not have it. Their do_timer_settime() sets old_spec64 = NULL before it goes back around the retry loop, so a timer_settime() that races timer expiry now reports old_value.it_interval == {0,0} instead of the previous interval.
Does it make sense?
Signature · 2 lines
Thanks, Sasha
Thomas Gleixner
maintainerRe: [PATCH 5.15.y, 6.1.y, 6.6.y] posix-cpu-timers: Prevent UAF caused by non-leader exec()
Quoted 35 lines · Sasha Levin
Thanks for doing these. I have queued the 6.12 one, but I would like to ask for a v2 of this one before taking 6.6, 6.1 and 5.15.
Quoted 7 lines
kernel/time/posix-cpu-timers.c +1
-1@@ -714,7 +781,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; }[...]
Quoted 3 lines
kernel/time/posix-cpu-timers.c +3
-0+if (old) + old->it_interval =ns_to_timespec64 (old_incr); +[...]
kernel/time/posix-cpu-timers.c static int posix_cpu_timer_set(struct k_ · line 777
Quoted 4 lines
- out:
kernel/time/posix-cpu-timers.c +0
-3-rcu_read_unlock (); -if (old) - old->it_interval =ns_to_timespec64 (old_incr);On these trees that changes user-visible behaviour on the
TIMER_RETRYpath. Before the patch the "goto out" fell through the shared out:
label, which filled inold->it_interval; afterwards the early return skips it and the assignment only runs on the success path.Upstream that is harmless because
bfa408f03fc7("posix-timers: Retrieve interval in commontimer_settime()code") moved the interval retrieval into the common code, but that landed in v6.12 and 6.6, 6.1 and 5.15 do not have it. Theirdo_timer_settime()setsold_spec64= NULL before it goes back around the retry loop, so atimer_settime()that races timer expiry now reports old_value.it_interval == {0,0} instead of the previous interval.
Quoted 1 line
Does it make sense?
Yes. Let me look at it tomorrow again with brain awake.
Thomas Gleixner
maintainerPATCH v2 1/1 posix-cpu-timers: Prevent UAF caused by non-leader exec()
From: Thomas Gleixner <[email protected]>
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!
---
V2: Fixed the old->interval fallout - Sasha
--- kernel/exit.c | 8 + kernel/signal.c | 10 + kernel/time/posix-cpu-timers.c | 212 ++++++++++++++++++++++++----------------- 3 files changed, 143 insertions(+), 87 deletions(-)
---
kernel/exit.c +7 -1
@@ -204,7 +204,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);
kernel/signal.c +9 -1
@@ -1408,8 +1408,16 @@ struct sighand_struct *__lock_task_sigharcu_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
kernel/time/posix-cpu-timers.c +127 -85
299 lines not on this page · raw message
Re: [PATCH V2 5.15.y, 6.1.y, 6.6.y] posix-cpu-timers: Prevent UAF caused by non-leader exec()
Quoted 1 line · Thomas Gleixner
V2: Fixed the
old->intervalfallout - Sasha
Queued for 5.15, 6.1 and 6.6, thanks.
Signature · 2 lines
Thanks, Sasha