Skip to content

Commit 9d9e522

Browse files
committed
posix-timers: Prevent RT livelock in itimer_delete()
itimer_delete() has a retry loop when the timer is concurrently expired. On non-RT kernels this just spin-waits until the timer callback has completed, except for posix CPU timers which have HAVE_POSIX_CPU_TIMERS_TASK_WORK enabled. In that case and on RT kernels the existing task could live lock when preempting the task which does the timer delivery. Replace spin_unlock() with an invocation of timer_wait_running() to handle it the same way as the other retry loops in the posix timer code. Fixes: ec8f954 ("posix-timers: Use a callback for cancel synchronization on PREEMPT_RT") Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Frederic Weisbecker <frederic@kernel.org> Link: https://lore.kernel.org/r/87v8g7c50d.ffs@tglx
1 parent b7a7ce1 commit 9d9e522

File tree

1 file changed

+35
-8
lines changed

1 file changed

+35
-8
lines changed

kernel/time/posix-timers.c

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,27 +1037,52 @@ SYSCALL_DEFINE1(timer_delete, timer_t, timer_id)
10371037
}
10381038

10391039
/*
1040-
* return timer owned by the process, used by exit_itimers
1040+
* Delete a timer if it is armed, remove it from the hash and schedule it
1041+
* for RCU freeing.
10411042
*/
10421043
static void itimer_delete(struct k_itimer *timer)
10431044
{
1044-
retry_delete:
1045-
spin_lock_irq(&timer->it_lock);
1045+
unsigned long flags;
1046+
1047+
/*
1048+
* irqsave is required to make timer_wait_running() work.
1049+
*/
1050+
spin_lock_irqsave(&timer->it_lock, flags);
10461051

1052+
retry_delete:
1053+
/*
1054+
* Even if the timer is not longer accessible from other tasks
1055+
* it still might be armed and queued in the underlying timer
1056+
* mechanism. Worse, that timer mechanism might run the expiry
1057+
* function concurrently.
1058+
*/
10471059
if (timer_delete_hook(timer) == TIMER_RETRY) {
1048-
spin_unlock_irq(&timer->it_lock);
1060+
/*
1061+
* Timer is expired concurrently, prevent livelocks
1062+
* and pointless spinning on RT.
1063+
*
1064+
* timer_wait_running() drops timer::it_lock, which opens
1065+
* the possibility for another task to delete the timer.
1066+
*
1067+
* That's not possible here because this is invoked from
1068+
* do_exit() only for the last thread of the thread group.
1069+
* So no other task can access and delete that timer.
1070+
*/
1071+
if (WARN_ON_ONCE(timer_wait_running(timer, &flags) != timer))
1072+
return;
1073+
10491074
goto retry_delete;
10501075
}
10511076
list_del(&timer->list);
10521077

1053-
spin_unlock_irq(&timer->it_lock);
1078+
spin_unlock_irqrestore(&timer->it_lock, flags);
10541079
release_posix_timer(timer, IT_ID_SET);
10551080
}
10561081

10571082
/*
1058-
* This is called by do_exit or de_thread, only when nobody else can
1059-
* modify the signal->posix_timers list. Yet we need sighand->siglock
1060-
* to prevent the race with /proc/pid/timers.
1083+
* Invoked from do_exit() when the last thread of a thread group exits.
1084+
* At that point no other task can access the timers of the dying
1085+
* task anymore.
10611086
*/
10621087
void exit_itimers(struct task_struct *tsk)
10631088
{
@@ -1067,10 +1092,12 @@ void exit_itimers(struct task_struct *tsk)
10671092
if (list_empty(&tsk->signal->posix_timers))
10681093
return;
10691094

1095+
/* Protect against concurrent read via /proc/$PID/timers */
10701096
spin_lock_irq(&tsk->sighand->siglock);
10711097
list_replace_init(&tsk->signal->posix_timers, &timers);
10721098
spin_unlock_irq(&tsk->sighand->siglock);
10731099

1100+
/* The timers are not longer accessible via tsk::signal */
10741101
while (!list_empty(&timers)) {
10751102
tmr = list_first_entry(&timers, struct k_itimer, list);
10761103
itimer_delete(tmr);

0 commit comments

Comments
 (0)