posix-cpu-timers: Clear task::posix_cputimers_work in copy_process()

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: 1fb497dd0030 ("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

authored by

Michael Pratt and committed by
Thomas Gleixner
ca7752ca 879dbe9f

+20 -2
+2
include/linux/posix-timers.h
··· 184 #endif 185 186 #ifdef CONFIG_POSIX_CPU_TIMERS_TASK_WORK 187 void posix_cputimers_init_work(void); 188 #else 189 static inline void posix_cputimers_init_work(void) { } 190 #endif 191
··· 184 #endif 185 186 #ifdef CONFIG_POSIX_CPU_TIMERS_TASK_WORK 187 + void clear_posix_cputimers_work(struct task_struct *p); 188 void posix_cputimers_init_work(void); 189 #else 190 + static inline void clear_posix_cputimers_work(struct task_struct *p) { } 191 static inline void posix_cputimers_init_work(void) { } 192 #endif 193
+1
kernel/fork.c
··· 2279 p->pdeath_signal = 0; 2280 INIT_LIST_HEAD(&p->thread_group); 2281 p->task_works = NULL; 2282 2283 #ifdef CONFIG_KRETPROBES 2284 p->kretprobe_instances.first = NULL;
··· 2279 p->pdeath_signal = 0; 2280 INIT_LIST_HEAD(&p->thread_group); 2281 p->task_works = NULL; 2282 + clear_posix_cputimers_work(p); 2283 2284 #ifdef CONFIG_KRETPROBES 2285 p->kretprobe_instances.first = NULL;
+17 -2
kernel/time/posix-cpu-timers.c
··· 1159 } 1160 1161 /* 1162 * Initialize posix CPU timers task work in init task. Out of line to 1163 * keep the callback static and to avoid header recursion hell. 1164 */ 1165 void __init posix_cputimers_init_work(void) 1166 { 1167 - init_task_work(&current->posix_cputimers_work.work, 1168 - posix_cpu_timers_work); 1169 } 1170 1171 /*
··· 1159 } 1160 1161 /* 1162 + * Clear existing posix CPU timers task work. 1163 + */ 1164 + void clear_posix_cputimers_work(struct task_struct *p) 1165 + { 1166 + /* 1167 + * A copied work entry from the old task is not meaningful, clear it. 1168 + * N.B. init_task_work will not do this. 1169 + */ 1170 + memset(&p->posix_cputimers_work.work, 0, 1171 + sizeof(p->posix_cputimers_work.work)); 1172 + init_task_work(&p->posix_cputimers_work.work, 1173 + posix_cpu_timers_work); 1174 + p->posix_cputimers_work.scheduled = false; 1175 + } 1176 + 1177 + /* 1178 * Initialize posix CPU timers task work in init task. Out of line to 1179 * keep the callback static and to avoid header recursion hell. 1180 */ 1181 void __init posix_cputimers_init_work(void) 1182 { 1183 + clear_posix_cputimers_work(current); 1184 } 1185 1186 /*