Skip to content

Commit 12a891c

Browse files
Thomas Gleixnergregkh
authored andcommitted
posix-cpu-timers: Prevent UAF caused by non-leader exec() race
commit 920f893 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 e0a7021 ("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: 55e8c8e ("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>
1 parent ae068b6 commit 12a891c

3 files changed

Lines changed: 143 additions & 87 deletions

File tree

kernel/exit.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,13 @@ static void __exit_signal(struct task_struct *tsk)
204204
* doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals.
205205
*/
206206
flush_sigqueue(&tsk->pending);
207-
tsk->sighand = NULL;
207+
208+
/*
209+
* Ensure that all preceeding state is visible. Pairs with
210+
* the smp_acquire__after_ctrl_dep() in the sighand == NULL
211+
* path of lock_task_sighand().
212+
*/
213+
smp_store_release(&tsk->sighand, NULL);
208214
spin_unlock(&sighand->siglock);
209215

210216
__cleanup_sighand(sighand);

kernel/signal.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1408,8 +1408,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk,
14081408
rcu_read_lock();
14091409
for (;;) {
14101410
sighand = rcu_dereference(tsk->sighand);
1411-
if (unlikely(sighand == NULL))
1411+
if (unlikely(sighand == NULL)) {
1412+
/*
1413+
* Pairs with the smp_store_release() in
1414+
* __exit_signal(). It ensures that all state
1415+
* modifications to the task preceeding the store are
1416+
* visible to the callers of lock_task_sighand().
1417+
*/
1418+
smp_acquire__after_ctrl_dep();
14121419
break;
1420+
}
14131421

14141422
/*
14151423
* This sighand can be already freed and even reused, but

kernel/time/posix-cpu-timers.c

Lines changed: 127 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p)
461461
trigger_base_recalc_expires(timer, p);
462462
}
463463

464+
/*
465+
* Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand.
466+
*
467+
* This can race with the reaping of the task:
468+
*
469+
* CPU0 CPU1
470+
*
471+
* // Finds task
472+
* p = pid_task(pid, pid_type); __exit_signal(p)
473+
* lock(p, sighand);
474+
* posix_cpu_timers*_exit();
475+
* sighand = lock_task_sighand(p); unhash_task(p);
476+
* p->sighand = NULL;
477+
* unlock(sighand);
478+
*
479+
* In this case sighand is NULL, which means the task and the associated timer
480+
* queue cannot be longer accessed safely.
481+
*
482+
* __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is
483+
* dead it also invokes posix_cpu_timers_group_exit(). These functions delete
484+
* all pending timers from the related timer queues. The POSIX timers (k_itimer)
485+
* themself are still accessible, but not longer connected to the task.
486+
*
487+
* exec() works slightly differently. The task which exec()'s terminates all
488+
* other threads in the thread group and runs __exit_signal() on them. As the
489+
* thread group is not dead they only clean up the per task timers via
490+
* posix_cpu_timers_exit().
491+
*
492+
* As the TGID on exec() stays the same per process timers stay queued, if they
493+
* are armed. This works without a problem when exec() is done by the thread
494+
* group leader. If a non-leader thread exec()'s this can end up in the
495+
* following scenario:
496+
*
497+
* CPU0 CPU1
498+
* // Returns old leader
499+
* p = pid_task(pid, pid_type); de_thread()
500+
* switch_leader()
501+
* release_task(old leader)
502+
* __exit_signal()
503+
* old_leader->sighand = NULL;
504+
* // Returns NULL
505+
* sighand = lock_task_sighand(p)
506+
*
507+
* That's problematic for several functions:
508+
*
509+
* - posix_cpu_timer_del(): If the timer is still enqueued on the task the
510+
* underlying k_itimer will be freed which results in a UAF in
511+
* run_posix_cpu_timers() or on timerqueue related add/delete operations.
512+
* If the timer is not enqueued, the failure is harmless
513+
*
514+
* - posix_cpu_timer_set(): Independent of the enqueued state that results in a
515+
* transient failure which is user space visible (-ESRCH) for regular posix
516+
* timers. But for the use case in do_cpu_nanosleep() it's the same UAF
517+
* problem just that the timer is allocated on the stack.
518+
*
519+
* - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this
520+
* silently ignores the rearm request, which is a functional problem as the
521+
* timer wont expire anymore.
522+
*/
523+
static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags)
524+
{
525+
enum pid_type type = clock_pid_type(timer->it_clock);
526+
struct cpu_timer *ctmr = &timer->it.cpu;
527+
528+
guard(rcu)();
529+
530+
for (;;) {
531+
struct task_struct *t = pid_task(timer->it.cpu.pid, type);
532+
533+
/* Fail if the task cannot be found. */
534+
if (!t)
535+
break;
536+
537+
/* Try to lock the task's sighand */
538+
if (lock_task_sighand(t, flags))
539+
return t;
540+
541+
/*
542+
* The next PID lookup might either fail or return the new
543+
* leader. This is correct for both exit() and exec().
544+
*/
545+
}
546+
547+
/*
548+
* If the timer is still enqueued, warn. There is nothing safe to do
549+
* here as there might be two timers in there which are removed in
550+
* parallel and that will cause more damage than good. This should never
551+
* happen!
552+
*
553+
* Ensure that the stores to the timer and timerqueue are visible:
554+
*
555+
* __exit_signal()
556+
* posix_cpu_timers*_exit()
557+
* write_seqlock(seqlock)
558+
* smp_wmb(); <-------
559+
* __unhash_process() | !pid_task()
560+
* ----> smp_rmb();
561+
* WARN_ON_ONCE(...)
562+
*/
563+
smp_rmb();
564+
WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node));
565+
return NULL;
566+
}
464567

465568
/*
466569
* 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)
470573
*/
471574
static int posix_cpu_timer_del(struct k_itimer *timer)
472575
{
473-
struct cpu_timer *ctmr = &timer->it.cpu;
474-
struct sighand_struct *sighand;
475576
struct task_struct *p;
476577
unsigned long flags;
477578
int ret = 0;
478579

479-
rcu_read_lock();
480-
p = cpu_timer_task_rcu(timer);
481-
if (!p)
482-
goto out;
580+
p = timer_lock_sighand(timer, &flags);
483581

484-
/*
485-
* Protect against sighand release/switch in exit/exec and process/
486-
* thread timer list entry concurrent read/writes.
487-
*/
488-
sighand = lock_task_sighand(p, &flags);
489-
if (unlikely(sighand == NULL)) {
490-
/*
491-
* This raced with the reaping of the task. The exit cleanup
492-
* should have removed this timer from the timer queue.
493-
*/
494-
WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node));
495-
} else {
582+
if (likely(p)) {
496583
if (timer->it.cpu.firing)
497584
ret = TIMER_RETRY;
498585
else
@@ -501,10 +588,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer)
501588
unlock_task_sighand(p, &flags);
502589
}
503590

504-
out:
505-
rcu_read_unlock();
506591
if (!ret)
507-
put_pid(ctmr->pid);
592+
put_pid(timer->it.cpu.pid);
508593

509594
return ret;
510595
}
@@ -626,42 +711,24 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
626711
clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock);
627712
u64 old_expires, new_expires, old_incr, val;
628713
struct cpu_timer *ctmr = &timer->it.cpu;
629-
struct sighand_struct *sighand;
630714
struct task_struct *p;
631715
unsigned long flags;
632716
int ret = 0;
633717

634-
rcu_read_lock();
635-
p = cpu_timer_task_rcu(timer);
636-
if (!p) {
637-
/*
638-
* If p has just been reaped, we can no
639-
* longer get any information about it at all.
640-
*/
641-
rcu_read_unlock();
718+
p = timer_lock_sighand(timer, &flags);
719+
/*
720+
* If p has just been reaped, we can no longer get any information about
721+
* it at all.
722+
*/
723+
if (!p)
642724
return -ESRCH;
643-
}
644725

645726
/*
646727
* Use the to_ktime conversion because that clamps the maximum
647728
* value to KTIME_MAX and avoid multiplication overflows.
648729
*/
649730
new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value));
650731

651-
/*
652-
* Protect against sighand release/switch in exit/exec and p->cpu_timers
653-
* and p->signal->cpu_timers read/write in arm_timer()
654-
*/
655-
sighand = lock_task_sighand(p, &flags);
656-
/*
657-
* If p has just been reaped, we can no
658-
* longer get any information about it at all.
659-
*/
660-
if (unlikely(sighand == NULL)) {
661-
rcu_read_unlock();
662-
return -ESRCH;
663-
}
664-
665732
/*
666733
* Disarm any old timer after extracting its expiry time.
667734
*/
@@ -710,6 +777,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
710777
old->it_value.tv_sec = 0;
711778
}
712779
}
780+
old->it_interval = ns_to_timespec64(old_incr);
713781
}
714782

715783
if (unlikely(ret)) {
@@ -720,7 +788,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
720788
* it as an overrun (thanks to bump_cpu_timer above).
721789
*/
722790
unlock_task_sighand(p, &flags);
723-
goto out;
791+
return ret;
724792
}
725793

726794
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,
733801
* arm the timer (we'll just fake it for timer_gettime).
734802
*/
735803
cpu_timer_setexpires(ctmr, new_expires);
736-
if (new_expires != 0 && val < new_expires) {
804+
if (new_expires != 0 && val < new_expires)
737805
arm_timer(timer, p);
738-
}
806+
else
807+
trigger_base_recalc_expires(timer, p);
739808

740-
unlock_task_sighand(p, &flags);
741809
/*
742810
* Install the new reload setting, and
743811
* set up the signal and overrun bookkeeping.
@@ -754,35 +822,18 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags,
754822
timer->it_overrun_last = 0;
755823
timer->it_overrun = -1;
756824

757-
if (val >= new_expires) {
758-
if (new_expires != 0) {
759-
/*
760-
* The designated time already passed, so we notify
761-
* immediately, even if the thread never runs to
762-
* accumulate more time on this clock.
763-
*/
764-
cpu_timer_fire(timer);
765-
}
825+
unlock_task_sighand(p, &flags);
766826

827+
if (new_expires && val >= new_expires) {
767828
/*
768-
* Make sure we don't keep around the process wide cputime
769-
* counter or the tick dependency if they are not necessary.
829+
* The designated time already passed, so we notify immediately,
830+
* even if the thread never runs to accumulate more time on this
831+
* clock.
770832
*/
771-
sighand = lock_task_sighand(p, &flags);
772-
if (!sighand)
773-
goto out;
774-
775-
if (!cpu_timer_queued(ctmr))
776-
trigger_base_recalc_expires(timer, p);
777-
778-
unlock_task_sighand(p, &flags);
833+
cpu_timer_fire(timer);
779834
}
780-
out:
781-
rcu_read_unlock();
782-
if (old)
783-
old->it_interval = ns_to_timespec64(old_incr);
784835

785-
return ret;
836+
return 0;
786837
}
787838

788839
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)
10481099
{
10491100
clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock);
10501101
struct task_struct *p;
1051-
struct sighand_struct *sighand;
10521102
unsigned long flags;
10531103
u64 now;
10541104

1055-
rcu_read_lock();
1056-
p = cpu_timer_task_rcu(timer);
1057-
if (!p)
1058-
goto out;
1059-
1060-
/* Protect timer list r/w in arm_timer() */
1061-
sighand = lock_task_sighand(p, &flags);
1062-
if (unlikely(sighand == NULL))
1063-
goto out;
1105+
p = timer_lock_sighand(timer, &flags);
1106+
if (unlikely(!p))
1107+
return;
10641108

10651109
/*
10661110
* 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)
10771121
*/
10781122
arm_timer(timer, p);
10791123
unlock_task_sighand(p, &flags);
1080-
out:
1081-
rcu_read_unlock();
10821124
}
10831125

10841126
/**

0 commit comments

Comments
 (0)