* Re: [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values
From: Yunseong Kim @ 2026-06-12 7:55 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Dmitry Vyukov, Andrey Konovalov, Andrew Morton,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Nicolas Schier, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
linux-kernel, kasan-dev, llvm, linux-kbuild, rust-for-linux,
workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260604084115.GZ3126523@noisy.programming.kicks-ass.net>
Hi Peter,
> On Wed, Jun 03, 2026 at 07:43:28PM +0200, Yunseong Kim wrote:
>> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
>> index e2f976c3301b..abd1a94589aa 100644
>> --- a/lib/Kconfig.debug
>> +++ b/lib/Kconfig.debug
>> @@ -2261,6 +2261,28 @@ config KCOV_SELFTEST
>> On test failure, causes the kernel to panic. Recommended to be
>> enabled, ensuring critical functionality works as intended.
>>
>> +
>
> ^ This line...
>
>> +config KCOV_DATAFLOW_ARGS
>> + bool "Enable KCOV dataflow: function argument capture"
>> + depends on KCOV
>> + depends on $(cc-option,-fsanitize-coverage=dataflow-args)
>> + help
>> + Captures function arguments at entry via /sys/kernel/debug/kcov_dataflow.
>> + Struct pointer arguments are auto-expanded using compiler DebugInfo
>> + metadata, recording individual field values at runtime.
>> + Enable per-module with: KCOV_DATAFLOW_file.o := y in the Makefile.
>> + Requires clang with -fsanitize-coverage=dataflow-args support.
>> +
>> +config KCOV_DATAFLOW_RET
>> + bool "Enable KCOV dataflow: return value capture"
>> + depends on KCOV
>> + depends on $(cc-option,-fsanitize-coverage=dataflow-ret)
>> + help
>> + Captures function return values via /sys/kernel/debug/kcov_dataflow.
>> + Struct pointer returns are auto-expanded using compiler DebugInfo
>> + metadata, recording individual field values at runtime.
>> + Enable per-module with: KCOV_DATAFLOW_file.o := y in the Makefile.
>> + Requires clang with -fsanitize-coverage=dataflow-ret support.
>
> Probably wants to be here...
Thank you for code review. I updated at v2 patch.
>
>> config DEBUG_AID_FOR_SYZBOT
>> bool "Additional debug code for syzbot"
>> default n
>>
>> --
>> 2.43.0
>>
Best regards,
Yunseong
^ permalink raw reply
* Re: [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values
From: Yunseong Kim @ 2026-06-12 7:52 UTC (permalink / raw)
To: Alexander Potapenko
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan, linux-kernel,
kasan-dev, llvm, linux-kbuild, rust-for-linux, workflows,
linux-doc, Yunseong Kim
In-Reply-To: <CAG_fn=UMJJz+3zipowaC4uTvcbC0gvXbBRaF0UUJ_1AW+oWNGA@mail.gmail.com>
Hi Alexander,
>> - Per-task buffer: task->kcov_df_area with atomic xadd reservation
>
> I don't understand this line...
>
>> - Recursion-safe: notrace __no_sanitize_coverage noinline
>> - ERR_PTR aware: skips struct expansion for error pointers
>
> ... and this.
I updated this text at v2 patch.
>>
>> The callbacks (__sanitizer_cov_trace_args/ret) are inserted by the
>> compiler when -fsanitize-coverage=dataflow-args,dataflow-ret is used.
>> The Kconfig options depend on cc-option to verify compiler support.
>>
>> Buffer format (TLV records, all u64):
>> area[0]: atomic word count
>> [pos+0]: type_and_seq (0xE=entry, 0xF=return in upper 4 bits)
>> [pos+1]: PC
>> [pos+2]: meta (arg_idx | arg_size | ptr)
>> [pos+3..N]: field values read via copy_from_kernel_nofault()
>>
>> This is completely independent from legacy /sys/kernel/debug/kcov.
>> Existing users (syzkaller, oss-fuzz) are unaffected.
>
> Does oss-fuzz even use kcov?
Also, I removed this text at v2 patch. I mistakenly confused it with another
usage of KCOV with a other fuzzer.
https://security.googleblog.com/2024/06/hacking-for-defenders-approaches-to.html
>>
>> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
>> ---
>> include/linux/sched.h | 8 ++
>> kernel/kcov.c | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++
>> lib/Kconfig.debug | 22 ++++
>> 3 files changed, 321 insertions(+)
>>
>> diff --git a/include/linux/sched.h b/include/linux/sched.h
>> index c4433c185ad8..03be4b495f70 100644
>> --- a/include/linux/sched.h
>> +++ b/include/linux/sched.h
>> @@ -1533,6 +1533,14 @@ struct task_struct {
>> /* KCOV sequence number: */
>> int kcov_sequence;
>>
>> + /* KCOV dataflow per-task sequence counter for TLV records: */
>> + u32 kcov_dataflow_seq;
>> +
>> + /* KCOV dataflow: separate buffer for trace-args/trace-ret */
>> + unsigned int kcov_df_size;
>> + void *kcov_df_area;
>> + bool kcov_df_enabled;
>> +
>> /* Collect coverage from softirq context: */
>> unsigned int kcov_softirq;
>> #endif
>> diff --git a/kernel/kcov.c b/kernel/kcov.c
>> index 1df373fb562b..d3c9c0efe961 100644
>> --- a/kernel/kcov.c
>> +++ b/kernel/kcov.c
>> @@ -353,6 +353,288 @@ void notrace __sanitizer_cov_trace_switch(kcov_u64 val, void *arg)
>> EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
>> #endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
>>
>> +#if defined(CONFIG_KCOV_DATAFLOW_ARGS) || defined(CONFIG_KCOV_DATAFLOW_RET)
>> +/*
>> + * KCOV Dataflow: /sys/kernel/debug/kcov_dataflow
>> + *
>> + * Completely separate from legacy /sys/kernel/debug/kcov.
>
> Since this code is completely separate, could it be put into a separate file?
> I think kcov.c is too big already.
Thank you again for your guide, I updated it at v2.
>> + * Own buffer, own ioctl, own mmap. No printk — buffer only.
>
> Can you please not use these long dashes in C code?
I removed all a the v2.
>> +/*
>> + * Core write function — no printk, no locks, just atomic buffer write.
>
> I think it's okay to omit what this function is not doing.
>
>
>> +
>> + /* Atomic reservation */
>> + pos = 1 + xadd((unsigned long *)&area[0], record_len);
>> + if (unlikely(pos + record_len > max_pos)) {
>> + xadd((unsigned long *)&area[0], -(long)record_len);
>> + return;
>> + }
>
> Have you tried compiling this code on ARM64?
> I am pretty sure they don't have xadd(), so it won't work.
> But why do we need an atomic increment here at all? write_comp_data()
> performs the same job, and does not need it.
> Or am I missing something?
Thank you again for pointing out. After updating to the READ_ONCE/WRITE_ONCE
atomic pattern, Testing results based on v2 on arm64 for the Rust for Linux
kernel module(eight_struct_args_rust) are as follows:
do_el0_svc({0xffffffffffffff9c, 0xffffffffffffff9c, 0xffffffff, 0x0, 0x0, 0x0})
invoke_syscall({0xffffffffffffff9c, 0xffffffffffffff9c, 0x38, 0x0, 0x0, 0x0}, 0x38)
__arm64_sys_openat({0xffffffffffffff9c, 0xffffffffffffff9c, 0x38, 0x0, 0x0, 0x0})
ksys_write(0xffff9a031231, 0x1)
fdget_pos(0x4)
0xffff000004421cc0 = fdget_pos()
0x0 = vfs_write()
vfs_write(0xffff9a031231, 0x1, 0x0)
0x0 = _RNvCsdfZGIOKgjaD_22eight_struct_args_rust13write_handler [eight_struct_args_rust]()
_RNvCsdfZGIOKgjaD_22eight_struct_args_rust13write_handler [eight_struct_args_rust](0xffff9a031231, 0x1, 0x0)
rsf_1 [eight_struct_args_rust](0x11)
0x11 = rsf_1 [eight_struct_args_rust]()
rsf_2 [eight_struct_args_rust](0x11, {0x11, 0x22})
0x33 = rsf_2 [eight_struct_args_rust]()
rsf_4 [eight_struct_args_rust](0x11, {0x11, 0x22}, {0x11, 0x22, 0x33}, {0x11, 0x22, 0x33, 0x44})
0xaa = rsf_4 [eight_struct_args_rust]()
...
Latest test results from Github CI:
https://github.com/yskzalloc/kcov-dataflow/actions/runs/27397351811/job/80967927283
Best regards,
Yunseong
^ permalink raw reply
* Re: [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
From: Peter Zijlstra @ 2026-06-12 7:38 UTC (permalink / raw)
To: Yunseong Kim
Cc: Ingo Molnar, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Dmitry Vyukov, Andrey Konovalov, Andrew Morton,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Nicolas Schier, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
linux-kernel, kasan-dev, llvm, linux-kbuild, rust-for-linux,
workflows, linux-doc, Yunseong Kim
In-Reply-To: <513c68a8-e530-4461-a8e5-36e5d3d5858f@est.tech>
On Fri, Jun 12, 2026 at 09:37:40AM +0200, Yunseong Kim wrote:
> Hi Peter,
>
> > On Wed, Jun 03, 2026 at 07:43:27PM +0200, Yunseong Kim wrote:
> >> CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL instruments every function in the
> >> kernel.
> >
> > Well, I would hope it would very much not instrument noinstr functions.
>
> Thank you for your guidance. I updated the default
> CONFIG_KCOV_DATAFLOW_NO_INLINE to n.
That's not really the point. The compiler extension *must* respect
noinstr. If there it no function attribute to suppress this kcov stuff,
then its an absolute non-starter.
^ permalink raw reply
* Re: [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
From: Yunseong Kim @ 2026-06-12 7:37 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Dmitry Vyukov, Andrey Konovalov, Andrew Morton,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Nicolas Schier, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
linux-kernel, kasan-dev, llvm, linux-kbuild, rust-for-linux,
workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260604084026.GY3126523@noisy.programming.kicks-ass.net>
Hi Peter,
> On Wed, Jun 03, 2026 at 07:43:27PM +0200, Yunseong Kim wrote:
>> CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL instruments every function in the
>> kernel.
>
> Well, I would hope it would very much not instrument noinstr functions.
Thank you for your guidance. I updated the default
CONFIG_KCOV_DATAFLOW_NO_INLINE to n.
Best regards,
Yunseong
^ permalink raw reply
* Re: [RFC PATCH v2 01/14] kcov: add per-task dataflow tracking for function arguments/return values
From: Alexander Potapenko @ 2026-06-12 7:34 UTC (permalink / raw)
To: Yunseong Kim
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Dmitry Vyukov, Andrew Morton, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Nathan Chancellor, Nicolas Schier,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Shuah Khan, Jonathan Corbet, Shuah Khan, linux-kernel, kasan-dev,
rust-for-linux, linux-kbuild, llvm, linux-mm, linux-kselftest,
workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-1-0a261da3987c@est.tech>
On Thu, Jun 11, 2026 at 6:21 PM Yunseong Kim <yunseong.kim@est.tech> wrote:
>
> Add a new tracking mechanism that captures function arguments/return
> values at instrumented function boundaries via submitted as an LLVM
> RFC SanitizerCoverage callbacks:
>
> __sanitizer_cov_trace_args
> __sanitizer_cov_trace_ret
>
> This requires a custom LLVM/Clang build with the trace-args/ret passes:
>
> LLVM RFC:
>
> https://discourse.llvm.org/t/rfc-sanitizercoverage-add-fsanitize-coverage-trace-args-trace-ret/91026
>
> LLVM PR:
>
> https://github.com/llvm/llvm-project/pull/201410
>
> Clone and build toolchain:
>
> git clone --recursive --depth 1 --shallow-submodules \
> --jobs `nproc` https://github.com/yskzalloc/kcov-dataflow.git
> cd kcov-dataflow
>
> cd llvm-project
> cmake -S llvm -B build -G Ninja \
> -DCMAKE_BUILD_TYPE=Release \
> -DCMAKE_C_COMPILER=clang \
> -DCMAKE_CXX_COMPILER=clang++ \
> -DLLVM_ENABLE_LLD=ON \
> -DLLVM_ENABLE_PROJECTS="clang;lld" \
> -DLLVM_TARGETS_TO_BUILD="X86;AArch64"
> ninja -C build
> cd ..
>
> Build and boot kernel (using virtme-ng):
>
> export PATH=$PWD/llvm-project/build/bin:$PATH
> cd linux
> vng --build \
> --configitem CONFIG_KCOV=y \
> --configitem CONFIG_KCOV_DATAFLOW_ARGS=y \
> --configitem CONFIG_KCOV_DATAFLOW_RET=y \
> --configitem CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y \
> --configitem CONFIG_DEBUG_INFO=y \
> --configitem CONFIG_RUST=y # for rust module kselftest
> LLVM=1 CC=clang
>
> Core implementation in kernel/kcov_dataflow.c (separating from kcov.c
> as Alexander's request):
> - Per-task lock-free ring buffer via debugfs kcov_dataflow device
> - READ_ONCE/WRITE_ONCE atomic pattern (tested on arm64)
> - copy_from_kernel_nofault() for safe struct field reads
> - in_task() guard rejects interrupt context
> - Bit-31 recursion guard prevents INSTRUMENT_ALL re-entry
>
> Build system (scripts/Makefile.kcov, scripts/Makefile.lib):
> - CFLAGS_KCOV_DATAFLOW: -fsanitize-coverage=trace-args,trace-ret
> - RUSTFLAGS_KCOV_DATAFLOW: -Cllvm-args=-sanitizer-coverage-trace-args/ret
> - Per-file opt-in: KCOV_DATAFLOW_file.o := y
> - Respects KCOV_INSTRUMENT := n for noinstr exclusion
> - CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL for whole-kernel
>
> Kconfig (lib/Kconfig.debug):
> - CONFIG_KCOV_DATAFLOW_ARGS / CONFIG_KCOV_DATAFLOW_RET
> - Depends on CONFIG_KCOV and CONFIG_DEBUG_INFO
> - CONFIG_KCOV_DATAFLOW_NO_INLINE (default n)
> - CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL
>
> Also fix rust/kernel/str.rs unused import (flags::* -> flags::GFP_KERNEL)
> which newer rustc (1.98-nightly) rejects as a hard error.
>
> Rust support requires rustc built against the custom LLVM with
> trace-args/ret passes compiled in:
>
> https://github.com/yskzalloc/rust
>
> Link: https://github.com/yskzalloc/kcov-dataflow/
> Cc: Alexander Potapenko <glider@google.com>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Nicolas Schier <nsc@kernel.org>
> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
> ---
> include/linux/sched.h | 10 ++
> kernel/Makefile | 3 +
> kernel/kcov.c | 2 +
> kernel/kcov_dataflow.c | 324 +++++++++++++++++++++++++++++++++++++++++++++++++
I think the total size of kcov_dataflow.c doesn't justify splitting it
in multiple patches.
^ permalink raw reply
* Re: [RFC PATCH v2 04/14] kcov: reject enable on multiple dataflow fds simultaneously
From: Alexander Potapenko @ 2026-06-12 7:32 UTC (permalink / raw)
To: Yunseong Kim
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Dmitry Vyukov, Andrew Morton, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Nathan Chancellor, Nicolas Schier,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Shuah Khan, Jonathan Corbet, Shuah Khan, linux-kernel, kasan-dev,
rust-for-linux, linux-kbuild, llvm, linux-mm, linux-kselftest,
workflows, linux-doc, Yeoreum Yun, sashiko-bot
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-4-0a261da3987c@est.tech>
On Thu, Jun 11, 2026 at 6:21 PM Yunseong Kim <yunseong.kim@est.tech> wrote:
>
> A task could enable tracing on multiple kcov_dataflow file descriptors,
> corrupting the internal tracking state when one is subsequently closed.
>
> Check current->kcov_df_enabled before allowing KCOV_DF_ENABLE and
> return -EBUSY if already active. This matches kcov's check of
> t->kcov != NULL in the KCOV_ENABLE path.
>
> Reported-by: sashiko-bot <sashiko-bot@kernel.org>
> Closes: https://sashiko.dev/#/patchset/20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4%40est.tech
> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
> ---
> kernel/kcov_dataflow.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/kernel/kcov_dataflow.c b/kernel/kcov_dataflow.c
> index 5248293280d5..27587b8ceeab 100644
> --- a/kernel/kcov_dataflow.c
> +++ b/kernel/kcov_dataflow.c
Please merge this patch into the one introducing kcov_dataflow.c
^ permalink raw reply
* Re: [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
From: Yunseong Kim @ 2026-06-12 7:33 UTC (permalink / raw)
To: Alexander Potapenko
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Nicolas Schier,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Jonathan Corbet, Shuah Khan, linux-kernel,
kasan-dev, llvm, linux-kbuild, rust-for-linux, workflows,
linux-doc, Yunseong Kim
In-Reply-To: <CAG_fn=W1++qPJWQk1+4MtRfe6n1iUKF2O5pddnqKGwSq85CuqA@mail.gmail.com>
Hi Alexander,
> On Wed, Jun 3, 2026 at 7:43 PM Yunseong Kim <yunseong.kim@est.tech> wrote:
>>
>> Introduces a new KCOV exetened feature that captures function arguments and
>> return values at kernel function boundaries, enabling per-process visibility
>> into runtime dataflow.
>
> Some high-level comments:
> - Make sure your code can run on every platform supported by kcov (namely ARM64)
> - Check out Sashiko findings:
> https://sashiko.dev/#/patchset/20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4%40est.tech,
I handled those parts that seemed problematic from sashiko's review.
> at least some of them seem to make sense
> - Please consolidate changes to the same file into a single patch
> - There seem to be two tools (one in C and one in Python) with
> overlapping functionality, can you keep only one?
I revised this part in v2.
> - The test modules seem to be used only in manual testing. Can you
> convert them to kselftests or remove them?
Thanks again for yout guide, I've updated it to v2.
> - At this point, long dashes in the kernel codebase are quite rare,
> and I don't see a reason to add more.
I checked that the v2 series patchset was removed, using long dashes.
Best regards,
Yunseong
^ permalink raw reply
* Re: [RFC PATCH v2 03/14] kcov: add barriers to recursion guard in kcov_df_write
From: Alexander Potapenko @ 2026-06-12 7:30 UTC (permalink / raw)
To: Yunseong Kim
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Dmitry Vyukov, Andrew Morton, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Nathan Chancellor, Nicolas Schier,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Shuah Khan, Jonathan Corbet, Shuah Khan, linux-kernel, kasan-dev,
rust-for-linux, linux-kbuild, llvm, linux-mm, linux-kselftest,
workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-3-0a261da3987c@est.tech>
On Thu, Jun 11, 2026 at 6:21 PM Yunseong Kim <yunseong.kim@est.tech> wrote:
>
> The recursion guard (bit-31 of kcov_df_seq) prevents reentry when
> copy_from_kernel_nofault() or other called functions are instrumented
> with INSTRUMENT_ALL. Without compiler barriers, the guard set/clear
> can be reordered relative to the function body, making the protection
> ineffective under optimization.
>
> Add barrier() after setting the guard and before clearing it, ensuring
> the compiler does not move instrumented operations outside the guarded
> region.
>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
> ---
> kernel/kcov_dataflow.c | 2 ++
Please merge this patch into the one introducing kcov_dataflow.c
> 1 file changed, 2 insertions(+)
>
> diff --git a/kernel/kcov_dataflow.c b/kernel/kcov_dataflow.c
> index df7e8bf70bfa..5248293280d5 100644
> --- a/kernel/kcov_dataflow.c
> +++ b/kernel/kcov_dataflow.c
> @@ -86,6 +86,7 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
> if (t->kcov_df_seq & (1U << 31))
> return;
> t->kcov_df_seq |= (1U << 31);
> + barrier();
Please make sure barriers have comments explaining which barriers they
pair with (see kernel/kcov.c)
^ permalink raw reply
* Re: [RFC PATCH v2 02/14] kcov: fix INIT_TRACK race in kcov_dataflow
From: Yunseong Kim @ 2026-06-12 7:25 UTC (permalink / raw)
To: Alexander Potapenko
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Dmitry Vyukov, Andrew Morton, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Nathan Chancellor, Nicolas Schier,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Shuah Khan, Jonathan Corbet, Shuah Khan, linux-kernel, kasan-dev,
rust-for-linux, linux-kbuild, llvm, linux-mm, linux-kselftest,
workflows, linux-doc, Yeoreum Yun, sashiko-bot
In-Reply-To: <CAG_fn=V1+_xLgCZgdLnT7Y-muRO0CXkrNKkC8AzrqzWoL4eR8w@mail.gmail.com>
Hi Alexander,
> On Thu, Jun 11, 2026 at 6:21 PM Yunseong Kim <yunseong.kim@est.tech> wrote:
>>
>> [snip...]
>> Reported-by: sashiko-bot <sashiko-bot@kernel.org>
>> Closes: https://sashiko.dev/#/patchset/20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4%40est.tech
>> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
>
> Can we please avoid this?
> kcov_dataflow.c is being introduced in the same series, there is no
> need to send a buggy commit and a follow-up fix - just squash the two
> together and note the changes after Signed-off-by: separated by a
> triple dash.
Thank you for your guide. I'll remove it in the next patch set.
Best regards,
Yunseong
^ permalink raw reply
* Re: [RFC PATCH v2 02/14] kcov: fix INIT_TRACK race in kcov_dataflow
From: Alexander Potapenko @ 2026-06-12 6:55 UTC (permalink / raw)
To: Yunseong Kim
Cc: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Dmitry Vyukov, Andrew Morton, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Nathan Chancellor, Nicolas Schier,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Shuah Khan, Jonathan Corbet, Shuah Khan, linux-kernel, kasan-dev,
rust-for-linux, linux-kbuild, llvm, linux-mm, linux-kselftest,
workflows, linux-doc, Yeoreum Yun, sashiko-bot
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-2-0a261da3987c@est.tech>
On Thu, Jun 11, 2026 at 6:21 PM Yunseong Kim <yunseong.kim@est.tech> wrote:
>
> Two threads calling KCOV_DF_INIT_TRACK concurrently could both observe
> df->area == NULL, drop the lock to allocate, and then both assign their
> allocation to df->area, leaking one buffer.
>
> Fix by rechecking df->area after re-acquiring the lock. If another
> thread won the race, free the allocation and return -EBUSY. This
> matches the pattern used by KCOV_INIT_TRACE in kernel/kcov.c.
>
> Reported-by: sashiko-bot <sashiko-bot@kernel.org>
> Closes: https://sashiko.dev/#/patchset/20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4%40est.tech
> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
Can we please avoid this?
kcov_dataflow.c is being introduced in the same series, there is no
need to send a buggy commit and a follow-up fix - just squash the two
together and note the changes after Signed-off-by: separated by a
triple dash.
^ permalink raw reply
* [RFC PATCH 2/2] kasan: hw_tags: Add boot option to elide free time poisoning
From: Dev Jain @ 2026-06-12 4:44 UTC (permalink / raw)
To: ryabinin.a.a, akpm, corbet
Cc: Dev Jain, glider, andreyknvl, dvyukov, vincenzo.frascino,
kasan-dev, linux-mm, linux-kernel, skhan, workflows, linux-doc,
linux-arm-kernel, ryan.roberts, anshuman.khandual, kaleshsingh,
21cnbao, david, will, catalin.marinas
In-Reply-To: <20260612044425.763060-1-dev.jain@arm.com>
Introduce a boot option to tag only at allocation time of the objects. This
reduces KASAN MTE overhead, the tradeoff being reduced ability
of catching bugs.
Now, when a memory object will be freed, it will retain the random tag it
had at allocation time. This compromises on catching UAF bugs, till the
time the object is not reallocated.
Hence, not catching "use-after-free-before-reallocation" and not catching
"double-free" will be the compromise for reduced KASAN overhead.
Keep this as a boot time feature to prevent building two kernel images.
To implement the feature, we need to effectively render kasan_poison()
redundant for hw tags case, but keep it working in the case where it is
used not in an object-freeing code path, but the redzoning path (which
means, poisoning the tail end of a vmalloc or kmalloc allocation).
We achieve this by overloading the poison values for the hw tags case: we
define the four poison values as 0x0E, 0x1E, 0x2E, 0x3E. In kasan_poison(),
if we arrive with KASAN_SLAB_REDZONE or KASAN_PAGE_REDZONE, do a bitwise
OR on the value of the tag to make it equal to KASAN_TAG_INVALID.
If not, then, if init is true, zero out the memory and bail out.
Signed-off-by: Dev Jain <dev.jain@arm.com>
---
Documentation/dev-tools/kasan.rst | 4 +++
mm/kasan/hw_tags.c | 43 ++++++++++++++++++++++++++++++-
mm/kasan/kasan.h | 23 ++++++++++++++++-
3 files changed, 68 insertions(+), 2 deletions(-)
diff --git a/Documentation/dev-tools/kasan.rst b/Documentation/dev-tools/kasan.rst
index 4968b2aa60c80..b0c30584b5062 100644
--- a/Documentation/dev-tools/kasan.rst
+++ b/Documentation/dev-tools/kasan.rst
@@ -146,6 +146,10 @@ disabling KASAN altogether or controlling its features:
- ``kasan.vmalloc=off`` or ``=on`` disables or enables tagging of vmalloc
allocations (default: ``on``).
+- ``kasan.tag_only_on_alloc=off`` or ``=on`` disables or enables skipping
+ free-time tagging (poisoning) while keeping allocation-time tagging enabled
+ (default: ``off``).
+
- ``kasan.page_alloc.sample=<sampling interval>`` makes KASAN tag only every
Nth page_alloc allocation with the order equal or greater than
``kasan.page_alloc.sample.order``, where N is the value of the ``sample``
diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
index c1a2b48808ed7..a392e34d11e3a 100644
--- a/mm/kasan/hw_tags.c
+++ b/mm/kasan/hw_tags.c
@@ -41,9 +41,16 @@ enum kasan_arg_vmalloc {
KASAN_ARG_VMALLOC_ON,
};
+enum kasan_arg_tag_only_on_alloc {
+ KASAN_ARG_TAG_ONLY_ON_ALLOC_DEFAULT,
+ KASAN_ARG_TAG_ONLY_ON_ALLOC_OFF,
+ KASAN_ARG_TAG_ONLY_ON_ALLOC_ON,
+};
+
static enum kasan_arg kasan_arg __ro_after_init;
static enum kasan_arg_mode kasan_arg_mode __ro_after_init;
static enum kasan_arg_vmalloc kasan_arg_vmalloc __initdata;
+static enum kasan_arg_tag_only_on_alloc kasan_arg_tag_only_on_alloc __initdata;
/*
* Whether the selected mode is synchronous, asynchronous, or asymmetric.
@@ -63,6 +70,10 @@ EXPORT_SYMBOL_GPL(kasan_flag_vmalloc);
/* Whether to check write accesses only. */
static bool kasan_flag_write_only = false;
+/* Whether to skip free-time tagging. */
+DEFINE_STATIC_KEY_FALSE(kasan_flag_tag_only_on_alloc);
+EXPORT_SYMBOL_GPL(kasan_flag_tag_only_on_alloc);
+
#define PAGE_ALLOC_SAMPLE_DEFAULT 1
#define PAGE_ALLOC_SAMPLE_ORDER_DEFAULT 3
@@ -154,6 +165,23 @@ static int __init early_kasan_flag_write_only(char *arg)
}
early_param("kasan.write_only", early_kasan_flag_write_only);
+/* kasan.tag_only_on_alloc=off/on */
+static int __init early_kasan_flag_tag_only_on_alloc(char *arg)
+{
+ if (!arg)
+ return -EINVAL;
+
+ if (!strcmp(arg, "off"))
+ kasan_arg_tag_only_on_alloc = KASAN_ARG_TAG_ONLY_ON_ALLOC_OFF;
+ else if (!strcmp(arg, "on"))
+ kasan_arg_tag_only_on_alloc = KASAN_ARG_TAG_ONLY_ON_ALLOC_ON;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+early_param("kasan.tag_only_on_alloc", early_kasan_flag_tag_only_on_alloc);
+
static inline const char *kasan_mode_info(void)
{
if (kasan_mode == KASAN_MODE_ASYNC)
@@ -270,14 +298,27 @@ void __init kasan_init_hw_tags(void)
break;
}
+ switch (kasan_arg_tag_only_on_alloc) {
+ case KASAN_ARG_TAG_ONLY_ON_ALLOC_DEFAULT:
+ /* Default is specified by kasan_flag_tag_only_on_alloc. */
+ break;
+ case KASAN_ARG_TAG_ONLY_ON_ALLOC_OFF:
+ static_branch_disable(&kasan_flag_tag_only_on_alloc);
+ break;
+ case KASAN_ARG_TAG_ONLY_ON_ALLOC_ON:
+ static_branch_enable(&kasan_flag_tag_only_on_alloc);
+ break;
+ }
+
kasan_init_tags();
/* KASAN is now initialized, enable it. */
kasan_enable();
- pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s, write_only=%s)\n",
+ pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, tag_only_on_alloc=%s, stacktrace=%s, write_only=%s)\n",
kasan_mode_info(),
str_on_off(kasan_vmalloc_enabled()),
+ str_on_off(kasan_tag_only_on_alloc_enabled()),
str_on_off(kasan_stack_collection_enabled()),
str_on_off(kasan_flag_write_only));
}
diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
index fc9169a547662..4fa8abb312faa 100644
--- a/mm/kasan/kasan.h
+++ b/mm/kasan/kasan.h
@@ -33,6 +33,7 @@ static inline bool kasan_stack_collection_enabled(void)
#include "../slab.h"
DECLARE_STATIC_KEY_TRUE(kasan_flag_vmalloc);
+DECLARE_STATIC_KEY_FALSE(kasan_flag_tag_only_on_alloc);
enum kasan_mode {
KASAN_MODE_SYNC,
@@ -52,6 +53,11 @@ static inline bool kasan_vmalloc_enabled(void)
return static_branch_likely(&kasan_flag_vmalloc);
}
+static inline bool kasan_tag_only_on_alloc_enabled(void)
+{
+ return static_branch_unlikely(&kasan_flag_tag_only_on_alloc);
+}
+
static inline bool kasan_async_fault_possible(void)
{
return kasan_mode == KASAN_MODE_ASYNC || kasan_mode == KASAN_MODE_ASYMM;
@@ -145,12 +151,17 @@ static inline bool kasan_requires_meta(void)
#define KASAN_SLAB_REDZONE 0xFC /* redzone for slab object */
#define KASAN_SLAB_FREE 0xFB /* freed slab object */
#define KASAN_VMALLOC_INVALID 0xF8 /* inaccessible space in vmap area */
+#elif defined(CONFIG_KASAN_HW_TAGS)
+#define KASAN_PAGE_FREE 0x0E
+#define KASAN_PAGE_REDZONE 0x1E
+#define KASAN_SLAB_REDZONE 0x2E
+#define KASAN_SLAB_FREE 0x3E
#else
#define KASAN_PAGE_FREE KASAN_TAG_INVALID
#define KASAN_PAGE_REDZONE KASAN_TAG_INVALID
#define KASAN_SLAB_REDZONE KASAN_TAG_INVALID
#define KASAN_SLAB_FREE KASAN_TAG_INVALID
-#define KASAN_VMALLOC_INVALID KASAN_TAG_INVALID /* only used for SW_TAGS */
+#define KASAN_VMALLOC_INVALID KASAN_TAG_INVALID
#endif
#ifdef CONFIG_KASAN_GENERIC
@@ -478,6 +489,16 @@ static inline u8 kasan_random_tag(void) { return 0; }
static inline void kasan_poison(const void *addr, size_t size, u8 value, bool init)
{
+ if (kasan_tag_only_on_alloc_enabled()) {
+ if ((value != KASAN_SLAB_REDZONE) && (value != KASAN_PAGE_REDZONE)) {
+ if (init)
+ memset((void *)kasan_reset_tag(addr), 0, size);
+ return;
+ }
+ }
+
+ value |= 0xF0;
+
if (WARN_ON((unsigned long)addr & KASAN_GRANULE_MASK))
return;
if (WARN_ON(size & KASAN_GRANULE_MASK))
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 1/2] kasan: hw_tags: Use KASAN_PAGE_REDZONE for vmalloc redzoning
From: Dev Jain @ 2026-06-12 4:44 UTC (permalink / raw)
To: ryabinin.a.a, akpm, corbet
Cc: Dev Jain, glider, andreyknvl, dvyukov, vincenzo.frascino,
kasan-dev, linux-mm, linux-kernel, skhan, workflows, linux-doc,
linux-arm-kernel, ryan.roberts, anshuman.khandual, kaleshsingh,
21cnbao, david, will, catalin.marinas
In-Reply-To: <20260612044425.763060-1-dev.jain@arm.com>
In preparation for adding "tag only on alloc" boot time option, use
KASAN_PAGE_REDZONE instead of KASAN_TAG_INVALID for poisoning the tail end
of the vmalloc allocation.
Although both values are the same for hw tags, KASAN_SLAB_REDZONE is used
for poisoning the tail end of a kmalloc object allocation, so maintain
the pattern.
Signed-off-by: Dev Jain <dev.jain@arm.com>
---
mm/kasan/hw_tags.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
index cbef5e450954e..c1a2b48808ed7 100644
--- a/mm/kasan/hw_tags.c
+++ b/mm/kasan/hw_tags.c
@@ -375,7 +375,7 @@ void *__kasan_unpoison_vmalloc(const void *start, unsigned long size,
redzone_start = round_up((unsigned long)start + size,
KASAN_GRANULE_SIZE);
redzone_size = round_up(redzone_start, PAGE_SIZE) - redzone_start;
- kasan_poison((void *)redzone_start, redzone_size, KASAN_TAG_INVALID,
+ kasan_poison((void *)redzone_start, redzone_size, KASAN_PAGE_REDZONE,
flags & KASAN_VMALLOC_INIT);
/*
--
2.43.0
^ permalink raw reply related
* [RFC PATCH 0/2] kasan: hw_tags: Add option to tag only at allocation time
From: Dev Jain @ 2026-06-12 4:44 UTC (permalink / raw)
To: ryabinin.a.a, akpm, corbet
Cc: Dev Jain, glider, andreyknvl, dvyukov, vincenzo.frascino,
kasan-dev, linux-mm, linux-kernel, skhan, workflows, linux-doc,
linux-arm-kernel, ryan.roberts, anshuman.khandual, kaleshsingh,
21cnbao, david, will, catalin.marinas
Introduce a boot option to tag only at allocation time of the objects. This
reduces KASAN MTE overhead, the tradeoff being reduced ability of
catching bugs.
Now, when a memory object will be freed, it will retain the random tag it
had at allocation time. This compromises on catching UAF bugs, till the
time the object is not reallocated, at which point it will have a new
random tag.
Hence, not catching "use-after-free-before-reallocation" and not catching
"double-free" will be the compromise for reduced KASAN overhead.
This is an RFC because we are not clear about the performance benefit.
Android folks, please help with testing!
---
Applies on Linus master (9716c086c8e8).
Dev Jain (2):
kasan: hw_tags: Use KASAN_PAGE_REDZONE for vmalloc redzoning
kasan: hw_tags: Add boot option to elide free time poisoning
Documentation/dev-tools/kasan.rst | 4 +++
mm/kasan/hw_tags.c | 45 +++++++++++++++++++++++++++++--
mm/kasan/kasan.h | 23 +++++++++++++++-
3 files changed, 69 insertions(+), 3 deletions(-)
--
2.43.0
^ permalink raw reply
* [RFC PATCH v2 14/14] Documentation: add kcov-dataflow.rst
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
Add documentation for the KCOV-Dataflow subsystem covering:
- Prerequisites and Kconfig options
- Per-module and per-directory instrumentation
- Data collection example with buffer parsing
- Ring buffer TLV record format
- Safety properties
- Ioctl interface reference
- Compatibility with legacy KCOV
- Rust module support via post-compilation pipeline
- Fork/child process tracing pattern
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
Documentation/dev-tools/index.rst | 1 +
Documentation/dev-tools/kcov-dataflow.rst | 321 +++++++++++++++++++++++++
tools/testing/selftests/kcov_dataflow/Makefile | 2 +-
3 files changed, 323 insertions(+), 1 deletion(-)
diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 59cbb77b33ff..541c58cc65ea 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -24,6 +24,7 @@ Documentation/process/debugging/index.rst
context-analysis
sparse
kcov
+ kcov-dataflow
gcov
kasan
kmsan
diff --git a/Documentation/dev-tools/kcov-dataflow.rst b/Documentation/dev-tools/kcov-dataflow.rst
new file mode 100644
index 000000000000..603c83946d12
--- /dev/null
+++ b/Documentation/dev-tools/kcov-dataflow.rst
@@ -0,0 +1,321 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+KCOV-Dataflow: function argument and return value extraction
+=============================================================
+
+KCOV-Dataflow captures function arguments and return values, including
+automatic struct field decomposition, at instrumented kernel function
+boundaries. It provides per-task, lock-free ring buffers accessible via
+``mmap()``, enabling data-flow-aware fuzzing and post-mortem contract
+verification.
+
+Unlike KCOV's ``trace-pc`` which reports *which* code executed,
+KCOV-Dataflow reports *what values* were passed and returned. This is
+a completely separate device from ``/sys/kernel/debug/kcov``.
+
+Prerequisites
+-------------
+
+KCOV-Dataflow requires Clang/LLVM with the ``trace-args`` and
+``trace-ret`` SanitizerCoverage extensions. Standard (unpatched)
+compilers will not expose these Kconfig options.
+
+To enable KCOV-Dataflow, configure the kernel with::
+
+ CONFIG_KCOV=y
+ CONFIG_KCOV_DATAFLOW_ARGS=y
+ CONFIG_KCOV_DATAFLOW_RET=y
+
+Optional: instrument the entire kernel (significant overhead)::
+
+ CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y
+
+Coverage data becomes accessible once debugfs is mounted::
+
+ mount -t debugfs none /sys/kernel/debug
+
+Per-module instrumentation
+--------------------------
+
+To instrument a specific module, add to its Makefile::
+
+ KCOV_DATAFLOW_my_module.o := y
+
+For example, to instrument the Android binder driver::
+
+ # drivers/android/Makefile
+ KCOV_DATAFLOW_binder.o := y
+ KCOV_DATAFLOW_binder_alloc.o := y
+
+To instrument an entire directory, set the variable without a filename::
+
+ # fs/Makefile
+ KCOV_DATAFLOW := y
+
+The build system automatically adds the required compiler flags
+(``-fsanitize-coverage=trace-args,trace-ret``). Debug info is provided
+by ``CONFIG_DEBUG_INFO`` which is a Kconfig dependency.
+
+Data collection
+---------------
+
+The following program demonstrates how to collect function argument and
+return value data for a single syscall:
+
+.. code-block:: c
+
+ #include <stdio.h>
+ #include <stdint.h>
+ #include <stdlib.h>
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
+ #include <sys/mman.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+
+ #define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+ #define KCOV_DF_ENABLE _IO('d', 100)
+ #define KCOV_DF_DISABLE _IO('d', 101)
+ #define BUF_SIZE (1 << 20) /* 1M words = 8MB */
+
+ int main(void)
+ {
+ int fd;
+ uint64_t *buf, n, i;
+
+ fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ if (fd == -1)
+ perror("open"), exit(1);
+
+ /* Allocate buffer (size in u64 words). */
+ if (ioctl(fd, KCOV_DF_INIT_TRACE, BUF_SIZE))
+ perror("ioctl(INIT)"), exit(1);
+
+ /* Map the buffer into user space. */
+ buf = (uint64_t *)mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (buf == MAP_FAILED)
+ perror("mmap"), exit(1);
+
+ /* Enable data-flow collection for this task. */
+ if (ioctl(fd, KCOV_DF_ENABLE, 0))
+ perror("ioctl(ENABLE)"), exit(1);
+
+ /* Reset counter. */
+ __atomic_store_n(&buf[0], 0, __ATOMIC_RELAXED);
+
+ /* === Trigger syscall(s) here === */
+ read(-1, NULL, 0);
+
+ /* Read how many words were written. */
+ n = __atomic_load_n(&buf[0], __ATOMIC_RELAXED);
+
+ /* Parse TLV records. */
+ i = 1;
+ while (i + 3 < n) {
+ uint64_t type_seq = buf[i];
+ uint64_t pc = buf[i + 1];
+ uint64_t meta = buf[i + 2];
+ uint32_t type = (type_seq >> 28) & 0xF;
+ uint32_t num_vals = (type_seq >> 24) & 0xF;
+ uint32_t seq = type_seq & 0x00FFFFFF;
+ uint32_t arg_idx = (meta >> 56) & 0xFF;
+ uint32_t size = (meta >> 48) & 0xFF;
+
+ if (type_seq >> 32 || (type != 0xE && type != 0xF)) {
+ i++;
+ continue;
+ }
+ if (!num_vals)
+ num_vals = 1;
+
+ printf("[%s] seq=%u pc=0x%lx arg_idx=%u size=%u val=0x%lx\n",
+ type == 0xE ? "ENTRY" : "RET",
+ seq, pc, arg_idx, size, buf[i + 3]);
+ i += 3 + num_vals;
+ }
+
+ if (ioctl(fd, KCOV_DF_DISABLE, 0))
+ perror("ioctl(DISABLE)"), exit(1);
+
+ munmap(buf, BUF_SIZE * sizeof(uint64_t));
+ close(fd);
+ return 0;
+ }
+
+Ring buffer format
+------------------
+
+The buffer is an array of ``u64`` words::
+
+ buf[0]: atomic counter -- total words written
+
+Each record occupies 3 + N words:
+
+.. list-table::
+ :header-rows: 1
+
+ * - Offset
+ - Field
+ - Description
+ * - 0
+ - type_and_seq
+ - bits[31:28] = 0xE (entry) or 0xF (return), bits[27:24] = num_vals,
+ bits[23:0] = sequence number
+ * - 1
+ - pc
+ - Instrumented function address
+ * - 2
+ - meta
+ - bits[63:56] = arg_idx (0 for return), bits[55:48] = size in bytes,
+ bits[47:0] = raw pointer value
+ * - 3..N
+ - field_val[0..N]
+ - Struct field values or single scalar
+
+Magic values:
+
+- ``0xBADADD85``: field read failed (pointer was invalid/freed/poisoned)
+
+Safety
+------
+
+- Callbacks are ``notrace``, ``__no_sanitize_coverage``, ``noinline``
+ to prevent recursion.
+- All pointer reads use ``copy_from_kernel_nofault()`` -- survives
+ freed, poisoned, or unmapped memory.
+- An ``in_task()`` guard rejects calls from hardirq/softirq/NMI context,
+ preventing reentrant buffer corruption.
+- No ``printk`` or allocation in the data path.
+- When not enabled for a task, overhead is a single boolean check.
+
+Ioctl interface
+---------------
+
+.. list-table::
+ :header-rows: 1
+
+ * - Command
+ - Value
+ - Description
+ * - KCOV_DF_INIT_TRACK
+ - ``_IOR('d', 1, unsigned long)``
+ - Allocate buffer (size in u64 words)
+ * - KCOV_DF_ENABLE
+ - ``_IO('d', 100)``
+ - Start collection for current task
+ * - KCOV_DF_DISABLE
+ - ``_IO('d', 101)``
+ - Stop collection
+
+Compatibility
+-------------
+
+KCOV-Dataflow is completely independent from legacy KCOV:
+
+- Separate device: ``/sys/kernel/debug/kcov_dataflow``
+- Separate ioctl namespace (``'d'`` vs ``'c'``)
+- Separate per-task buffer
+- Both can be used simultaneously without interference
+- syzkaller and other KCOV users are unaffected
+
+Rust module support
+-------------------
+
+Rust kernel modules are supported via a post-compilation pipeline::
+
+ rustc --emit=llvm-ir -g module.rs
+ opt -passes=sancov-module \
+ -sanitizer-coverage-trace-args \
+ -sanitizer-coverage-trace-ret module.ll -S -o module_inst.ll
+ llc -filetype=obj module_inst.ll -o module.o
+
+Selftests
+---------
+
+Automated tests and visualization tools are in
+``tools/testing/selftests/kcov_dataflow/``::
+
+ # Automated ioctl interface test (TAP output):
+ make -C tools/testing/selftests/kcov_dataflow
+ vng --user root --exec \
+ tools/testing/selftests/kcov_dataflow/user_ioctl/user_ioctl
+
+ # Load a test module and view captured records:
+ make LLVM=1 CC=clang M=tools/testing/selftests/kcov_dataflow/eight_args_c modules
+ vng --user root --exec \
+ "python3 tools/testing/selftests/kcov_dataflow/trigger-view.py \
+ eight_args_c -C 8 --ko \
+ tools/testing/selftests/kcov_dataflow/eight_args_c/eight_args_c.ko"
+
+ # Binderfs ioctl capture test (requires CONFIG_ANDROID_BINDER_IPC):
+ make -C tools/testing/selftests/kcov_dataflow/binderfs
+ vng --user root --exec \
+ tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test
+
+See ``tools/testing/selftests/kcov_dataflow/README.rst`` for details.
+
+Tracing child processes
+-----------------------
+
+KCOV-Dataflow is per-task: after ``fork()``, the child does not inherit
+the enabled state. To trace child processes, re-enable on the inherited
+file descriptor in the child before ``exec()``. The ``mmap``'d buffer is
+shared (``MAP_SHARED``), so both parent and child write to the same ring
+buffer atomically.
+
+.. code-block:: c
+
+ #include <stdio.h>
+ #include <stdint.h>
+ #include <stdlib.h>
+ #include <sys/ioctl.h>
+ #include <sys/mman.h>
+ #include <sys/wait.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+
+ #define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+ #define KCOV_DF_ENABLE _IO('d', 100)
+ #define KCOV_DF_DISABLE _IO('d', 101)
+ #define BUF_SIZE (1 << 20)
+
+ int main(int argc, char **argv)
+ {
+ int fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ ioctl(fd, KCOV_DF_INIT_TRACE, BUF_SIZE);
+ uint64_t *buf = mmap(NULL, BUF_SIZE * 8,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ /* Enable for parent task. */
+ ioctl(fd, KCOV_DF_ENABLE, 0);
+ __atomic_store_n(&buf[0], 0, __ATOMIC_RELAXED);
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ /*
+ * Child: re-enable on inherited fd.
+ * The shared mmap buffer receives records from both tasks.
+ */
+ ioctl(fd, KCOV_DF_ENABLE, 0);
+ execvp(argv[1], &argv[1]);
+ _exit(1);
+ }
+
+ waitpid(pid, NULL, 0);
+ ioctl(fd, KCOV_DF_DISABLE, 0);
+
+ uint64_t n = __atomic_load_n(&buf[0], __ATOMIC_RELAXED);
+ printf("Captured %lu words from parent + child\n", n);
+
+ munmap(buf, BUF_SIZE * 8);
+ close(fd);
+ return 0;
+ }
+
+Note: the child's ``ioctl(fd, KCOV_DF_ENABLE)`` will fail if the parent
+has not yet called ``KCOV_DF_DISABLE``, because only one task can be
+associated with a descriptor at a time. For true multi-process tracing,
+open a separate ``kcov_dataflow`` fd per child, or disable in the parent
+before the child enables (as shown above -- the parent is blocked in
+``waitpid`` so it generates no records during that time anyway).
diff --git a/tools/testing/selftests/kcov_dataflow/Makefile b/tools/testing/selftests/kcov_dataflow/Makefile
index 6412c90edfa1..9691b41ffd3e 100644
--- a/tools/testing/selftests/kcov_dataflow/Makefile
+++ b/tools/testing/selftests/kcov_dataflow/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := user_ioctl/user_ioctl
-TEST_PROGS := run_eight_args_c.sh run_rust_ffi_contract.sh
+TEST_PROGS := run_eight_args_c.sh run_eight_args_rust.sh run_rust_ffi_contract.sh
include ../lib.mk
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 13/14] selftests/kcov_dataflow: add binderfs ioctl capture test
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
Exercise the binder driver via binderfs with kcov_dataflow recording
active. Verifies that function argument records are captured at binder
ioctl boundaries (BINDER_VERSION, BINDER_SET_MAX_THREADS).
Requires CONFIG_ANDROID_BINDER_IPC=y and CONFIG_ANDROID_BINDERFS=y.
Gracefully skips if binderfs is not available.
Build and test:
export PATH=$PWD/../llvm-project/build/bin:$PATH
vng --build \
--configitem CONFIG_KCOV=y \
--configitem CONFIG_KCOV_DATAFLOW_ARGS=y \
--configitem CONFIG_KCOV_DATAFLOW_RET=y \
--configitem CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y \
--configitem CONFIG_DEBUG_INFO=y \
--configitem CONFIG_ANDROID_BINDER_IPC=y \
--configitem CONFIG_ANDROID_BINDERFS=y \
LLVM=1 CC=clang
make -C tools/testing/selftests/kcov_dataflow/binderfs
vng --user root --exec \
tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test
Result:
TAP version 13
1..3
ok 1 kcov_dataflow.binderfs_setup
ok 2 kcov_dataflow.binderfs_captured # 636 words
ok 3 kcov_dataflow.binderfs_valid_records
# Totals: pass:3 fail:0 skip:0
#
# Captured call records:
# ENTRY pc=0xffffffff... arg=0x4 (fd)
# ENTRY pc=0xffffffff... arg=0xc0046209 (BINDER_VERSION)
# ENTRY pc=0xffffffff... arg=0x0 (binder_get_thread)
# RET pc=0xffffffff... ret=0x0 (success)
# ENTRY pc=0xffffffff... arg=0x40046205 (SET_MAX_THREADS)
# ENTRY pc=0xffffffff... arg=0x4 (_copy_from_user size)
Cc: Alexander Potapenko <glider@google.com>
Assisted-by: Claude:claude-opus-4-6 [kiro-chat]
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
tools/testing/selftests/kcov_dataflow/.gitignore | 1 +
.../selftests/kcov_dataflow/binderfs/Makefile | 4 +
.../kcov_dataflow/binderfs/binderfs_test.c | 177 +++++++++++++++++++++
.../selftests/kcov_dataflow/run_binderfs.sh | 13 ++
4 files changed, 195 insertions(+)
diff --git a/tools/testing/selftests/kcov_dataflow/.gitignore b/tools/testing/selftests/kcov_dataflow/.gitignore
index f71fc89580f8..da4c189ad3be 100644
--- a/tools/testing/selftests/kcov_dataflow/.gitignore
+++ b/tools/testing/selftests/kcov_dataflow/.gitignore
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
user_ioctl/user_ioctl
+binderfs/binderfs_test
*.o
*.ko
*.mod
diff --git a/tools/testing/selftests/kcov_dataflow/binderfs/Makefile b/tools/testing/selftests/kcov_dataflow/binderfs/Makefile
new file mode 100644
index 000000000000..9f1588512dba
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/binderfs/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := binderfs_test
+CFLAGS += -Wall -O2
+include ../../lib.mk
diff --git a/tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test.c b/tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test.c
new file mode 100644
index 000000000000..ce9b49aa0b9f
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * binderfs selftest for kcov_dataflow
+ *
+ * Exercises the binder driver via binderfs with kcov_dataflow recording
+ * active, then verifies that function argument records were captured at
+ * binder ioctl boundaries.
+ *
+ * Requires: CONFIG_ANDROID_BINDER_IPC=y (or _RUST), CONFIG_ANDROID_BINDERFS=y
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <linux/android/binder.h>
+#include <linux/android/binderfs.h>
+
+#define KCOV_DF_INIT_TRACK _IOR('d', 1, unsigned long)
+#define KCOV_DF_ENABLE _IO('d', 100)
+#define KCOV_DF_DISABLE _IO('d', 101)
+
+#define BUF_SIZE (1 << 20)
+#define BINDERFS_PATH "/tmp/binderfs_test"
+#define BINDER_DEV BINDERFS_PATH "/my_binder"
+
+static int setup_binderfs(void)
+{
+ struct binderfs_device dev = {};
+
+ mkdir(BINDERFS_PATH, 0755);
+
+ if (mount("binder", BINDERFS_PATH, "binder", 0, NULL)) {
+ if (errno == ENODEV || errno == ENOENT) {
+ printf("SKIP: binderfs not available\n");
+ return -1;
+ }
+ perror("mount binderfs");
+ return -1;
+ }
+
+ /* Create a binder device via BINDER_CTL_ADD ioctl */
+ int ctl_fd;
+
+ ctl_fd = open(BINDERFS_PATH "/binder-control", O_RDONLY);
+ if (ctl_fd < 0) {
+ perror("open binder-control");
+ umount(BINDERFS_PATH);
+ return -1;
+ }
+
+ strcpy(dev.name, "my_binder");
+ if (ioctl(ctl_fd, BINDER_CTL_ADD, &dev) && errno != EEXIST) {
+ perror("BINDER_CTL_ADD");
+ close(ctl_fd);
+ umount(BINDERFS_PATH);
+ return -1;
+ }
+ close(ctl_fd);
+ return 0;
+}
+
+static void cleanup_binderfs(void)
+{
+ umount(BINDERFS_PATH);
+ rmdir(BINDERFS_PATH);
+}
+
+int main(void)
+{
+ uint64_t *buf;
+ int df_fd, binder_fd;
+ uint64_t total;
+ int valid = 0;
+
+ printf("TAP version 13\n");
+ printf("1..3\n");
+
+ /* Setup binderfs */
+ if (setup_binderfs()) {
+ printf("ok 1 # SKIP binderfs not available\n");
+ printf("ok 2 # SKIP\n");
+ printf("ok 3 # SKIP\n");
+ return 0;
+ }
+
+ /* Open kcov_dataflow */
+ df_fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ if (df_fd < 0) {
+ printf("not ok 1 cannot open kcov_dataflow\n");
+ cleanup_binderfs();
+ return 1;
+ }
+
+ if (ioctl(df_fd, KCOV_DF_INIT_TRACK, BUF_SIZE)) {
+ printf("not ok 1 INIT_TRACK failed\n");
+ close(df_fd);
+ cleanup_binderfs();
+ return 1;
+ }
+
+ buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, df_fd, 0);
+ if (buf == MAP_FAILED) {
+ printf("not ok 1 mmap failed\n");
+ close(df_fd);
+ cleanup_binderfs();
+ return 1;
+ }
+
+ printf("ok 1 kcov_dataflow.binderfs_setup\n");
+
+ /* Open binder device */
+ binder_fd = open(BINDER_DEV, O_RDWR | O_CLOEXEC);
+ if (binder_fd < 0) {
+ printf("not ok 2 cannot open %s: %s\n", BINDER_DEV,
+ strerror(errno));
+ munmap(buf, BUF_SIZE * sizeof(uint64_t));
+ close(df_fd);
+ cleanup_binderfs();
+ return 1;
+ }
+
+ /* Enable recording and exercise binder ioctls */
+ ioctl(df_fd, KCOV_DF_ENABLE, 0);
+ __atomic_store_n(&buf[0], 0, __ATOMIC_RELAXED);
+
+ /* BINDER_VERSION - simple ioctl that exercises the binder path */
+ struct binder_version ver = {};
+
+ ioctl(binder_fd, BINDER_VERSION, &ver);
+
+ /* BINDER_SET_MAX_THREADS */
+ uint32_t max_threads = 4;
+
+ ioctl(binder_fd, BINDER_SET_MAX_THREADS, &max_threads);
+
+ ioctl(df_fd, KCOV_DF_DISABLE, 0);
+
+ total = __atomic_load_n(&buf[0], __ATOMIC_RELAXED);
+ close(binder_fd);
+
+ if (total > 0)
+ printf("ok 2 kcov_dataflow.binderfs_captured # %lu words\n",
+ (unsigned long)total);
+ else
+ printf("not ok 2 kcov_dataflow.binderfs_captured # 0 words\n");
+
+ /* Verify at least one record has valid header (type 0xE or 0xF) */
+
+ if (total > 3) {
+ uint64_t hdr = buf[1];
+ uint32_t type = (hdr >> 28) & 0xF;
+
+ if (type == 0xE || type == 0xF)
+ valid = 1;
+ }
+
+ if (valid)
+ printf("ok 3 kcov_dataflow.binderfs_valid_records\n");
+ else
+ printf("not ok 3 kcov_dataflow.binderfs_valid_records\n");
+
+ printf("# Totals: pass:%d fail:%d skip:0\n",
+ valid ? 3 : 2, valid ? 0 : 1);
+
+ munmap(buf, BUF_SIZE * sizeof(uint64_t));
+ close(df_fd);
+ cleanup_binderfs();
+ return valid ? 0 : 1;
+}
diff --git a/tools/testing/selftests/kcov_dataflow/run_binderfs.sh b/tools/testing/selftests/kcov_dataflow/run_binderfs.sh
new file mode 100755
index 000000000000..5376e5350061
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/run_binderfs.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Test binderfs ioctl capture via kcov_dataflow
+DIR="$(dirname "$0")"
+BIN="$DIR/binderfs/binderfs_test"
+
+if [ ! -f "$BIN" ]; then
+ echo "SKIP: $BIN not found"
+ echo "Build: make -C tools/testing/selftests/kcov_dataflow/binderfs"
+ exit 4
+fi
+
+exec "$BIN"
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 12/14] selftests/kcov_dataflow: add rust_ffi_contract test module
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
Demonstrates FFI contract violation detection. A C callee returns
success (0) but leaves buffer=NULL, violating the postcondition
"ret==0 implies buffer!=NULL". kcov_dataflow captures struct fields
at the boundary proving the violation without a crash or KASAN report.
Test:
make LLVM=1 CC=clang \
M=tools/testing/selftests/kcov_dataflow/rust_ffi_contract modules
vng --user root --exec \
"python3 tools/testing/selftests/kcov_dataflow/trigger-view.py \
rust_ffi_contract -C 8 --ko \
tools/testing/selftests/kcov_dataflow/rust_ffi_contract/rust_ffi_contract.ko"
Result:
vfs_write(0x0)
0x0 = full_proxy_write()
full_proxy_write(0x0, 0x1, 0x0)
0x8200080 = __debugfs_file_get()
__debugfs_file_get(0x0)
0x0 = __debugfs_file_get()
0x0 = rust_ffi_trigger_write [rust_ffi_contract]()
rust_ffi_trigger_write [rust_ffi_contract](0x0, 0x1, 0x0)
ffi_alloc_buf [rust_ffi_contract](0xffffffff912288ad, 0x100, 0x0, 0x1)
0x0 = ffi_alloc_buf [rust_ffi_contract]()
_printk(0x6f635f6966663601)
vprintk(0x6f635f6966663601, 0x8)
vprintk_default(0x6f635f6966663601, 0x8)
vprintk_emit(0x0, 0xffffffff, 0x0)
0x0 = panic_on_this_cpu()
0x0 = _prb_read_valid()
0x0 = prb_read_valid()
0x0 = console_unlock()
0x3f = vprintk_emit()
0x3f = vprintk_default()
0x3f = vprintk()
0x3f = _printk()
ffi_check_result [rust_ffi_contract](0x0)
_printk(0x6f635f6966663301)
vprintk(0x6f635f6966663301, 0x8)
vprintk_default(0x6f635f6966663301, 0x8)
vprintk_emit(0x0, 0xffffffff, 0x0)
0x0 = panic_on_this_cpu()
0x0 = _prb_read_valid()
0x0 = prb_read_valid()
0x0 = console_unlock()
0x3f = vprintk_emit()
0x3f = vprintk_default()
0x3f = vprintk()
0x3f = _printk()
0xfffffff2 = ffi_check_result [rust_ffi_contract]()
0x1 = rust_ffi_trigger_write [rust_ffi_contract]()
0x1 = full_proxy_write()
0x1 = vfs_write()
0x1 = ksys_write()
0x1 = __x64_sys_write()
0x0 = fpregs_assert_state_consistent()
0xba5748 = __x64_sys_close()
file_close_fd(0x4)
0x0 = file_close_fd()
Cc: Alexander Potapenko <glider@google.com>
Assisted-by: Claude:claude-opus-4-6 [kiro-chat]
Link: https://github.com/yskzalloc/kcov-dataflow/actions
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
tools/testing/selftests/kcov_dataflow/Makefile | 2 +-
tools/testing/selftests/kcov_dataflow/README.rst | 8 ++
.../kcov_dataflow/run_rust_ffi_contract.sh | 35 +++++++
.../kcov_dataflow/rust_ffi_contract/Makefile | 3 +
.../rust_ffi_contract/rust_ffi_contract.c | 111 +++++++++++++++++++++
5 files changed, 158 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/kcov_dataflow/Makefile b/tools/testing/selftests/kcov_dataflow/Makefile
index 3a42c54e954d..6412c90edfa1 100644
--- a/tools/testing/selftests/kcov_dataflow/Makefile
+++ b/tools/testing/selftests/kcov_dataflow/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := user_ioctl/user_ioctl
-TEST_PROGS := run_eight_args_c.sh
+TEST_PROGS := run_eight_args_c.sh run_rust_ffi_contract.sh
include ../lib.mk
diff --git a/tools/testing/selftests/kcov_dataflow/README.rst b/tools/testing/selftests/kcov_dataflow/README.rst
index 61a41f3bd596..06a0c805cc74 100644
--- a/tools/testing/selftests/kcov_dataflow/README.rst
+++ b/tools/testing/selftests/kcov_dataflow/README.rst
@@ -48,3 +48,11 @@ eight_args_rust/
make LLVM=1 CC=clang M=tools/testing/selftests/kcov_dataflow/eight_args_rust modules
python3 trigger-view.py eight_args_rust
+
+rust_ffi_contract/
+ Demonstrates FFI contract violation detection. A callee returns
+ success but leaves buffer=NULL. kcov_dataflow captures struct
+ fields proving the violation::
+
+ make LLVM=1 CC=clang M=tools/testing/selftests/kcov_dataflow/rust_ffi_contract modules
+ python3 trigger-view.py rust_ffi_contract
diff --git a/tools/testing/selftests/kcov_dataflow/run_rust_ffi_contract.sh b/tools/testing/selftests/kcov_dataflow/run_rust_ffi_contract.sh
new file mode 100755
index 000000000000..8662e532296b
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/run_rust_ffi_contract.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Test rust_ffi_contract module capture via kcov_dataflow
+DIR="$(dirname "$0")"
+KO="$DIR/rust_ffi_contract/rust_ffi_contract.ko"
+
+if [ ! -f "$KO" ]; then
+ echo "SKIP: $KO not found"
+ echo "Build: make LLVM=1 CC=clang M=...rust_ffi_contract modules""
+ exit 4 # kselftest SKIP
+fi
+
+if [ ! -e /sys/kernel/debug/kcov_dataflow ]; then
+ echo "SKIP: kcov_dataflow not available"
+ exit 4
+fi
+
+OUTPUT=$(python3 "$DIR/trigger-view.py" rust_ffi_contract --ko "$KO" --raw 2>&1)
+RC=$?
+
+if [ $RC -ne 0 ]; then
+ echo "FAIL: trigger-and-view exited with $RC"
+ echo "$OUTPUT"
+ exit 1
+fi
+
+RECORDS=$(echo "$OUTPUT" | grep -c "^\[ENTRY\]\|^\[RET")
+if [ "$RECORDS" -gt 0 ]; then
+ echo "PASS: captured $RECORDS records from rust_ffi_contract"
+ exit 0
+else
+ echo "FAIL: no records captured"
+ echo "$OUTPUT"
+ exit 1
+fi
diff --git a/tools/testing/selftests/kcov_dataflow/rust_ffi_contract/Makefile b/tools/testing/selftests/kcov_dataflow/rust_ffi_contract/Makefile
new file mode 100644
index 000000000000..d2a0261070b1
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/rust_ffi_contract/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-m := rust_ffi_contract.o
+KCOV_DATAFLOW_rust_ffi_contract.o := y
diff --git a/tools/testing/selftests/kcov_dataflow/rust_ffi_contract/rust_ffi_contract.c b/tools/testing/selftests/kcov_dataflow/rust_ffi_contract/rust_ffi_contract.c
new file mode 100644
index 000000000000..9cbb17c42195
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/rust_ffi_contract/rust_ffi_contract.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rust_ffi_contract.c - Demonstrates kcov_dataflow detecting an FFI
+ * contract violation at a function boundary.
+ *
+ * The pattern: caller passes a struct pointer to callee. Callee's
+ * contract says "returns 0 implies out->buffer is valid". A bug in
+ * the async path returns 0 but leaves buffer=NULL.
+ *
+ * kcov_dataflow captures:
+ * [ENTRY] ffi_alloc_buf(alloc={.buffer=NULL, .data_size=0}, 256, 16, 1)
+ * [RET] ffi_alloc_buf() = 0
+ * [ENTRY] ffi_check_result(alloc={.buffer=NULL, ...})
+ * ^ proves contract violated
+ *
+ * Write to /sys/kernel/debug/kcov_dataflow_test/rust_ffi_trigger to run.
+ */
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("FFI contract violation detection via kcov_dataflow");
+
+struct ffi_alloc {
+ void *buffer;
+ u64 data_size;
+ u32 free_async;
+ u32 flags;
+};
+
+/* Prototypes */
+int ffi_alloc_buf(struct ffi_alloc *alloc, u64 data_size,
+ u64 offsets_size, int is_async);
+int ffi_check_result(struct ffi_alloc *alloc);
+
+/*
+ * Callee with contract: returns 0 implies alloc->buffer is valid.
+ * BUG: async path with free_async==0 returns 0 but buffer stays NULL.
+ */
+noinline int ffi_alloc_buf(struct ffi_alloc *alloc, u64 data_size,
+ u64 offsets_size, int is_async)
+{
+ if (!is_async) {
+ alloc->buffer = kmalloc(data_size, GFP_KERNEL);
+ if (!alloc->buffer)
+ return -ENOMEM;
+ return 0;
+ }
+ /* BUG: returns success but buffer is NULL when pool empty */
+ if (alloc->free_async == 0) {
+ alloc->buffer = NULL;
+ return 0; /* contract violation */
+ }
+ alloc->buffer = kmalloc(data_size, GFP_KERNEL);
+ alloc->free_async--;
+ return 0;
+}
+EXPORT_SYMBOL(ffi_alloc_buf);
+
+/* Caller that trusts the contract */
+noinline int ffi_check_result(struct ffi_alloc *alloc)
+{
+ if (!alloc->buffer) {
+ pr_err("ffi_contract: VIOLATION detected - buffer is NULL after success\n");
+ return -EFAULT;
+ }
+ kfree(alloc->buffer);
+ return 0;
+}
+EXPORT_SYMBOL(ffi_check_result);
+
+static struct dentry *test_dir;
+
+static ssize_t rust_ffi_trigger_write(struct file *f, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ffi_alloc alloc = { .buffer = NULL, .data_size = 0,
+ .free_async = 0, .flags = 0 };
+ int ret;
+
+ /* Trigger the bug: is_async=1, free_async=0 */
+ ret = ffi_alloc_buf(&alloc, 256, 16, 1);
+ pr_info("ffi_contract: ffi_alloc_buf returned %d, buffer=%p\n",
+ ret, alloc.buffer);
+
+ if (ret == 0)
+ ffi_check_result(&alloc);
+
+ return count;
+}
+
+static const struct file_operations rust_ffi_trigger_fops = {
+ .write = rust_ffi_trigger_write,
+};
+
+static int __init ffi_contract_init(void)
+{
+ test_dir = debugfs_create_dir("kcov_dataflow_test", NULL);
+ debugfs_create_file("rust_ffi_trigger", 0200, test_dir, NULL,
+ &rust_ffi_trigger_fops);
+ return 0;
+}
+
+static void __exit ffi_contract_exit(void)
+{
+ debugfs_remove_recursive(test_dir);
+}
+
+module_init(ffi_contract_init);
+module_exit(ffi_contract_exit);
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 11/14] selftests/kcov_dataflow: add eight_args_rust test module
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
Rust module exercising 1-8 argument functions plus struct pointer.
Verifies register-passed (1-6) and stack-passed (7-8) arguments.
Test:
make LLVM=1 CC=clang RUSTC=$RUSTC RUST_LIB_SRC=$RUST_LIB_SRC \
M=tools/testing/selftests/kcov_dataflow/eight_args_rust modules
vng --user root --exec \
"python3 tools/testing/selftests/kcov_dataflow/trigger-view.py \
eight_args_rust -C 8 --ko \
tools/testing/selftests/kcov_dataflow/eight_args_rust/eight_args_rust.ko"
Result:
ksys_write(0x0, 0x1)
fdget_pos(0x4)
0xffff891481d2bc00 = fdget_pos()
0x0 = vfs_write()
vfs_write(0x0, 0x1, 0x0)
0x0 = _RNvCs3p16QzTwthP_15eight_args_rust13write_handler [eight_args_rust]()
_RNvCs3p16QzTwthP_15eight_args_rust13write_handler [eight_args_rust](0x0, 0x1, 0x0)
rdf_func2 [eight_args_rust](0x11, 0x22)
0x33 = rdf_func2 [eight_args_rust]()
rdf_func3 [eight_args_rust](0x11, 0x22, 0x33)
0x66 = rdf_func3 [eight_args_rust]()
rdf_func4 [eight_args_rust](0x11, 0x22, 0x33, 0x44)
0xaa = rdf_func4 [eight_args_rust]()
rdf_func5 [eight_args_rust](0x11, 0x22, 0x33, 0x44, 0x55)
0xff = rdf_func5 [eight_args_rust]()
rdf_func6 [eight_args_rust](0x11, 0x22, 0x33, 0x44, 0x55, 0x66)
0x165 = rdf_func6 [eight_args_rust]()
rdf_func7 [eight_args_rust](0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77)
0x1dc = rdf_func7 [eight_args_rust]()
rdf_func8 [eight_args_rust](0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88)
0x264 = rdf_func8 [eight_args_rust]()
rdf_func_struct [eight_args_rust](0xaaaa)
0x16665 = rdf_func_struct [eight_args_rust]()
0x1 = _RNvCs3p16QzTwthP_15eight_args_rust13write_handler [eight_args_rust]()
0x1 = vfs_write()
0x1 = ksys_write()
0x1 = __x64_sys_write()
0x0 = fpregs_assert_state_consistent()
0xba5748 = __x64_sys_close()
file_close_fd(0x4)
0x0 = file_close_fd()
0x0 = filp_flush()
Cc: Alexander Potapenko <glider@google.com>
Assisted-by: Claude:claude-opus-4-6 [kiro-chat]
Link: https://github.com/yskzalloc/kcov-dataflow/actions
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
tools/testing/selftests/kcov_dataflow/README.rst | 7 +
.../kcov_dataflow/eight_args_rust/Makefile | 3 +
.../eight_args_rust/eight_args_rust.rs | 143 +++++++++++++++++++++
.../selftests/kcov_dataflow/run_eight_args_rust.sh | 35 +++++
4 files changed, 188 insertions(+)
diff --git a/tools/testing/selftests/kcov_dataflow/README.rst b/tools/testing/selftests/kcov_dataflow/README.rst
index e93b4e573504..61a41f3bd596 100644
--- a/tools/testing/selftests/kcov_dataflow/README.rst
+++ b/tools/testing/selftests/kcov_dataflow/README.rst
@@ -41,3 +41,10 @@ eight_args_c/
make LLVM=1 CC=clang M=tools/testing/selftests/kcov_dataflow/eight_args_c modules
python3 trigger-view.py eight_args_c
+
+eight_args_rust/
+ Rust equivalent of eight_args_c. Captures arguments at -O2 where
+ drgn/vmcore cannot. Requires CONFIG_RUST::
+
+ make LLVM=1 CC=clang M=tools/testing/selftests/kcov_dataflow/eight_args_rust modules
+ python3 trigger-view.py eight_args_rust
diff --git a/tools/testing/selftests/kcov_dataflow/eight_args_rust/Makefile b/tools/testing/selftests/kcov_dataflow/eight_args_rust/Makefile
new file mode 100644
index 000000000000..c1e9ea2c5622
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/eight_args_rust/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-m := eight_args_rust.o
+KCOV_DATAFLOW_eight_args_rust.o := y
diff --git a/tools/testing/selftests/kcov_dataflow/eight_args_rust/eight_args_rust.rs b/tools/testing/selftests/kcov_dataflow/eight_args_rust/eight_args_rust.rs
new file mode 100644
index 000000000000..3026265cda97
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/eight_args_rust/eight_args_rust.rs
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+//! Verify kcov_dataflow captures 1-8 argument Rust functions at -O2.
+//!
+//! This is the Rust equivalent of eight_args_c. Since rustc elides DWARF
+//! variable locations at -O2, drgn/vmcore cannot observe these arguments.
+//! kcov_dataflow captures them via the post-compilation pipeline.
+//!
+//! Write to /sys/kernel/debug/kcov_dataflow_test/trigger_rust to invoke.
+
+#![allow(missing_docs)]
+
+use kernel::prelude::*;
+use kernel::c_str;
+
+module! {
+ type: EightArgsRust,
+ name: "eight_args_rust",
+ authors: ["kcov-dataflow"],
+ description: "1-8 arg Rust verification for kcov_dataflow",
+ license: "GPL",
+}
+
+#[repr(C)]
+pub struct Pair {
+ pub x: u32,
+ pub y: u32,
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func1(a1: u64) -> u64 { a1 }
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func2(a1: u64, a2: u64) -> u64 { a1 + a2 }
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func3(a1: u64, a2: u64, a3: u64) -> u64 {
+ a1 + a2 + a3
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func4(a1: u64, a2: u64, a3: u64, a4: u64) -> u64 {
+ a1 + a2 + a3 + a4
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func5(a1: u64, a2: u64, a3: u64, a4: u64, a5: u64) -> u64 {
+ a1 + a2 + a3 + a4 + a5
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func6(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func7(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64, a7: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6 + a7
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func8(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64, a7: u64, a8: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rdf_func_struct(p: *const Pair) -> u64 {
+ unsafe { (*p).x as u64 + (*p).y as u64 }
+}
+
+unsafe extern "C" fn write_handler(
+ _file: *mut kernel::bindings::file,
+ _buf: *const core::ffi::c_char,
+ count: usize,
+ _ppos: *mut kernel::bindings::loff_t,
+) -> kernel::ffi::c_long {
+ let p = Pair { x: 0xAAAA, y: 0xBBBB };
+
+ let mut sum: u64 = 0;
+ sum = sum.wrapping_add(rdf_func1(0x11));
+ sum = sum.wrapping_add(rdf_func2(0x11, 0x22));
+ sum = sum.wrapping_add(rdf_func3(0x11, 0x22, 0x33));
+ sum = sum.wrapping_add(rdf_func4(0x11, 0x22, 0x33, 0x44));
+ sum = sum.wrapping_add(rdf_func5(0x11, 0x22, 0x33, 0x44, 0x55));
+ sum = sum.wrapping_add(rdf_func6(0x11, 0x22, 0x33, 0x44, 0x55, 0x66));
+ sum = sum.wrapping_add(rdf_func7(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77));
+ sum = sum.wrapping_add(rdf_func8(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88));
+ sum = sum.wrapping_add(rdf_func_struct(&p as *const Pair));
+ core::hint::black_box(sum);
+
+ count as kernel::ffi::c_long
+}
+
+#[repr(transparent)]
+struct SyncFops(kernel::bindings::file_operations);
+unsafe impl Sync for SyncFops {}
+
+static FOPS: SyncFops = SyncFops(kernel::bindings::file_operations {
+ write: Some(unsafe { core::mem::transmute(write_handler as *const ()) }),
+ ..unsafe { core::mem::zeroed() }
+});
+
+struct EightArgsRust {
+ d: *mut kernel::bindings::dentry,
+}
+
+impl kernel::Module for EightArgsRust {
+ fn init(_module: &'static ThisModule) -> Result<Self> {
+ let d = unsafe {
+ kernel::bindings::debugfs_create_file_unsafe(
+ c_str!("trigger_rust").as_char_ptr(),
+ 0o222,
+ core::ptr::null_mut(),
+ core::ptr::null_mut(),
+ &FOPS.0,
+ )
+ };
+ Ok(Self { d })
+ }
+}
+
+impl Drop for EightArgsRust {
+ fn drop(&mut self) {
+ unsafe { kernel::bindings::debugfs_remove(self.d) };
+ }
+}
+
+unsafe impl Send for EightArgsRust {}
+unsafe impl Sync for EightArgsRust {}
diff --git a/tools/testing/selftests/kcov_dataflow/run_eight_args_rust.sh b/tools/testing/selftests/kcov_dataflow/run_eight_args_rust.sh
new file mode 100755
index 000000000000..c5f11866e19d
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/run_eight_args_rust.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Test eight_args_rust module capture via kcov_dataflow
+DIR="$(dirname "$0")"
+KO="$DIR/eight_args_rust/eight_args_rust.ko"
+
+if [ ! -f "$KO" ]; then
+ echo "SKIP: $KO not found"
+ echo "Build: make LLVM=1 CC=clang RUSTC=\$RUSTC M=...eight_args_rust modules""
+ exit 4 # kselftest SKIP
+fi
+
+if [ ! -e /sys/kernel/debug/kcov_dataflow ]; then
+ echo "SKIP: kcov_dataflow not available"
+ exit 4
+fi
+
+OUTPUT=$(python3 "$DIR/trigger-view.py" eight_args_rust --ko "$KO" --raw 2>&1)
+RC=$?
+
+if [ $RC -ne 0 ]; then
+ echo "FAIL: trigger-and-view exited with $RC"
+ echo "$OUTPUT"
+ exit 1
+fi
+
+RECORDS=$(echo "$OUTPUT" | grep -c "^\[ENTRY\]\|^\[RET")
+if [ "$RECORDS" -gt 0 ]; then
+ echo "PASS: captured $RECORDS records from eight_args_rust"
+ exit 0
+else
+ echo "FAIL: no records captured"
+ echo "$OUTPUT"
+ exit 1
+fi
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 10/14] selftests/kcov_dataflow: add eight_args_c test module
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
C module exercising 1-8 argument functions plus struct pointer.
Verifies register-passed (1-6) and stack-passed (7-8) arguments.
Test:
make LLVM=1 CC=clang \
M=tools/testing/selftests/kcov_dataflow/eight_args_c modules
vng --user root --exec \
"python3 tools/testing/selftests/kcov_dataflow/trigger-view.py \
eight_args_c -C 8 --ko \
tools/testing/selftests/kcov_dataflow/eight_args_c/eight_args_c.ko"
Result:
# Loaded eight_args_c
# Captured 6195 words
# 578 records
# showing 65 records with context=8 around eight_args_c
vfs_write(0x0)
0x0 = full_proxy_write()
full_proxy_write(0x0, 0x1, 0x0)
0x8200080 = __debugfs_file_get()
__debugfs_file_get(0x0)
0x0 = __debugfs_file_get()
0x0 = trigger_write [eight_args_c]()
trigger_write [eight_args_c](0x0, 0x1, 0x0)
df_func2 [eight_args_c](0x11, 0x22)
0x33 = df_func2 [eight_args_c]()
df_func3 [eight_args_c](0x11, 0x22, 0x33)
0x66 = df_func3 [eight_args_c]()
df_func4 [eight_args_c](0x11, 0x22, 0x33, 0x44)
0xaa = df_func4 [eight_args_c]()
df_func5 [eight_args_c](0x11, 0x22, 0x33, 0x44, 0x55)
0xff = df_func5 [eight_args_c]()
df_func6 [eight_args_c](0x11, 0x22, 0x33, 0x44, 0x55, 0x66)
0x165 = df_func6 [eight_args_c]()
df_func7 [eight_args_c](0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77)
0x1dc = df_func7 [eight_args_c]()
df_func8 [eight_args_c](0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88)
0x264 = df_func8 [eight_args_c]()
df_func_struct [eight_args_c](0xaaaa)
0x16665 = df_func_struct [eight_args_c]()
0x1 = trigger_write [eight_args_c]()
0x1 = full_proxy_write()
0x1 = vfs_write()
0x1 = ksys_write()
0x1 = __x64_sys_write()
0x0 = fpregs_assert_state_consistent()
0xba5748 = __x64_sys_close()
file_close_fd(0x4)
0x0 = file_close_fd()
Cc: Alexander Potapenko <glider@google.com>
Assisted-by: Claude:claude-opus-4-6 [kiro-chat]
Link: https://github.com/yskzalloc/kcov-dataflow/actions
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
tools/testing/selftests/kcov_dataflow/Makefile | 1 +
tools/testing/selftests/kcov_dataflow/README.rst | 6 ++
.../selftests/kcov_dataflow/eight_args_c/Makefile | 3 +
.../kcov_dataflow/eight_args_c/eight_args_c.c | 95 ++++++++++++++++++++++
.../selftests/kcov_dataflow/run_eight_args_c.sh | 35 ++++++++
5 files changed, 140 insertions(+)
diff --git a/tools/testing/selftests/kcov_dataflow/Makefile b/tools/testing/selftests/kcov_dataflow/Makefile
index b9fc1c5f0104..3a42c54e954d 100644
--- a/tools/testing/selftests/kcov_dataflow/Makefile
+++ b/tools/testing/selftests/kcov_dataflow/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := user_ioctl/user_ioctl
+TEST_PROGS := run_eight_args_c.sh
include ../lib.mk
diff --git a/tools/testing/selftests/kcov_dataflow/README.rst b/tools/testing/selftests/kcov_dataflow/README.rst
index 8b650a62acb1..e93b4e573504 100644
--- a/tools/testing/selftests/kcov_dataflow/README.rst
+++ b/tools/testing/selftests/kcov_dataflow/README.rst
@@ -35,3 +35,9 @@ trigger-view.py
python3 trigger-view.py <module_name>
python3 trigger-view.py <module_name> --raw
+
+eight_args_c/
+ C module with 1-8 argument functions + struct pointer::
+
+ make LLVM=1 CC=clang M=tools/testing/selftests/kcov_dataflow/eight_args_c modules
+ python3 trigger-view.py eight_args_c
diff --git a/tools/testing/selftests/kcov_dataflow/eight_args_c/Makefile b/tools/testing/selftests/kcov_dataflow/eight_args_c/Makefile
new file mode 100644
index 000000000000..aad45c7e3863
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/eight_args_c/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-m := eight_args_c.o
+KCOV_DATAFLOW_eight_args_c.o := y
diff --git a/tools/testing/selftests/kcov_dataflow/eight_args_c/eight_args_c.c b/tools/testing/selftests/kcov_dataflow/eight_args_c/eight_args_c.c
new file mode 100644
index 000000000000..09fbbbf8d14b
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/eight_args_c/eight_args_c.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * eight_args_c.c - Verify kcov_dataflow captures 1-8 argument functions.
+ *
+ * Write to /sys/kernel/debug/kcov_dataflow_test/trigger to invoke all
+ * eight functions and a struct-pointer function. Use with the
+ * kcov_dataflow selftest to verify correct capture of register-passed
+ * (1-6) and stack-passed (7-8) arguments on x86_64.
+ */
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KCOV dataflow 8-argument stress test");
+
+struct pair {
+ u32 x;
+ u32 y;
+};
+
+/* Prototypes */
+u64 df_func1(u64 a1);
+u64 df_func2(u64 a1, u64 a2);
+u64 df_func3(u64 a1, u64 a2, u64 a3);
+u64 df_func4(u64 a1, u64 a2, u64 a3, u64 a4);
+u64 df_func5(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5);
+u64 df_func6(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6);
+u64 df_func7(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6, u64 a7);
+u64 df_func8(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6, u64 a7,
+ u64 a8);
+u64 df_func_struct(struct pair *p);
+
+/* Implementations - noinline ensures trace callbacks are emitted */
+#define DEF_FUNC(name, ret_expr, ...) \
+noinline u64 name(__VA_ARGS__) { return (ret_expr); } \
+EXPORT_SYMBOL(name)
+
+DEF_FUNC(df_func1, a1, u64 a1);
+DEF_FUNC(df_func2, a1 + a2, u64 a1, u64 a2);
+DEF_FUNC(df_func3, a1 + a2 + a3, u64 a1, u64 a2, u64 a3);
+DEF_FUNC(df_func4, a1 + a2 + a3 + a4, u64 a1, u64 a2, u64 a3, u64 a4);
+DEF_FUNC(df_func5, a1 + a2 + a3 + a4 + a5,
+ u64 a1, u64 a2, u64 a3, u64 a4, u64 a5);
+DEF_FUNC(df_func6, a1 + a2 + a3 + a4 + a5 + a6,
+ u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6);
+DEF_FUNC(df_func7, a1 + a2 + a3 + a4 + a5 + a6 + a7,
+ u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6, u64 a7);
+DEF_FUNC(df_func8, a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8,
+ u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6, u64 a7, u64 a8);
+
+noinline u64 df_func_struct(struct pair *p)
+{
+ return (u64)p->x + (u64)p->y;
+}
+EXPORT_SYMBOL(df_func_struct);
+
+static struct dentry *test_dir;
+
+static ssize_t trigger_write(struct file *f, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct pair p = { .x = 0xAAAA, .y = 0xBBBB };
+ volatile u64 sum = 0;
+
+ sum += df_func1(0x11);
+ sum += df_func2(0x11, 0x22);
+ sum += df_func3(0x11, 0x22, 0x33);
+ sum += df_func4(0x11, 0x22, 0x33, 0x44);
+ sum += df_func5(0x11, 0x22, 0x33, 0x44, 0x55);
+ sum += df_func6(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
+ sum += df_func7(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77);
+ sum += df_func8(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88);
+ sum += df_func_struct(&p);
+
+ return count;
+}
+
+static const struct file_operations trigger_fops = {
+ .write = trigger_write,
+};
+
+static int __init eight_args_init(void)
+{
+ test_dir = debugfs_create_dir("kcov_dataflow_test", NULL);
+ debugfs_create_file("trigger", 0200, test_dir, NULL, &trigger_fops);
+ return 0;
+}
+
+static void __exit eight_args_exit(void)
+{
+ debugfs_remove_recursive(test_dir);
+}
+
+module_init(eight_args_init);
+module_exit(eight_args_exit);
diff --git a/tools/testing/selftests/kcov_dataflow/run_eight_args_c.sh b/tools/testing/selftests/kcov_dataflow/run_eight_args_c.sh
new file mode 100755
index 000000000000..d24092e920ff
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/run_eight_args_c.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Test eight_args_c module capture via kcov_dataflow
+DIR="$(dirname "$0")"
+KO="$DIR/eight_args_c/eight_args_c.ko"
+
+if [ ! -f "$KO" ]; then
+ echo "SKIP: $KO not found"
+ echo "Build: make LLVM=1 CC=clang M=...eight_args_c modules"
+ exit 4 # kselftest SKIP
+fi
+
+if [ ! -e /sys/kernel/debug/kcov_dataflow ]; then
+ echo "SKIP: kcov_dataflow not available"
+ exit 4
+fi
+
+OUTPUT=$(python3 "$DIR/trigger-view.py" eight_args_c --ko "$KO" --raw 2>&1)
+RC=$?
+
+if [ $RC -ne 0 ]; then
+ echo "FAIL: trigger-and-view exited with $RC"
+ echo "$OUTPUT"
+ exit 1
+fi
+
+RECORDS=$(echo "$OUTPUT" | grep -c "^\[ENTRY\]\|^\[RET")
+if [ "$RECORDS" -gt 0 ]; then
+ echo "PASS: captured $RECORDS records from eight_args_c"
+ exit 0
+else
+ echo "FAIL: no records captured"
+ echo "$OUTPUT"
+ exit 1
+fi
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 09/14] selftests/kcov_dataflow: add ioctl interface selftest
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
Add kselftest_harness-based test in user_ioctl/ covering the
kcov_dataflow ioctl interface (9 TAP cases): init, mmap, enable,
disable, error paths, double-enable rejection, and record capture.
Test:
make -C tools/testing/selftests/kcov_dataflow
./user_ioctl/user_ioctl
Result:
TAP version 13
1..9
# Starting 9 tests from 1 test cases.
# RUN kcov_dataflow.init_track ...
# OK kcov_dataflow.init_track
ok 1 kcov_dataflow.init_track
# RUN kcov_dataflow.init_track_too_small ...
# OK kcov_dataflow.init_track_too_small
ok 2 kcov_dataflow.init_track_too_small
# RUN kcov_dataflow.init_track_double ...
# OK kcov_dataflow.init_track_double
ok 3 kcov_dataflow.init_track_double
# RUN kcov_dataflow.mmap_before_init ...
# OK kcov_dataflow.mmap_before_init
ok 4 kcov_dataflow.mmap_before_init
# RUN kcov_dataflow.enable_disable ...
# OK kcov_dataflow.enable_disable
ok 5 kcov_dataflow.enable_disable
# RUN kcov_dataflow.enable_without_mmap ...
# OK kcov_dataflow.enable_without_mmap
ok 6 kcov_dataflow.enable_without_mmap
# RUN kcov_dataflow.disable_without_enable ...
# OK kcov_dataflow.disable_without_enable
ok 7 kcov_dataflow.disable_without_enable
# RUN kcov_dataflow.double_enable ...
# OK kcov_dataflow.double_enable
ok 8 kcov_dataflow.double_enable
# RUN kcov_dataflow.records_captured ...
# OK kcov_dataflow.records_captured
Cc: Alexander Potapenko <glider@google.com>
Assisted-by: Claude:claude-opus-4-6 [kiro-chat]
Link: https://github.com/yskzalloc/kcov-dataflow/actions
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
tools/testing/selftests/kcov_dataflow/.gitignore | 8 ++
tools/testing/selftests/kcov_dataflow/Makefile | 3 +
tools/testing/selftests/kcov_dataflow/README.rst | 37 +++++
.../kcov_dataflow/user_ioctl/user_ioctl.c | 156 +++++++++++++++++++++
4 files changed, 204 insertions(+)
diff --git a/tools/testing/selftests/kcov_dataflow/.gitignore b/tools/testing/selftests/kcov_dataflow/.gitignore
new file mode 100644
index 000000000000..f71fc89580f8
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/.gitignore
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+user_ioctl/user_ioctl
+*.o
+*.ko
+*.mod
+*.mod.c
+Module.symvers
+modules.order
diff --git a/tools/testing/selftests/kcov_dataflow/Makefile b/tools/testing/selftests/kcov_dataflow/Makefile
new file mode 100644
index 000000000000..b9fc1c5f0104
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := user_ioctl/user_ioctl
+include ../lib.mk
diff --git a/tools/testing/selftests/kcov_dataflow/README.rst b/tools/testing/selftests/kcov_dataflow/README.rst
new file mode 100644
index 000000000000..8b650a62acb1
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/README.rst
@@ -0,0 +1,37 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+KCOV-Dataflow Selftests
+========================
+
+This directory contains selftests for the KCOV-Dataflow subsystem
+(``/sys/kernel/debug/kcov_dataflow``).
+
+Prerequisites
+-------------
+
+Build the kernel with::
+
+ CONFIG_KCOV=y
+ CONFIG_KCOV_DATAFLOW_ARGS=y
+ CONFIG_KCOV_DATAFLOW_RET=y
+ CONFIG_DEBUG_INFO=y
+
+For full capture, also enable::
+
+ CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y
+
+Tests
+-----
+
+user_ioctl/user_ioctl.c
+ Automated ioctl interface test (9 TAP cases)::
+
+ make -C tools/testing/selftests/kcov_dataflow
+ ./user_ioctl/user_ioctl
+
+trigger-view.py
+ Loads a test module via finit_module() with recording active,
+ prints captured records with symbol resolution::
+
+ python3 trigger-view.py <module_name>
+ python3 trigger-view.py <module_name> --raw
diff --git a/tools/testing/selftests/kcov_dataflow/user_ioctl/user_ioctl.c b/tools/testing/selftests/kcov_dataflow/user_ioctl/user_ioctl.c
new file mode 100644
index 000000000000..48448bc02d2f
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/user_ioctl/user_ioctl.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kcov_dataflow_test.c - Selftest for /sys/kernel/debug/kcov_dataflow
+ *
+ * Verifies the ioctl interface: open, INIT_TRACK, mmap, ENABLE, DISABLE.
+ * With INSTRUMENT_ALL, also verifies that records are produced for
+ * syscalls executed while recording is active.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include "../../kselftest_harness.h"
+
+#define KCOV_DF_INIT_TRACK _IOR('d', 1, unsigned long)
+#define KCOV_DF_ENABLE _IO('d', 100)
+#define KCOV_DF_DISABLE _IO('d', 101)
+
+#define BUF_SIZE 65536
+
+#define DF_TYPE_ENTRY 0xE
+#define DF_TYPE_RET 0xF
+
+FIXTURE(kcov_dataflow) {
+ int fd;
+ uint64_t *buf;
+};
+
+FIXTURE_SETUP(kcov_dataflow)
+{
+ self->fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ if (self->fd < 0)
+ SKIP(return, "kcov_dataflow not available (need CONFIG_KCOV_DATAFLOW_ARGS)");
+ self->buf = MAP_FAILED;
+}
+
+FIXTURE_TEARDOWN(kcov_dataflow)
+{
+ if (self->buf != MAP_FAILED)
+ munmap(self->buf, BUF_SIZE * sizeof(uint64_t));
+ if (self->fd >= 0)
+ close(self->fd);
+}
+
+TEST_F(kcov_dataflow, init_track)
+{
+ int ret = ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE);
+
+ ASSERT_EQ(0, ret);
+}
+
+TEST_F(kcov_dataflow, init_track_too_small)
+{
+ int ret = ioctl(self->fd, KCOV_DF_INIT_TRACK, 1UL);
+
+ ASSERT_EQ(-1, ret);
+ ASSERT_EQ(EINVAL, errno);
+}
+
+TEST_F(kcov_dataflow, init_track_double)
+{
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE));
+ ASSERT_EQ(-1, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE));
+ ASSERT_EQ(EBUSY, errno);
+}
+
+TEST_F(kcov_dataflow, mmap_before_init)
+{
+ self->buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, 0);
+ ASSERT_EQ(MAP_FAILED, self->buf);
+}
+
+TEST_F(kcov_dataflow, enable_disable)
+{
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE));
+ self->buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, 0);
+ ASSERT_NE(MAP_FAILED, self->buf);
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_ENABLE, 0));
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_DISABLE, 0));
+}
+
+TEST_F(kcov_dataflow, enable_without_mmap)
+{
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE));
+ /* enable works even without mmap (mmap is optional for setup) */
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_ENABLE, 0));
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_DISABLE, 0));
+}
+
+TEST_F(kcov_dataflow, disable_without_enable)
+{
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE));
+ ASSERT_EQ(-1, ioctl(self->fd, KCOV_DF_DISABLE, 0));
+ ASSERT_EQ(EINVAL, errno);
+}
+
+TEST_F(kcov_dataflow, double_enable)
+{
+ int fd2;
+
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE));
+ self->buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, 0);
+ ASSERT_NE(MAP_FAILED, self->buf);
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_ENABLE, 0));
+
+ /* Second fd should fail to enable (task already active) */
+ fd2 = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ ASSERT_GE(fd2, 0);
+ ASSERT_EQ(0, ioctl(fd2, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE));
+ ASSERT_EQ(-1, ioctl(fd2, KCOV_DF_ENABLE, 0));
+ ASSERT_EQ(EBUSY, errno);
+ close(fd2);
+
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_DISABLE, 0));
+}
+
+TEST_F(kcov_dataflow, records_captured)
+{
+ uint64_t count;
+
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE));
+ self->buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, 0);
+ ASSERT_NE(MAP_FAILED, self->buf);
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_ENABLE, 0));
+
+ /* Trigger some kernel code in this task */
+ getpid();
+
+ ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_DISABLE, 0));
+
+ count = self->buf[0];
+ /*
+ * With INSTRUMENT_ALL, getpid() produces records.
+ * Without it, count may be 0 (no instrumented code).
+ * Either way, the interface works correctly.
+ */
+ if (count > 0) {
+ uint64_t hdr = self->buf[1];
+ unsigned int type = (hdr >> 28) & 0xF;
+
+ /* First record should be ENTRY or RET */
+ ASSERT_TRUE(type == DF_TYPE_ENTRY || type == DF_TYPE_RET);
+ }
+}
+
+TEST_HARNESS_MAIN
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 08/14] selftests/kcov_dataflow: add trigger-view.py
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
Add a Python script that loads a test module, triggers its debugfs
entry with kcov_dataflow recording active, then pretty-prints captured
records as a nested call tree with kallsyms symbol resolution.
Features:
- 8MB ring buffer (1M u64 words) for INSTRUMENT_ALL kernels
- Enable recording after module load, before trigger (avoids VFS noise)
- Variable-length record parsing using header-encoded field count
- Module-only filtering via kallsyms symbol lookup
- --context/-C N: show N records before/after each module function call
- --raw: print raw records instead of call tree
- Architecture-aware syscall numbers (x86_64 and arm64)
Usage:
python3 trigger-view.py eight_args_c \
--ko eight_args_c/eight_args_c.ko
python3 trigger-view.py eight_args_rust \
--ko eight_args_rust/eight_args_rust.ko
python3 trigger-view.py rust_ffi_contract \
--ko rust_ffi_contract/rust_ffi_contract.ko
Cc: Alexander Potapenko <glider@google.com>
Assisted-by: Claude:claude-opus-4-6 [kiro-chat]
Link: https://github.com/yskzalloc/kcov-dataflow/actions
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
.../selftests/kcov_dataflow/trigger-view.py | 377 +++++++++++++++++++++
1 file changed, 377 insertions(+)
diff --git a/tools/testing/selftests/kcov_dataflow/trigger-view.py b/tools/testing/selftests/kcov_dataflow/trigger-view.py
new file mode 100755
index 000000000000..a3274e472dc1
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/trigger-view.py
@@ -0,0 +1,377 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+trigger-view.py - Load a module with kcov_dataflow
+recording active, then pretty-print captured records.
+
+Usage:
+ python3 trigger-view.py eight_args_c
+ python3 trigger-view.py rust_ffi_contract
+ python3 trigger-view.py eight_args_c --raw
+
+The script:
+ 1. Opens /sys/kernel/debug/kcov_dataflow
+ 2. Inits and mmaps the buffer
+ 3. Enables recording for this process
+ 4. Loads the module via finit_module() -- init runs in our context
+ 5. Disables recording
+ 6. Unloads the module
+ 7. Parses and prints captured records with kallsyms resolution
+"""
+import os
+import sys
+import struct
+import ctypes
+import ctypes.util
+import argparse
+import fcntl
+
+# Constants
+DF_TYPE_ENTRY = 0xE
+DF_TYPE_RET = 0xF
+MAGIC_BAD = 0xBADADD85
+BUF_SIZE = 1048576 # 1M words = 8MB
+
+# Ioctl numbers
+def _IOR(t, nr, size):
+ return (2 << 30) | (ord(t) << 8) | nr | (size << 16)
+
+def _IO(t, nr):
+ return (ord(t) << 8) | nr
+
+KCOV_DF_INIT_TRACK = _IOR('d', 1, 8)
+KCOV_DF_ENABLE = _IO('d', 100)
+KCOV_DF_DISABLE = _IO('d', 101)
+
+# syscall numbers (x86_64)
+import platform
+_machine = platform.machine()
+if _machine == "aarch64":
+ SYS_FINIT_MODULE = 273
+ SYS_DELETE_MODULE = 106
+else: # x86_64
+ SYS_FINIT_MODULE = 313
+ SYS_DELETE_MODULE = 176
+
+SELFTEST_DIR = os.path.dirname(os.path.abspath(__file__))
+
+
+def load_kallsyms():
+ """Load kernel symbols for PC resolution."""
+ syms = []
+ try:
+ with open("/proc/kallsyms") as f:
+ for line in f:
+ parts = line.split()
+ if len(parts) >= 3:
+ addr = int(parts[0], 16)
+ name = parts[2]
+ mod = parts[3].strip("[]") if len(parts) > 3 else ""
+ syms.append((addr, name, mod))
+ except (PermissionError, FileNotFoundError):
+ pass
+ syms.sort()
+ return syms
+
+
+def symbolize(pc, syms):
+ """Find nearest symbol <= pc."""
+ if not syms:
+ return f"0x{pc:x}"
+ lo, hi = 0, len(syms) - 1
+ while lo < hi:
+ mid = (lo + hi + 1) // 2
+ if syms[mid][0] <= pc:
+ lo = mid
+ else:
+ hi = mid - 1
+ addr, name, mod = syms[lo]
+ if addr > pc:
+ return f"0x{pc:x}"
+ offset = pc - addr
+ if mod:
+ return f"{name}+0x{offset:x} [{mod}]" if offset else f"{name} [{mod}]"
+ return f"{name}+0x{offset:x}" if offset else name
+
+
+def format_val(v):
+ """Format a captured value."""
+ if v == MAGIC_BAD:
+ return "FAULT"
+ if v == 0:
+ return "0x0"
+ return f"0x{v:x}"
+
+
+def find_module(name):
+ """Find the .ko file for the given test name."""
+ ko_path = os.path.join(SELFTEST_DIR, name, f"{name}_mod.ko")
+ if os.path.exists(ko_path):
+ return ko_path
+ # Try without _mod suffix
+ ko_path = os.path.join(SELFTEST_DIR, name, f"{name}.ko")
+ if os.path.exists(ko_path):
+ return ko_path
+ # Search for any .ko in the directory
+ mod_dir = os.path.join(SELFTEST_DIR, name)
+ if os.path.isdir(mod_dir):
+ for f in os.listdir(mod_dir):
+ if f.endswith(".ko"):
+ return os.path.join(mod_dir, f)
+ return None
+
+
+def finit_module(ko_path):
+ """Load a kernel module via finit_module syscall."""
+ libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
+ fd = os.open(ko_path, os.O_RDONLY)
+ ret = libc.syscall(SYS_FINIT_MODULE, fd, b"", 0)
+ os.close(fd)
+ if ret != 0:
+ errno = ctypes.get_errno()
+ raise OSError(errno, f"finit_module({ko_path}): {os.strerror(errno)}")
+
+
+def delete_module(name):
+ """Unload a kernel module."""
+ libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
+ ret = libc.syscall(SYS_DELETE_MODULE, name.encode(), 0)
+ if ret != 0:
+ errno = ctypes.get_errno()
+ raise OSError(errno, f"delete_module({name}): {os.strerror(errno)}")
+
+
+def parse_records(buf, total_words):
+ """Parse the ring buffer into a list of records."""
+ records = []
+ pos = 1
+ while pos + 3 <= total_words and pos < BUF_SIZE:
+ hdr = buf[pos]
+
+ # Valid headers fit in 32 bits (upper 32 must be zero)
+ if hdr >> 32:
+ pos += 1
+ continue
+
+ rtype = (hdr >> 28) & 0xF
+
+ if rtype not in (DF_TYPE_ENTRY, DF_TYPE_RET):
+ pos += 1
+ continue
+
+ pc = buf[pos + 1]
+ meta = buf[pos + 2]
+ seq = hdr & 0x00FFFFFF
+ num_vals = (hdr >> 24) & 0xF
+ if num_vals == 0:
+ num_vals = 1
+
+ # Valid records always have a non-zero PC (kernel text address)
+ if pc == 0:
+ pos += 1
+ continue
+
+ val = buf[pos + 3] if pos + 3 < BUF_SIZE else 0
+ records.append({
+ "type": rtype,
+ "seq": seq,
+ "pc": pc,
+ "meta": meta,
+ "val": val,
+ })
+ pos += 3 + num_vals
+ return records
+
+
+def print_raw(records, syms):
+ """Print records in raw format."""
+ for r in records:
+ sym = symbolize(r["pc"], syms)
+ t = "ENTRY" if r["type"] == DF_TYPE_ENTRY else "RET "
+ arg_idx = (r["meta"] >> 56) & 0xFF
+ size = (r["meta"] >> 48) & 0xFF
+ print(f"[{t}] seq={r['seq']:3d} {sym} "
+ f"arg[{arg_idx}]({size}) = {format_val(r['val'])}")
+
+
+def print_tree(records, syms):
+ """Print records as indented call tree matching converted.txt format."""
+ depth = 0
+ # Group consecutive ENTRY records by PC to collect all args
+ i = 0
+ while i < len(records):
+ r = records[i]
+ sym = symbolize(r["pc"], syms)
+
+ if r["type"] == DF_TYPE_ENTRY:
+ # Collect all args for this call (same PC, consecutive entries)
+ args = []
+ pc = r["pc"]
+ while i < len(records) and records[i]["type"] == DF_TYPE_ENTRY \
+ and records[i]["pc"] == pc:
+ args.append(format_val(records[i]["val"]))
+ i += 1
+ indent = " " * depth
+ print(f"{indent}{sym}({', '.join(args)})")
+ depth += 1
+ else:
+ depth = max(0, depth - 1)
+ indent = " " * depth
+ print(f"{indent}{format_val(r['val'])} = {sym}()")
+ i += 1
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Load a test module with kcov_dataflow and view records")
+ parser.add_argument("module", help="Test module name (e.g. eight_args_c)")
+ parser.add_argument("--raw", action="store_true",
+ help="Print raw records instead of tree")
+ parser.add_argument("--ko", help="Explicit path to .ko file")
+ parser.add_argument("--context", "-C", type=int, default=0,
+ help="Show N lines before/after each module record")
+ args = parser.parse_args()
+
+ # Find module
+ if args.ko:
+ ko_path = args.ko
+ else:
+ ko_path = find_module(args.module)
+ if not ko_path or not os.path.exists(ko_path):
+ print(f"Cannot find module for '{args.module}'", file=sys.stderr)
+ print(f"Build it first: make LLVM=1 CC=clang "
+ f"M=tools/testing/selftests/kcov_dataflow/{args.module} modules",
+ file=sys.stderr)
+ sys.exit(1)
+
+ # Open kcov_dataflow
+ # Ensure kallsyms shows real addresses
+ try:
+ with open("/proc/sys/kernel/kptr_restrict", "w") as f:
+ f.write("0")
+ except (PermissionError, FileNotFoundError):
+ pass
+
+ try:
+ df_fd = os.open("/sys/kernel/debug/kcov_dataflow", os.O_RDWR)
+ except OSError as e:
+ print(f"Cannot open kcov_dataflow: {e}", file=sys.stderr)
+ sys.exit(1)
+
+ # Init + mmap
+ fcntl.ioctl(df_fd, KCOV_DF_INIT_TRACK, BUF_SIZE)
+ libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
+ libc.mmap.restype = ctypes.c_void_p
+ libc.mmap.argtypes = [
+ ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int,
+ ctypes.c_int, ctypes.c_int, ctypes.c_long
+ ]
+ buf_ptr = libc.mmap(None, BUF_SIZE * 8, 0x3, 0x01, df_fd, 0)
+ if buf_ptr == ctypes.c_void_p(-1).value:
+ print("mmap failed", file=sys.stderr)
+ sys.exit(1)
+ buf = (ctypes.c_uint64 * BUF_SIZE).from_address(buf_ptr)
+
+ # Load module first (generates noise with INSTRUMENT_ALL)
+ mod_name = os.path.basename(ko_path).replace(".ko", "")
+ try:
+ finit_module(ko_path)
+ print(f"# Loaded {mod_name}")
+ except OSError as e:
+ print(f"Failed to load module: {e}", file=sys.stderr)
+ sys.exit(1)
+
+ # Get module .text address for PC filtering
+ mod_text_start = 0
+ try:
+ with open(f"/sys/module/{mod_name}/sections/.text") as f:
+ mod_text_start = int(f.read().strip(), 16)
+ except (FileNotFoundError, ValueError, PermissionError):
+ pass
+
+ # Enable recording AFTER load, BEFORE trigger (avoids VFS/loader noise)
+ fcntl.ioctl(df_fd, KCOV_DF_ENABLE, 0)
+ buf[0] = 0
+
+ # Trigger the module's debugfs file to invoke test functions
+ trigger_paths = [
+ f"/sys/kernel/debug/kcov_dataflow_test/trigger",
+ f"/sys/kernel/debug/kcov_dataflow_test/rust_ffi_trigger",
+ f"/sys/kernel/debug/trigger_rust",
+ f"/sys/kernel/debug/{mod_name}/trigger",
+ ]
+ for tp in trigger_paths:
+ try:
+ with open(tp, "w") as f:
+ f.write("1")
+ break
+ except (FileNotFoundError, PermissionError):
+ continue
+
+ fcntl.ioctl(df_fd, KCOV_DF_DISABLE, 0)
+
+ # Read kallsyms while module is still loaded (symbols available)
+ syms = load_kallsyms()
+
+ # Unload
+ try:
+ delete_module(mod_name)
+ except OSError:
+ pass
+
+ # Parse and display
+ total = int(buf[0])
+ print(f"# Captured {total} words")
+ records = parse_records(buf, total)
+ print(f"# {len(records)} records")
+
+ # Filter to module records using kallsyms
+ # Build set of module symbol addresses for fast lookup
+ mod_syms = set()
+ for addr, name, mod in syms:
+ if mod == mod_name and addr != 0:
+ mod_syms.add(addr)
+
+ def is_module_pc(pc):
+ """Check if PC belongs to mod_name via kallsyms."""
+ if mod_syms:
+ # Binary search: find nearest symbol <= pc, check module
+ lo, hi = 0, len(syms) - 1
+ while lo < hi:
+ mid = (lo + hi + 1) // 2
+ if syms[mid][0] <= pc:
+ lo = mid
+ else:
+ hi = mid - 1
+ return syms[lo][2] == mod_name
+ # Fallback: if no module symbols (kptr_restrict), use .text start
+ return mod_text_start and pc >= mod_text_start
+
+ if syms or mod_text_start:
+ if args.context > 0:
+ module_indices = set()
+ for i, r in enumerate(records):
+ if is_module_pc(r["pc"]):
+ for j in range(max(0, i - args.context),
+ min(len(records), i + args.context + 1)):
+ module_indices.add(j)
+ records = [records[i] for i in sorted(module_indices)]
+ print(f"# showing {len(records)} records with context={args.context} "
+ f"around {mod_name}\n")
+ else:
+ module_records = [r for r in records if is_module_pc(r["pc"])]
+ print(f"# {len(module_records)} from {mod_name}\n")
+ records = module_records
+ else:
+ print("")
+
+ if args.raw:
+ print_raw(records, syms)
+ else:
+ print_tree(records, syms)
+
+ os.close(df_fd)
+
+
+if __name__ == "__main__":
+ main()
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 07/14] kcov: exclude kcov_dataflow.o from sanitizer instrumentation
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
Exclude kcov_dataflow.o from KCOV, KASAN, KCSAN, UBSAN, and KMSAN
instrumentation, matching the exclusions already applied to kcov.o.
Without this, sanitizers instrumenting the dataflow callbacks would
cause infinite recursion.
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
kernel/Makefile | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/kernel/Makefile b/kernel/Makefile
index b70e524c4074..307b7fd1e1f9 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -44,6 +44,12 @@ KCSAN_SANITIZE_kcov.o := n
UBSAN_SANITIZE_kcov.o := n
KMSAN_SANITIZE_kcov.o := n
+KCOV_INSTRUMENT_kcov_dataflow.o := n
+KASAN_SANITIZE_kcov_dataflow.o := n
+KCSAN_SANITIZE_kcov_dataflow.o := n
+UBSAN_SANITIZE_kcov_dataflow.o := n
+KMSAN_SANITIZE_kcov_dataflow.o := n
+
CONTEXT_ANALYSIS_kcov.o := y
CFLAGS_kcov.o := $(call cc-option, -fno-conserve-stack) -fno-stack-protector
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 06/14] kcov: clean up dataflow state on task exit
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun,
sashiko-bot
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
If a task exits without calling KCOV_DF_DISABLE, the kcov_df_enabled
flag and area pointer remain set on the freed task_struct. If that
memory is reallocated, subsequent writes could corrupt arbitrary memory.
Add kcov_dataflow_task_exit() which clears the dataflow fields, called
from kernel/exit.c alongside kcov_task_exit(). This matches how
kcov_task_exit() cleans up the legacy kcov state.
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4%40est.tech
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
include/linux/kcov.h | 2 ++
kernel/exit.c | 1 +
kernel/kcov_dataflow.c | 11 +++++++++++
3 files changed, 14 insertions(+)
diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index e9822b02982b..07d7823e5d6f 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -30,8 +30,10 @@ void kcov_task_exit(struct task_struct *t);
#if defined(CONFIG_KCOV_DATAFLOW_ARGS) || defined(CONFIG_KCOV_DATAFLOW_RET)
void kcov_dataflow_task_init(struct task_struct *t);
+void kcov_dataflow_task_exit(struct task_struct *t);
#else
static inline void kcov_dataflow_task_init(struct task_struct *t) {}
+static inline void kcov_dataflow_task_exit(struct task_struct *t) {}
#endif
#define kcov_prepare_switch(t) \
diff --git a/kernel/exit.c b/kernel/exit.c
index 1056422bc101..af2314500791 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -935,6 +935,7 @@ void __noreturn do_exit(long code)
kthread_do_exit(kthread, code);
kcov_task_exit(tsk);
+ kcov_dataflow_task_exit(tsk);
kmsan_task_exit(tsk);
synchronize_group_exit(tsk, code);
diff --git a/kernel/kcov_dataflow.c b/kernel/kcov_dataflow.c
index 7cfe2495275a..df037b7e90eb 100644
--- a/kernel/kcov_dataflow.c
+++ b/kernel/kcov_dataflow.c
@@ -196,6 +196,17 @@ void kcov_dataflow_task_init(struct task_struct *t)
t->kcov_df_enabled = false;
}
+/* Called from kernel/exit.c to clear state on task exit. */
+void kcov_dataflow_task_exit(struct task_struct *t)
+{
+ if (t->kcov_df_enabled) {
+ t->kcov_df_enabled = false;
+ barrier();
+ t->kcov_df_area = NULL;
+ t->kcov_df_size = 0;
+ }
+}
+
/* File operations for /sys/kernel/debug/kcov_dataflow */
static int kcov_df_open(struct inode *inode, struct file *filep)
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 05/14] kcov: clear dataflow fields on fork
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun,
sashiko-bot
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
dup_task_struct() copies the parent task_struct byte-for-byte. Without
explicitly clearing the dataflow fields, a forked child inherits the
parent's kcov_df_enabled flag and buffer pointer, leading to two tasks
writing to the same buffer and a potential use-after-free if the parent
closes the trace file.
Add kcov_dataflow_task_init() in kernel/kcov_dataflow.c and call it from
kernel/fork.c alongside kcov_task_init(), matching how kcov_stop() clears
the legacy kcov fields during fork.
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4%40est.tech
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
include/linux/kcov.h | 6 ++++++
kernel/fork.c | 1 +
kernel/kcov_dataflow.c | 10 ++++++++++
3 files changed, 17 insertions(+)
diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index 895b761b2db1..e9822b02982b 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -28,6 +28,12 @@ enum kcov_mode {
void kcov_task_init(struct task_struct *t);
void kcov_task_exit(struct task_struct *t);
+#if defined(CONFIG_KCOV_DATAFLOW_ARGS) || defined(CONFIG_KCOV_DATAFLOW_RET)
+void kcov_dataflow_task_init(struct task_struct *t);
+#else
+static inline void kcov_dataflow_task_init(struct task_struct *t) {}
+#endif
+
#define kcov_prepare_switch(t) \
do { \
(t)->kcov_mode |= KCOV_IN_CTXSW; \
diff --git a/kernel/fork.c b/kernel/fork.c
index 892a95214c54..a5741de07979 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -980,6 +980,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
tsk->worker_private = NULL;
kcov_task_init(tsk);
+ kcov_dataflow_task_init(tsk);
kmsan_task_create(tsk);
kmap_local_fork(tsk);
diff --git a/kernel/kcov_dataflow.c b/kernel/kcov_dataflow.c
index 27587b8ceeab..7cfe2495275a 100644
--- a/kernel/kcov_dataflow.c
+++ b/kernel/kcov_dataflow.c
@@ -32,6 +32,7 @@
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/refcount.h>
+#include <linux/kcov.h>
#define KCOV_DF_TYPE_ENTRY 0xE0000000ULL
#define KCOV_DF_TYPE_RET 0xF0000000ULL
@@ -186,6 +187,15 @@ __sanitizer_cov_trace_ret(u64 pc, u32 ret_size, void *ret_val,
EXPORT_SYMBOL(__sanitizer_cov_trace_ret);
#endif
+/* Called from kernel/fork.c to clear inherited state. */
+void kcov_dataflow_task_init(struct task_struct *t)
+{
+ t->kcov_df_area = NULL;
+ t->kcov_df_size = 0;
+ t->kcov_df_seq = 0;
+ t->kcov_df_enabled = false;
+}
+
/* File operations for /sys/kernel/debug/kcov_dataflow */
static int kcov_df_open(struct inode *inode, struct file *filep)
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 04/14] kcov: reject enable on multiple dataflow fds simultaneously
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun,
sashiko-bot
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
A task could enable tracing on multiple kcov_dataflow file descriptors,
corrupting the internal tracking state when one is subsequently closed.
Check current->kcov_df_enabled before allowing KCOV_DF_ENABLE and
return -EBUSY if already active. This matches kcov's check of
t->kcov != NULL in the KCOV_ENABLE path.
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4%40est.tech
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
kernel/kcov_dataflow.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/kernel/kcov_dataflow.c b/kernel/kcov_dataflow.c
index 5248293280d5..27587b8ceeab 100644
--- a/kernel/kcov_dataflow.c
+++ b/kernel/kcov_dataflow.c
@@ -287,8 +287,8 @@ static long kcov_df_ioctl(struct file *filep, unsigned int cmd, unsigned long ar
break;
case KCOV_DF_ENABLE:
- if (!df->area || df->t) {
- res = -EINVAL;
+ if (!df->area || df->t || current->kcov_df_enabled) {
+ res = -EBUSY;
break;
}
df->t = current;
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v2 03/14] kcov: add barriers to recursion guard in kcov_df_write
From: Yunseong Kim @ 2026-06-11 16:21 UTC (permalink / raw)
To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Andrey Konovalov,
Alexander Potapenko, Dmitry Vyukov, Andrew Morton, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Nathan Chancellor, Nicolas Schier, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Jonathan Corbet,
Shuah Khan, Yunseong Kim
Cc: linux-kernel, kasan-dev, rust-for-linux, linux-kbuild, llvm,
linux-mm, linux-kselftest, workflows, linux-doc, Yeoreum Yun
In-Reply-To: <20260611-b4-kcov-dataflow-v2-v2-0-0a261da3987c@est.tech>
The recursion guard (bit-31 of kcov_df_seq) prevents reentry when
copy_from_kernel_nofault() or other called functions are instrumented
with INSTRUMENT_ALL. Without compiler barriers, the guard set/clear
can be reordered relative to the function body, making the protection
ineffective under optimization.
Add barrier() after setting the guard and before clearing it, ensuring
the compiler does not move instrumented operations outside the guarded
region.
Cc: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
kernel/kcov_dataflow.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/kernel/kcov_dataflow.c b/kernel/kcov_dataflow.c
index df7e8bf70bfa..5248293280d5 100644
--- a/kernel/kcov_dataflow.c
+++ b/kernel/kcov_dataflow.c
@@ -86,6 +86,7 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
if (t->kcov_df_seq & (1U << 31))
return;
t->kcov_df_seq |= (1U << 31);
+ barrier();
area = (u64 *)t->kcov_df_area;
if (!area)
@@ -147,6 +148,7 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
}
}
out:
+ barrier();
t->kcov_df_seq &= ~(1U << 31);
}
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox