run_posix_cpu_timers() is called from the timer interrupt. The posix timer
expiry always affects the current task which got interrupted.

sighand locking is only racy when done on a foreign task, which must use
lock_task_sighand(). But in case of run_posix_cpu_timers() that's
pointless.

sighand of a task can only be dropped or changed by the task itself. Drop
happens in do_exit(), changing sighand happens in execve().

So the timer interrupt can see one of the following states of the current
task which it interrupted:

   - executing: sighand is set
   - exiting:   sighand is either set or NULL
   - execing:   sighand is either the original one or the new one

The 'check -> lock -> check again' logic in lock_task_sighand() is not
required here at all. The state cannot change as the interrupted task
context obviously is not executing instructions.

So the only interesting case here is to check whether current->sighand is
NULL or not.

Add the check for sighand == NULL right at the beginning and replace
lock_task_sighand() with a regular spin_lock() invocation along with proper
comments why this is safe.

Signed-off-by: Thomas Gleixner <t...@linutronix.de>
---
 kernel/time/posix-cpu-timers.c |   26 ++++++++++++++++++++------
 1 file changed, 20 insertions(+), 6 deletions(-)

--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -1141,20 +1141,34 @@ void run_posix_cpu_timers(void)
 {
        struct task_struct *tsk = current;
        struct k_itimer *timer, *next;
-       unsigned long flags;
        LIST_HEAD(firing);
 
        lockdep_assert_irqs_disabled();
 
        /*
-        * The fast path checks that there are no expired thread or thread
-        * group timers.  If that's so, just return.
+        * Verify that sighand exits. If not, the task is exiting and has
+        * dropped sighand already. Without sighand, timer expiry is
+        * pointless.
         */
-       if (!fastpath_timer_check(tsk))
+       if (!tsk->sighand)
                return;
 
-       if (!lock_task_sighand(tsk, &flags))
+       /*
+        * Now do a fast check for expired task and group timers. If
+        * no expired timers found, return.
+        */
+       if(!fastpath_timer_check(tsk))
                return;
+
+       /*
+        * The timer interrupt hit current. It's verified that sighand
+        * exists, so it cannot go away before the timer interrupt returns
+        * as only current can remove or change its own sighand in exit()
+        * or exec() with sighand lock held and interrupts disabled. Ergo
+        * the interrupt can only observe a valid sighand or NULL.
+        */
+       spin_lock(&tsk->sighand->siglock);
+
        /*
         * Here we take off tsk->signal->cpu_timers[N] and
         * tsk->cpu_timers[N] all the timers that are firing, and
@@ -1172,7 +1186,7 @@ void run_posix_cpu_timers(void)
         * that gets the timer lock before we do will give it up and
         * spin until we've taken care of that timer below.
         */
-       unlock_task_sighand(tsk, &flags);
+       spin_unlock(&tsk->sighand->siglock);
 
        /*
         * Now that all the timers on our list have the firing flag,


Reply via email to