mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] kcsan: Reset per-task KCSAN state on fork
@ 2026-10-30 14:56 Marco Elver
  2026-10-30 16:37 ` Paul E. McKenney
  0 siblings, 1 reply; 2+ messages in thread
From: Marco Elver @ 2026-10-30 14:56 UTC (permalink / raw)
  To: elver
  Cc: Paul E. McKenney, Alexander Potapenko, Boqun Feng, kasan-dev,
	linux-kernel, Peter Zijlstra, Kees Cook, stable

arch_dup_task_struct() copies the parent's task_struct verbatim, which
includes kcsan_ctx and kcsan_stack_depth; nothing reinitializes them for
the child.

With CONFIG_KCSAN_WEAK_MEMORY this leaks a live reorder_access: an
access whose simulated reordering is only valid until the parent returns
from the frame that performed it (tracked via kcsan_stack_depth and
cleared by __tsan_func_exit()). The child inherits both the entry and
the parent's stack depth, but never returns through the parent's frames,
so the entry is never invalidated and is replayed on every subsequent
check_access() of the child.

The most direct way to hit this is the copy itself: __tsan_memcpy()
runs check_access() on the destination before copying. If that access is
selected for a watchpoint, set_reorder_access() records it (ptr = the
child's task_struct, ip in arch_dup_task_struct()) in the parent's ctx,
and the memcpy then copies that entry into the child. For an idle task
created by fork_idle(), the stale access covers thread_info.flags, which
other CPUs frequently access, resulting in false positive reports and:

  WARNING: kernel/kcsan/report.c:344 at sanitize_stack_entries+0x120/0x12c
  Cannot find frame for arch_dup_task_struct+0x40/0x154 in stack trace
  ...
  Call trace:
   sanitize_stack_entries+0x120/0x12c (P)
   print_report+0x84/0x488
   kcsan_report_known_origin+0x14c/0x1ec
   kcsan_setup_watchpoint+0x578/0x864
   __kcsan_check_access+0x1ec/0x1f8
   do_raw_spin_lock+0xa4/0x1c4
   _raw_spin_lock+0x70/0x88
   rcutree_report_cpu_starting+0x1ac/0x4b8
   secondary_start_kernel+0xe8/0x28c
   __secondary_switched+0xc0/0xc4

Reset the child's KCSAN state in dup_task_struct() after the copy,
alongside the existing kcov/kmsan initializers.

Reported-by: Paul E. McKenney <paulmck@kernel.org>
Link: https://lore.kernel.org/all/97f6213a-1dec-494e-96d1-abe546bce993@paulmck-laptop/
Fixes: 69562e4983d9 ("kcsan: Add core support for a subset of weak memory modeling")
Cc: stable@vger.kernel.org
Signed-off-by: Marco Elver <elver@google.com>
---
 include/linux/kcsan.h | 10 ++++++++++
 kernel/fork.c         |  2 ++
 kernel/kcsan/core.c   | 13 +++++++++++++
 3 files changed, 25 insertions(+)

diff --git a/include/linux/kcsan.h b/include/linux/kcsan.h
index c07c71f5ba4f..c7e9d15dd7bd 100644
--- a/include/linux/kcsan.h
+++ b/include/linux/kcsan.h
@@ -13,6 +13,8 @@
 #include <linux/kcsan-checks.h>
 #include <linux/types.h>
 
+struct task_struct;
+
 #ifdef CONFIG_KCSAN
 
 /*
@@ -66,9 +68,17 @@ struct kcsan_ctx {
  */
 void kcsan_init(void);
 
+/**
+ * kcsan_task_init - initialize KCSAN state of a new task
+ *
+ * @task: task to initialize; must have been copied from its parent already.
+ */
+void kcsan_task_init(struct task_struct *task);
+
 #else /* CONFIG_KCSAN */
 
 static inline void kcsan_init(void)			{ }
+static inline void kcsan_task_init(struct task_struct *task) { }
 
 #endif /* CONFIG_KCSAN */
 
diff --git a/kernel/fork.c b/kernel/fork.c
index 5ef413368912..62a096ca8dce 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -93,6 +93,7 @@
 #include <linux/compiler.h>
 #include <linux/sysctl.h>
 #include <linux/kcov.h>
+#include <linux/kcsan.h>
 #include <linux/livepatch.h>
 #include <linux/thread_info.h>
 #include <linux/kstack_erase.h>
@@ -986,6 +987,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
 
 	kcov_task_init(tsk);
 	kmsan_task_create(tsk);
+	kcsan_task_init(tsk);
 	kmap_local_fork(tsk);
 
 #ifdef CONFIG_FAULT_INJECTION
diff --git a/kernel/kcsan/core.c b/kernel/kcsan/core.c
index a3aa5e5b039d..26755add7cdb 100644
--- a/kernel/kcsan/core.c
+++ b/kernel/kcsan/core.c
@@ -828,6 +828,19 @@ void __init kcsan_init(void)
 	}
 }
 
+void kcsan_task_init(struct task_struct *task)
+{
+	/*
+	 * dup_task_struct() copies the parent's kcsan_ctx, including a
+	 * reorder_access that is still live in the parent's frame.
+	 */
+	memset(&task->kcsan_ctx, 0, sizeof(task->kcsan_ctx));
+	task->kcsan_ctx.scoped_accesses = (struct list_head){ LIST_POISON1, NULL };
+#ifdef CONFIG_KCSAN_WEAK_MEMORY
+	task->kcsan_stack_depth = 0;
+#endif
+}
+
 /* === Exported interface =================================================== */
 
 void kcsan_disable_current(void)
-- 
2.56.0.317.g454f8302e1-goog


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH] kcsan: Reset per-task KCSAN state on fork
  2026-10-30 14:56 [PATCH] kcsan: Reset per-task KCSAN state on fork Marco Elver
@ 2026-10-30 16:37 ` Paul E. McKenney
  0 siblings, 0 replies; 2+ messages in thread
From: Paul E. McKenney @ 2026-10-30 16:37 UTC (permalink / raw)
  To: Marco Elver
  Cc: Alexander Potapenko, Boqun Feng, kasan-dev, linux-kernel,
	Peter Zijlstra, Kees Cook, stable

On Fri, Oct 30, 2026 at 02:56:19PM +0000, Marco Elver wrote:
> arch_dup_task_struct() copies the parent's task_struct verbatim, which
> includes kcsan_ctx and kcsan_stack_depth; nothing reinitializes them for
> the child.
> 
> With CONFIG_KCSAN_WEAK_MEMORY this leaks a live reorder_access: an
> access whose simulated reordering is only valid until the parent returns
> from the frame that performed it (tracked via kcsan_stack_depth and
> cleared by __tsan_func_exit()). The child inherits both the entry and
> the parent's stack depth, but never returns through the parent's frames,
> so the entry is never invalidated and is replayed on every subsequent
> check_access() of the child.
> 
> The most direct way to hit this is the copy itself: __tsan_memcpy()
> runs check_access() on the destination before copying. If that access is
> selected for a watchpoint, set_reorder_access() records it (ptr = the
> child's task_struct, ip in arch_dup_task_struct()) in the parent's ctx,
> and the memcpy then copies that entry into the child. For an idle task
> created by fork_idle(), the stale access covers thread_info.flags, which
> other CPUs frequently access, resulting in false positive reports and:
> 
>   WARNING: kernel/kcsan/report.c:344 at sanitize_stack_entries+0x120/0x12c
>   Cannot find frame for arch_dup_task_struct+0x40/0x154 in stack trace
>   ...
>   Call trace:
>    sanitize_stack_entries+0x120/0x12c (P)
>    print_report+0x84/0x488
>    kcsan_report_known_origin+0x14c/0x1ec
>    kcsan_setup_watchpoint+0x578/0x864
>    __kcsan_check_access+0x1ec/0x1f8
>    do_raw_spin_lock+0xa4/0x1c4
>    _raw_spin_lock+0x70/0x88
>    rcutree_report_cpu_starting+0x1ac/0x4b8
>    secondary_start_kernel+0xe8/0x28c
>    __secondary_switched+0xc0/0xc4
> 
> Reset the child's KCSAN state in dup_task_struct() after the copy,
> alongside the existing kcov/kmsan initializers.
> 
> Reported-by: Paul E. McKenney <paulmck@kernel.org>
> Link: https://lore.kernel.org/all/97f6213a-1dec-494e-96d1-abe546bce993@paulmck-laptop/
> Fixes: 69562e4983d9 ("kcsan: Add core support for a subset of weak memory modeling")
> Cc: stable@vger.kernel.org
> Signed-off-by: Marco Elver <elver@google.com>

Thank you, Marco!  I will be testing this over the weekend.

							Thanx, Paul

> ---
>  include/linux/kcsan.h | 10 ++++++++++
>  kernel/fork.c         |  2 ++
>  kernel/kcsan/core.c   | 13 +++++++++++++
>  3 files changed, 25 insertions(+)
> 
> diff --git a/include/linux/kcsan.h b/include/linux/kcsan.h
> index c07c71f5ba4f..c7e9d15dd7bd 100644
> --- a/include/linux/kcsan.h
> +++ b/include/linux/kcsan.h
> @@ -13,6 +13,8 @@
>  #include <linux/kcsan-checks.h>
>  #include <linux/types.h>
>  
> +struct task_struct;
> +
>  #ifdef CONFIG_KCSAN
>  
>  /*
> @@ -66,9 +68,17 @@ struct kcsan_ctx {
>   */
>  void kcsan_init(void);
>  
> +/**
> + * kcsan_task_init - initialize KCSAN state of a new task
> + *
> + * @task: task to initialize; must have been copied from its parent already.
> + */
> +void kcsan_task_init(struct task_struct *task);
> +
>  #else /* CONFIG_KCSAN */
>  
>  static inline void kcsan_init(void)			{ }
> +static inline void kcsan_task_init(struct task_struct *task) { }
>  
>  #endif /* CONFIG_KCSAN */
>  
> diff --git a/kernel/fork.c b/kernel/fork.c
> index 5ef413368912..62a096ca8dce 100644
> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -93,6 +93,7 @@
>  #include <linux/compiler.h>
>  #include <linux/sysctl.h>
>  #include <linux/kcov.h>
> +#include <linux/kcsan.h>
>  #include <linux/livepatch.h>
>  #include <linux/thread_info.h>
>  #include <linux/kstack_erase.h>
> @@ -986,6 +987,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
>  
>  	kcov_task_init(tsk);
>  	kmsan_task_create(tsk);
> +	kcsan_task_init(tsk);
>  	kmap_local_fork(tsk);
>  
>  #ifdef CONFIG_FAULT_INJECTION
> diff --git a/kernel/kcsan/core.c b/kernel/kcsan/core.c
> index a3aa5e5b039d..26755add7cdb 100644
> --- a/kernel/kcsan/core.c
> +++ b/kernel/kcsan/core.c
> @@ -828,6 +828,19 @@ void __init kcsan_init(void)
>  	}
>  }
>  
> +void kcsan_task_init(struct task_struct *task)
> +{
> +	/*
> +	 * dup_task_struct() copies the parent's kcsan_ctx, including a
> +	 * reorder_access that is still live in the parent's frame.
> +	 */
> +	memset(&task->kcsan_ctx, 0, sizeof(task->kcsan_ctx));
> +	task->kcsan_ctx.scoped_accesses = (struct list_head){ LIST_POISON1, NULL };
> +#ifdef CONFIG_KCSAN_WEAK_MEMORY
> +	task->kcsan_stack_depth = 0;
> +#endif
> +}
> +
>  /* === Exported interface =================================================== */
>  
>  void kcsan_disable_current(void)
> -- 
> 2.56.0.317.g454f8302e1-goog
> 

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-30 16:37 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 14:56 [PATCH] kcsan: Reset per-task KCSAN state on fork Marco Elver
2026-10-30 16:37 ` Paul E. McKenney

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®