Re: [PATCH] kcsan: Reset per-task KCSAN state on fork

From: Paul E. McKenney

Date: Fri Oct 30 2026 - 12:38:05 EST


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@xxxxxxxxxx>
> 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@xxxxxxxxxxxxxxx
> Signed-off-by: Marco Elver <elver@xxxxxxxxxx>

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
>