perf: Add cond_resched() to task_function_call()
authorBarret Rhoden <brho@google.com>
Tue, 14 Apr 2020 22:29:20 +0000 (18:29 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 20 Jun 2020 08:25:06 +0000 (10:25 +0200)
commit 2ed6edd33a214bca02bd2b45e3fc3038a059436b upstream.

Under rare circumstances, task_function_call() can repeatedly fail and
cause a soft lockup.

There is a slight race where the process is no longer running on the cpu
we targeted by the time remote_function() runs.  The code will simply
try again.  If we are very unlucky, this will continue to fail, until a
watchdog fires.  This can happen in a heavily loaded, multi-core virtual
machine.

Reported-by: syzbot+bb4935a5c09b5ff79940@syzkaller.appspotmail.com
Signed-off-by: Barret Rhoden <brho@google.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lkml.kernel.org/r/20200414222920.121401-1-brho@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
kernel/events/core.c

index 5636c9c48545c112ae994c27d9636764a741837a..e50b140053f9a804cf43faf3d7aeb82d9b65be2d 100644 (file)
@@ -94,11 +94,11 @@ static void remote_function(void *data)
  * @info:      the function call argument
  *
  * Calls the function @func when the task is currently running. This might
- * be on the current CPU, which just calls the function directly
+ * be on the current CPU, which just calls the function directly.  This will
+ * retry due to any failures in smp_call_function_single(), such as if the
+ * task_cpu() goes offline concurrently.
  *
- * returns: @func return value, or
- *         -ESRCH  - when the process isn't running
- *         -EAGAIN - when the process moved away
+ * returns @func return value or -ESRCH when the process isn't running
  */
 static int
 task_function_call(struct task_struct *p, remote_function_f func, void *info)
@@ -111,11 +111,16 @@ task_function_call(struct task_struct *p, remote_function_f func, void *info)
        };
        int ret;
 
-       do {
-               ret = smp_call_function_single(task_cpu(p), remote_function, &data, 1);
-               if (!ret)
-                       ret = data.ret;
-       } while (ret == -EAGAIN);
+       for (;;) {
+               ret = smp_call_function_single(task_cpu(p), remote_function,
+                                              &data, 1);
+               ret = !ret ? data.ret : -EAGAIN;
+
+               if (ret != -EAGAIN)
+                       break;
+
+               cond_resched();
+       }
 
        return ret;
 }