Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

kthread: consolidate kthread exit paths to prevent use-after-free

Guillaume reported crashes via corrupted RCU callback function pointers
during KUnit testing. The crash was traced back to the pidfs rhashtable
conversion which replaced the 24-byte rb_node with an 8-byte rhash_head
in struct pid, shrinking it from 160 to 144 bytes.

struct kthread (without CONFIG_BLK_CGROUP) is also 144 bytes. With
CONFIG_SLAB_MERGE_DEFAULT and SLAB_HWCACHE_ALIGN both round up to
192 bytes and share the same slab cache. struct pid.rcu.func and
struct kthread.affinity_node both sit at offset 0x78.

When a kthread exits via make_task_dead() it bypasses kthread_exit() and
misses the affinity_node cleanup. free_kthread_struct() frees the memory
while the node is still linked into the global kthread_affinity_list. A
subsequent list_del() by another kthread writes through dangling list
pointers into the freed and reused memory, corrupting the pid's
rcu.func pointer.

Instead of patching free_kthread_struct() to handle the missed cleanup,
consolidate all kthread exit paths. Turn kthread_exit() into a macro
that calls do_exit() and add kthread_do_exit() which is called from
do_exit() for any task with PF_KTHREAD set. This guarantees that
kthread-specific cleanup always happens regardless of the exit path -
make_task_dead(), direct do_exit(), or kthread_exit().

Replace __to_kthread() with a new tsk_is_kthread() accessor in the
public header. Export do_exit() since module code using the
kthread_exit() macro now needs it directly.

Reported-by: Guillaume Tucker <gtucker@gtucker.io>
Tested-by: Guillaume Tucker <gtucker@gtucker.io>
Tested-by: Mark Brown <broonie@kernel.org>
Tested-by: David Gow <davidgow@google.com>
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/all/20260224-mittlerweile-besessen-2738831ae7f6@brauner
Co-developed-by: Linus Torvalds <torvalds@linux-foundation.org>
Fixes: 4d13f4304fa4 ("kthread: Implement preferred affinity")
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Christian Brauner <brauner@kernel.org>

+31 -37
+20 -1
include/linux/kthread.h
··· 7 7 8 8 struct mm_struct; 9 9 10 + /* opaque kthread data */ 11 + struct kthread; 12 + 13 + /* 14 + * When "(p->flags & PF_KTHREAD)" is set the task is a kthread and will 15 + * always remain a kthread. For kthreads p->worker_private always 16 + * points to a struct kthread. For tasks that are not kthreads 17 + * p->worker_private is used to point to other things. 18 + * 19 + * Return NULL for any task that is not a kthread. 20 + */ 21 + static inline struct kthread *tsk_is_kthread(struct task_struct *p) 22 + { 23 + if (p->flags & PF_KTHREAD) 24 + return p->worker_private; 25 + return NULL; 26 + } 27 + 10 28 __printf(4, 5) 11 29 struct task_struct *kthread_create_on_node(int (*threadfn)(void *data), 12 30 void *data, ··· 116 98 int kthread_park(struct task_struct *k); 117 99 void kthread_unpark(struct task_struct *k); 118 100 void kthread_parkme(void); 119 - void kthread_exit(long result) __noreturn; 101 + #define kthread_exit(result) do_exit(result) 120 102 void kthread_complete_and_exit(struct completion *, long) __noreturn; 121 103 int kthreads_update_housekeeping(void); 104 + void kthread_do_exit(struct kthread *, long); 122 105 123 106 int kthreadd(void *unused); 124 107 extern struct task_struct *kthreadd_task;
+6
kernel/exit.c
··· 896 896 void __noreturn do_exit(long code) 897 897 { 898 898 struct task_struct *tsk = current; 899 + struct kthread *kthread; 899 900 int group_dead; 900 901 901 902 WARN_ON(irqs_disabled()); 902 903 WARN_ON(tsk->plug); 904 + 905 + kthread = tsk_is_kthread(tsk); 906 + if (unlikely(kthread)) 907 + kthread_do_exit(kthread, code); 903 908 904 909 kcov_task_exit(tsk); 905 910 kmsan_task_exit(tsk); ··· 1018 1013 lockdep_free_task(tsk); 1019 1014 do_task_dead(); 1020 1015 } 1016 + EXPORT_SYMBOL(do_exit); 1021 1017 1022 1018 void __noreturn make_task_dead(int signr) 1023 1019 {
+5 -36
kernel/kthread.c
··· 85 85 return k->worker_private; 86 86 } 87 87 88 - /* 89 - * Variant of to_kthread() that doesn't assume @p is a kthread. 90 - * 91 - * When "(p->flags & PF_KTHREAD)" is set the task is a kthread and will 92 - * always remain a kthread. For kthreads p->worker_private always 93 - * points to a struct kthread. For tasks that are not kthreads 94 - * p->worker_private is used to point to other things. 95 - * 96 - * Return NULL for any task that is not a kthread. 97 - */ 98 - static inline struct kthread *__to_kthread(struct task_struct *p) 99 - { 100 - void *kthread = p->worker_private; 101 - if (kthread && !(p->flags & PF_KTHREAD)) 102 - kthread = NULL; 103 - return kthread; 104 - } 105 - 106 88 void get_kthread_comm(char *buf, size_t buf_size, struct task_struct *tsk) 107 89 { 108 90 struct kthread *kthread = to_kthread(tsk); ··· 175 193 176 194 bool kthread_should_stop_or_park(void) 177 195 { 178 - struct kthread *kthread = __to_kthread(current); 196 + struct kthread *kthread = tsk_is_kthread(current); 179 197 180 198 if (!kthread) 181 199 return false; ··· 216 234 */ 217 235 void *kthread_func(struct task_struct *task) 218 236 { 219 - struct kthread *kthread = __to_kthread(task); 237 + struct kthread *kthread = tsk_is_kthread(task); 220 238 if (kthread) 221 239 return kthread->threadfn; 222 240 return NULL; ··· 248 266 */ 249 267 void *kthread_probe_data(struct task_struct *task) 250 268 { 251 - struct kthread *kthread = __to_kthread(task); 269 + struct kthread *kthread = tsk_is_kthread(task); 252 270 void *data = NULL; 253 271 254 272 if (kthread) ··· 291 309 } 292 310 EXPORT_SYMBOL_GPL(kthread_parkme); 293 311 294 - /** 295 - * kthread_exit - Cause the current kthread return @result to kthread_stop(). 296 - * @result: The integer value to return to kthread_stop(). 297 - * 298 - * While kthread_exit can be called directly, it exists so that 299 - * functions which do some additional work in non-modular code such as 300 - * module_put_and_kthread_exit can be implemented. 301 - * 302 - * Does not return. 303 - */ 304 - void __noreturn kthread_exit(long result) 312 + void kthread_do_exit(struct kthread *kthread, long result) 305 313 { 306 - struct kthread *kthread = to_kthread(current); 307 314 kthread->result = result; 308 315 if (!list_empty(&kthread->affinity_node)) { 309 316 mutex_lock(&kthread_affinity_lock); ··· 304 333 kthread->preferred_affinity = NULL; 305 334 } 306 335 } 307 - do_exit(0); 308 336 } 309 - EXPORT_SYMBOL(kthread_exit); 310 337 311 338 /** 312 339 * kthread_complete_and_exit - Exit the current kthread. ··· 652 683 653 684 bool kthread_is_per_cpu(struct task_struct *p) 654 685 { 655 - struct kthread *kthread = __to_kthread(p); 686 + struct kthread *kthread = tsk_is_kthread(p); 656 687 if (!kthread) 657 688 return false; 658 689