]> git.baikalelectronics.ru Git - kernel.git/commitdiff
posix-cpu-timers: Clear task::posix_cputimers_work in copy_process()
authorMichael Pratt <mpratt@google.com>
Mon, 1 Nov 2021 21:06:15 +0000 (17:06 -0400)
committerThomas Gleixner <tglx@linutronix.de>
Tue, 2 Nov 2021 11:52:17 +0000 (12:52 +0100)
copy_process currently copies task_struct.posix_cputimers_work as-is. If a
timer interrupt arrives while handling clone and before dup_task_struct
completes then the child task will have:

1. posix_cputimers_work.scheduled = true
2. posix_cputimers_work.work queued.

copy_process clears task_struct.task_works, so (2) will have no effect and
posix_cpu_timers_work will never run (not to mention it doesn't make sense
for two tasks to share a common linked list).

Since posix_cpu_timers_work never runs, posix_cputimers_work.scheduled is
never cleared. Since scheduled is set, future timer interrupts will skip
scheduling work, with the ultimate result that the task will never receive
timer expirations.

Together, the complete flow is:

1. Task 1 calls clone(), enters kernel.
2. Timer interrupt fires, schedules task work on Task 1.
   2a. task_struct.posix_cputimers_work.scheduled = true
   2b. task_struct.posix_cputimers_work.work added to
       task_struct.task_works.
3. dup_task_struct() copies Task 1 to Task 2.
4. copy_process() clears task_struct.task_works for Task 2.
5. Future timer interrupts on Task 2 see
   task_struct.posix_cputimers_work.scheduled = true and skip scheduling
   work.

Fix this by explicitly clearing contents of task_struct.posix_cputimers_work
in copy_process(). This was never meant to be shared or inherited across
tasks in the first place.

Fixes: a5de38716e75 ("posix-cpu-timers: Provide mechanisms to defer timer handling to task_work")
Reported-by: Rhys Hiltner <rhys@justin.tv>
Signed-off-by: Michael Pratt <mpratt@google.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/20211101210615.716522-1-mpratt@google.com
include/linux/posix-timers.h
kernel/fork.c
kernel/time/posix-cpu-timers.c

index 00fef0064355fee5fd500b6c3d34dc2cc160f06b..5bbcd280bfd267bcab32e766725c4350af0b8f89 100644 (file)
@@ -184,8 +184,10 @@ static inline void posix_cputimers_group_init(struct posix_cputimers *pct,
 #endif
 
 #ifdef CONFIG_POSIX_CPU_TIMERS_TASK_WORK
+void clear_posix_cputimers_work(struct task_struct *p);
 void posix_cputimers_init_work(void);
 #else
+static inline void clear_posix_cputimers_work(struct task_struct *p) { }
 static inline void posix_cputimers_init_work(void) { }
 #endif
 
index 8e9feeef555e7f0650f07d84fa51f214c3c98b14..8269ae2e5d7c510ccdccc761e8f5fd65066263d5 100644 (file)
@@ -2279,6 +2279,7 @@ static __latent_entropy struct task_struct *copy_process(
        p->pdeath_signal = 0;
        INIT_LIST_HEAD(&p->thread_group);
        p->task_works = NULL;
+       clear_posix_cputimers_work(p);
 
 #ifdef CONFIG_KRETPROBES
        p->kretprobe_instances.first = NULL;
index 643d412ac6235eae110975f60247c97a8f278225..96b4e78104266fc6b3736de89ee20dbd49e02284 100644 (file)
@@ -1158,14 +1158,29 @@ static void posix_cpu_timers_work(struct callback_head *work)
        handle_posix_cpu_timers(current);
 }
 
+/*
+ * Clear existing posix CPU timers task work.
+ */
+void clear_posix_cputimers_work(struct task_struct *p)
+{
+       /*
+        * A copied work entry from the old task is not meaningful, clear it.
+        * N.B. init_task_work will not do this.
+        */
+       memset(&p->posix_cputimers_work.work, 0,
+              sizeof(p->posix_cputimers_work.work));
+       init_task_work(&p->posix_cputimers_work.work,
+                      posix_cpu_timers_work);
+       p->posix_cputimers_work.scheduled = false;
+}
+
 /*
  * Initialize posix CPU timers task work in init task. Out of line to
  * keep the callback static and to avoid header recursion hell.
  */
 void __init posix_cputimers_init_work(void)
 {
-       init_task_work(&current->posix_cputimers_work.work,
-                      posix_cpu_timers_work);
+       clear_posix_cputimers_work(current);
 }
 
 /*