* [RFC PATCH v2 00/14] kcov: add per-task dataflow tracking for function arguments/return values
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
Introduce kcov_dataflow, a per-task dataflow tracking mechanism for function
arguments/return values at instrumented function boundaries.
Motivation
==========
First, Coverage-guided kernel fuzzers use KCOV edge coverage as their
sole feedback signal. This cannot distinguish two executions of the same
function with different argument values. Fuzzers plateau on stateful
subsystems where security-critical behavior depends on runtime values
rather than control-flow topology.
Second, Existing tracing tools address parts of this challenge:
1. Per-Task Wide-Scale Tracing Contexts (ftrace / kprobes / eBPF)
Break point instruction and redirection: Hooks physically patch global kernel
text. The kernel cannot selectively hook functions per task; every CPU core
triggers the hook, deferring PID filtering to post-trigger logic.
2. Rust for Linux Tracing Status
rustc correctly emits -mfentry code stubs via its LLVM backend, enabling
native integration with ftrace, function_graph, and eBPF trampolines
(fentry/fexit). Metadata & Signature Analysis: funcgraph-args parses Rust
via pahole BTF generation. However, idiomatic types like generics or slices
are difficult to represent cleanly compared to standard repr(C) structs.
3. Inline Function Tracing Limitations
Tracing Visibility: Inlined code cannot be targeted via tracefs. Its runtime
footprint is absorbed by the caller. Debugging requires explicit noinline (C)
or #[inline(never)] (Rust) markers.
Approach
========
An LLVM SanitizerCoverage [1] pass inserts callbacks at function entry/exit
that record argument values into a per-task mmap'd ring buffer. Kernel
backend reads struct fields via copy_from_kernel_nofault(). When not enabled
for a task, the cost is a single boolean check.
The system captures:
- Function argument values at entry (with automatic struct field expansion)
- Return values at exit
- Per-task isolation (no interference between processes)
- Both C and Rust kernel modules
- Instument even inline(default n)
For C based kernel module example, 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()
For corresponding rust kernel example, eight_args_rust:
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()
Design
======
- Independent from existing /sys/kernel/debug/kcov
- Separate device: /sys/kernel/debug/kcov_dataflow
- Separate ioctl namespace ('d'), separate per-task buffer
- Lock-free write path: READ_ONCE/WRITE_ONCE (Tested on x86_64/arm64)
- Safe pointer reads: copy_from_kernel_nofault()
- in_task() guard rejects interrupt/NMI context
- Per-module opt-in: KCOV_DATAFLOW_file.o := y
- Optional global: CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL
- Compiler flags: -fsanitize-coverage=trace-args,trace-ret
(Kconfig uses cc-option to verify compiler support)
CI results:
https://github.com/yskzalloc/kcov-dataflow/actions
Performance
===========
Per-module instrumentation (recording active):
+8.3% on instrumented paths, ~27ns per callback
Global instrumentation (INSTRUMENT_ALL, recording disabled):
.text: +9.5%, .data: +44%, boot: +71%, syscall latency: +133%
Prerequisites
=============
Requires custom LLVM/Clang with trace-args/trace-ret passes:
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
export RUSTC=$PWD/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc
export RUST_LIB_SRC=$PWD/rust/library
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 kernel tracking
LLVM=1 CC=clang RUSTC=$RUSTC RUST_LIB_SRC=$RUST_LIB_SRC
Or without virtme-ng:
cd linux
make LLVM=1 CC=clang defconfig
scripts/config --enable KCOV \
--enable KCOV_DATAFLOW_ARGS \
--enable KCOV_DATAFLOW_RET \
--enable KCOV_DATAFLOW_INSTRUMENT_ALL \
--enable DEBUG_INFO
make LLVM=1 CC=clang olddefconfig
make LLVM=1 CC=clang -j$(nproc)
For Rust module support, build rustc against the custom LLVM:
https://github.com/yskzalloc/rust
Testing
=======
Tested on linux-next 7.1.0-rc6 (next-20260608) with custom clang/LLVM 23
and rustc 1.98-nightly. Verified on both x86_64 and arm64:
- user_ioctl: 9/9 tests pass (ioctl interface correctness: init, mmap,
enable/disable, double-enable rejection, buffer capture verification)
- eight_args_c: nested call tree with df_func2..8 + struct (65 context records)
- eight_args_rust: nested call tree with rdf_func2..8 + struct (65 context records)
- rust_ffi_contract: detects FFI contract violation where callee returns
success (0) but leaves buffer=NULL - captured without crash or KASAN
- binderfs: exercises binder driver via binderfs ioctls (BINDER_VERSION,
BINDER_SET_MAX_THREADS) with kcov_dataflow recording active, verifies
argument records captured at binder ioctl boundaries
Links
=====
[1] LLVM RFC: https://discourse.llvm.org/t/rfc-sanitizercoverage-add-fsanitize-coverage-trace-args-trace-ret/91026
[2] LLVM PR: https://github.com/llvm/llvm-project/pull/201410
[3] Repository: https://github.com/yskzalloc/kcov-dataflow
[4] Paper: https://arxiv.org/pdf/2606.00455
---
Change log:
Changes since v1 (https://lore.kernel.org/all/20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech/):
- Separate from /sys/kernel/debug/kcov (own device, own ioctl namespace)
- Rename internal symbols to avoid collision with existing kcov
- Add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL for whole-kernel capture
- Fix INIT_TRACK race, fork cleanup, task exit cleanup
- Add recursion guard barriers
- Reject concurrent enable on multiple fds
- Move from tools to kselftest adding:
user_ioctl, eight_args_c, eight_args_rust, rust_ffi_contract, binderfs_test
- Separate patch regarding kcov-dataflow Documentation
To: Ingo Molnar <mingo@redhat.com>
To: Peter Zijlstra <peterz@infradead.org>
To: Juri Lelli <juri.lelli@redhat.com>
To: Vincent Guittot <vincent.guittot@linaro.org>
To: Dietmar Eggemann <dietmar.eggemann@arm.com>
To: Steven Rostedt <rostedt@goodmis.org>
To: Ben Segall <bsegall@google.com>
To: Mel Gorman <mgorman@suse.de>
To: Valentin Schneider <vschneid@redhat.com>
To: K Prateek Nayak <kprateek.nayak@amd.com>
To: Andrey Konovalov <andreyknvl@gmail.com>
To: Alexander Potapenko <glider@google.com>
To: Dmitry Vyukov <dvyukov@google.com>
To: Andrew Morton <akpm@linux-foundation.org>
To: Miguel Ojeda <ojeda@kernel.org>
To: Boqun Feng <boqun@kernel.org>
To: Gary Guo <gary@garyguo.net>
To: Björn Roy Baron <bjorn3_gh@protonmail.com>
To: Benno Lossin <lossin@kernel.org>
To: Andreas Hindborg <a.hindborg@kernel.org>
To: Alice Ryhl <aliceryhl@google.com>
To: Trevor Gross <tmgross@umich.edu>
To: Danilo Krummrich <dakr@kernel.org>
To: Nathan Chancellor <nathan@kernel.org>
To: Nicolas Schier <nsc@kernel.org>
To: Nick Desaulniers <nick.desaulniers+lkml@gmail.com>
To: Bill Wendling <morbo@google.com>
To: Justin Stitt <justinstitt@google.com>
To: Kees Cook <kees@kernel.org>
To: David Hildenbrand <david@kernel.org>
To: Lorenzo Stoakes <ljs@kernel.org>
To: "Liam R. Howlett" <liam@infradead.org>
To: Vlastimil Babka <vbabka@kernel.org>
To: Mike Rapoport <rppt@kernel.org>
To: Suren Baghdasaryan <surenb@google.com>
To: Michal Hocko <mhocko@suse.com>
To: Shuah Khan <shuah@kernel.org>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-kernel@vger.kernel.org
Cc: kasan-dev@googlegroups.com
Cc: rust-for-linux@vger.kernel.org
Cc: linux-kbuild@vger.kernel.org
Cc: llvm@lists.linux.dev
Cc: linux-mm@kvack.org
Cc: linux-kselftest@vger.kernel.org
Cc: workflows@vger.kernel.org
Cc: linux-doc@vger.kernel.org
---
Yunseong Kim (14):
kcov: add per-task dataflow tracking for function arguments/return values
kcov: fix INIT_TRACK race in kcov_dataflow
kcov: add barriers to recursion guard in kcov_df_write
kcov: reject enable on multiple dataflow fds simultaneously
kcov: clear dataflow fields on fork
kcov: clean up dataflow state on task exit
kcov: exclude kcov_dataflow.o from sanitizer instrumentation
selftests/kcov_dataflow: add trigger-view.py
selftests/kcov_dataflow: add ioctl interface selftest
selftests/kcov_dataflow: add eight_args_c test module
selftests/kcov_dataflow: add eight_args_rust test module
selftests/kcov_dataflow: add rust_ffi_contract test module
selftests/kcov_dataflow: add binderfs ioctl capture test
Documentation: add kcov-dataflow.rst
Documentation/dev-tools/index.rst | 1 +
Documentation/dev-tools/kcov-dataflow.rst | 321 ++++++++++++++++++
include/linux/kcov.h | 8 +
include/linux/sched.h | 10 +
kernel/Makefile | 9 +
kernel/exit.c | 1 +
kernel/fork.c | 1 +
kernel/kcov.c | 2 +
kernel/kcov_dataflow.c | 356 +++++++++++++++++++
lib/Kconfig.debug | 43 +++
rust/kernel/str.rs | 2 +-
scripts/Makefile.kcov | 12 +
scripts/Makefile.lib | 9 +
tools/testing/selftests/kcov_dataflow/.gitignore | 9 +
tools/testing/selftests/kcov_dataflow/Makefile | 4 +
tools/testing/selftests/kcov_dataflow/README.rst | 58 ++++
.../selftests/kcov_dataflow/binderfs/Makefile | 4 +
.../kcov_dataflow/binderfs/binderfs_test.c | 177 ++++++++++
.../selftests/kcov_dataflow/eight_args_c/Makefile | 3 +
.../kcov_dataflow/eight_args_c/eight_args_c.c | 95 ++++++
.../kcov_dataflow/eight_args_rust/Makefile | 3 +
.../eight_args_rust/eight_args_rust.rs | 143 ++++++++
.../selftests/kcov_dataflow/run_binderfs.sh | 13 +
.../selftests/kcov_dataflow/run_eight_args_c.sh | 35 ++
.../selftests/kcov_dataflow/run_eight_args_rust.sh | 35 ++
.../kcov_dataflow/run_rust_ffi_contract.sh | 35 ++
.../kcov_dataflow/rust_ffi_contract/Makefile | 3 +
.../rust_ffi_contract/rust_ffi_contract.c | 111 ++++++
.../selftests/kcov_dataflow/trigger-view.py | 377 +++++++++++++++++++++
.../kcov_dataflow/user_ioctl/user_ioctl.c | 156 +++++++++
30 files changed, 2035 insertions(+), 1 deletion(-)
---
base-commit: a87737435cfa134f9cdcc696ba3080759d04cf72
change-id: 20260611-b4-kcov-dataflow-v2-3ccff828eb31
Best regards,
--
Yunseong Kim <yunseong.kim@est.tech>
^ permalink raw reply
* Re: [PATCH] Documentation: process: fix brackets
From: Geert Uytterhoeven @ 2026-06-11 8:01 UTC (permalink / raw)
To: Manuel Ebner
Cc: Jonathan Corbet, Shuah Khan, open list:DOCUMENTATION PROCESS,
open list:DOCUMENTATION, open list, Kees Cook,
Krzysztof Kozlowski
In-Reply-To: <20260611064311.117023-2-manuelebner@mailbox.org>
CC kees, krzk
On Thu, 11 Jun 2026 at 08:43, Manuel Ebner <manuelebner@mailbox.org> wrote:
>
> Fix missing ')' and needless ')'
>
> Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
> ---
> This is the first patch of a 'series', but I won't send them together
> because I'm still producing the patches and it will take me a couple weeks.
> Documentation/process/deprecated.rst | 2 +-
> Documentation/process/maintainer-soc.rst | 2 +-
> 2 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/process/deprecated.rst b/Documentation/process/deprecated.rst
> index ac75b7ecac47..03de71f654c7 100644
> --- a/Documentation/process/deprecated.rst
> +++ b/Documentation/process/deprecated.rst
> @@ -388,7 +388,7 @@ allocations. For example, these open coded assignments::
> ptr = kmalloc_array(count, sizeof(*ptr), gfp);
> ptr = kcalloc(count, sizeof(*ptr), gfp);
> ptr = kmalloc(struct_size(ptr, flex_member, count), gfp);
> - ptr = kmalloc(sizeof(struct foo, gfp);
> + ptr = kmalloc(sizeof(struct foo), gfp);
>
> become, respectively::
>
> diff --git a/Documentation/process/maintainer-soc.rst b/Documentation/process/maintainer-soc.rst
> index a3a90a7d4c68..fa91dfc53783 100644
> --- a/Documentation/process/maintainer-soc.rst
> +++ b/Documentation/process/maintainer-soc.rst
> @@ -60,7 +60,7 @@ All typical platform related patches should be sent via SoC submaintainers
> shared defconfigs. Note that scripts/get_maintainer.pl might not provide
> correct addresses for the shared defconfig, so ignore its output and manually
> create CC-list based on MAINTAINERS file or use something like
> -``scripts/get_maintainer.pl -f drivers/soc/FOO/``).
> +``scripts/get_maintainer.pl -f drivers/soc/FOO/``.
>
> Submitting Patches to the Main SoC Maintainers
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* [PATCH] Documentation: process: fix brackets
From: Manuel Ebner @ 2026-06-11 6:43 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, open list:DOCUMENTATION PROCESS,
open list:DOCUMENTATION, open list
Cc: Manuel Ebner
Fix missing ')' and needless ')'
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
This is the first patch of a 'series', but I won't send them together
because I'm still producing the patches and it will take me a couple weeks.
Documentation/process/deprecated.rst | 2 +-
Documentation/process/maintainer-soc.rst | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Documentation/process/deprecated.rst b/Documentation/process/deprecated.rst
index ac75b7ecac47..03de71f654c7 100644
--- a/Documentation/process/deprecated.rst
+++ b/Documentation/process/deprecated.rst
@@ -388,7 +388,7 @@ allocations. For example, these open coded assignments::
ptr = kmalloc_array(count, sizeof(*ptr), gfp);
ptr = kcalloc(count, sizeof(*ptr), gfp);
ptr = kmalloc(struct_size(ptr, flex_member, count), gfp);
- ptr = kmalloc(sizeof(struct foo, gfp);
+ ptr = kmalloc(sizeof(struct foo), gfp);
become, respectively::
diff --git a/Documentation/process/maintainer-soc.rst b/Documentation/process/maintainer-soc.rst
index a3a90a7d4c68..fa91dfc53783 100644
--- a/Documentation/process/maintainer-soc.rst
+++ b/Documentation/process/maintainer-soc.rst
@@ -60,7 +60,7 @@ All typical platform related patches should be sent via SoC submaintainers
shared defconfigs. Note that scripts/get_maintainer.pl might not provide
correct addresses for the shared defconfig, so ignore its output and manually
create CC-list based on MAINTAINERS file or use something like
-``scripts/get_maintainer.pl -f drivers/soc/FOO/``).
+``scripts/get_maintainer.pl -f drivers/soc/FOO/``.
Submitting Patches to the Main SoC Maintainers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v4 1/2] kunit: tool: Parse and print the reason tests are skipped
From: Kees Cook @ 2026-06-10 20:27 UTC (permalink / raw)
To: David Gow
Cc: Brendan Higgins, Rae Moar, Shuah Khan, Thomas Weißschuh,
kunit-dev, linux-kernel, linux-kselftest, workflows
In-Reply-To: <20260606013827.240790-1-david@davidgow.net>
On Sat, Jun 06, 2026 at 09:38:17AM +0800, David Gow wrote:
> When a KUnit test (or other KTAP test) is skipped, a "skip reason" can be
> provided. kunit.py has never done anything with this, ignoring anything
> included in the KTAP output after the 'SKIP' directive.
>
> Since we have it, and it's used, print it in a nice friendly yellow in
> parentheses after a skipped test's name.
>
> (And, by parsing it, it can be included in the JUnit results as well.)
>
> Signed-off-by: David Gow <david@davidgow.net>
Thank you; this makes my life easier so I don't have to use --raw_output
in my fortify testing. :)
Reviewed-by: Kees Cook <kees@kernel.org>
--
Kees Cook
^ permalink raw reply
* Re: [PATCH v3 2/3] Documentation: security-bugs: explain what is and is not a security bug
From: Greg KH @ 2026-06-10 6:10 UTC (permalink / raw)
To: Askar Safin
Cc: w, corbet, leon, linux-doc, linux-kernel, security, skhan,
workflows
In-Reply-To: <CAPnZJGAKHu4rR8+W67KRQYVwRqi3x2Y+iWwhG7a2bY7oEawhfg@mail.gmail.com>
On Wed, Jun 10, 2026 at 04:03:43AM +0300, Askar Safin wrote:
> Thank you for answer!
>
> On Tue, Jun 9, 2026 at 11:44 AM Greg KH <gregkh@linuxfoundation.org> wrote:
> > > - If unprivileged user prevents privileged user from suspending
> > > system, is this security bug?
> >
> > Physical access of suspending a machine feels like an odd threat model
> > to be worried about :)
>
> I think you didn't understand me here. I meant the following situation:
> unprivileged user without physical access was somehow able
> to prevent privileged user with physical access from suspending
> or hibernating the system.
If you can find a bug like this, sure, we'll be glad to review the fix
for it. As for it being a "security" issue, that will depend on the
specific case as "can not suspend" doesn't seem to fix the definition of
"vulnerability" to me.
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v3 2/3] Documentation: security-bugs: explain what is and is not a security bug
From: Askar Safin @ 2026-06-10 1:03 UTC (permalink / raw)
To: Greg KH
Cc: w, corbet, leon, linux-doc, linux-kernel, security, skhan,
workflows
In-Reply-To: <2026060955-zesty-cucumber-1a49@gregkh>
Thank you for answer!
On Tue, Jun 9, 2026 at 11:44 AM Greg KH <gregkh@linuxfoundation.org> wrote:
> > - If unprivileged user prevents privileged user from suspending
> > system, is this security bug?
>
> Physical access of suspending a machine feels like an odd threat model
> to be worried about :)
I think you didn't understand me here. I meant the following situation:
unprivileged user without physical access was somehow able
to prevent privileged user with physical access from suspending
or hibernating the system.
--
Askar Safin
^ permalink raw reply
* Re: [PATCH v3 2/3] Documentation: security-bugs: explain what is and is not a security bug
From: Greg KH @ 2026-06-09 8:43 UTC (permalink / raw)
To: Askar Safin
Cc: w, corbet, leon, linux-doc, linux-kernel, security, skhan,
workflows
In-Reply-To: <20260609083305.2382925-1-safinaskar@gmail.com>
On Tue, Jun 09, 2026 at 11:33:05AM +0300, Askar Safin wrote:
> Willy Tarreau <w@1wt.eu>:
> > +in a way that allows multiple local users to get a fair share of the available
>
> Your "security-bugs.rst" says that we should consult "threat-model.rst" to
> determine whether a bug should be sent to secret mailing list.
>
> And "threat-model.rst" says that kernel gives everyone "fair share"
> of resources.
>
> This can be interpreted so: if scheduler is not fair enough, then this is
> security bug and should be reported to secret mailing list. I don't think
> this is what you meant.
Within reason of course, please use your best judgement.
> > +When hardware fails to maintain its specified isolation (e.g., CPU bugs,
> > +side-channels, hardware response to unexpected inputs), the kernel will usually
> > +attempt to implement reasonable mitigations. These are best-effort measures
> > +intended to reduce the attack surface or elevate the cost of an attack within
> > +the limits of the hardware's facilities; they do not constitute a
> > +kernel-provided safety guarantee.
>
> "best-effort measures" and "they do not constitute a kernel-provided safety
> guarantee" can be interpreted so: if someone finds yet another Meltdown-like
> side-channel CPU bug, then this is not security bug, and should be
> reported openly. I don't think this is what you meant.
Again, please be reasonable. Hardware bugs have their own reporting
process that we have well documented.
> > + affect the system's availability (shutdown, reboot, panic, hang, or making
> > + the system unresponsive via unbounded resource exhaustion).
>
> So if unprivileged process can crash system, then this is security bug?
Yes.
> Also I'm not sure "unbounded resource exhaustion" is correct here.
Why not?
> As well as I understand, by default kernel and distros don't set any
> memory limits or limits for number of processes for unprivileged processes,
> so unprivileged process can easily cause resource exhaustion by
> allocating a lot of memory or by fork bomb.
That's a distro problem, not a kernel problem.
> So, I think you should instead say that unprivileged process, which
> has memory limit (and other limits) set using cgroups, should not
> be able to cause resource exhaustion.
Patches are always gladly accepted. But again, be reasonable please,
this isn't a legal document :)
> > +are designed to be accessible to regular local users with a low risk (e.g.
> > +kernel logs via ``/proc/kmsg``), some would expose enough information to
>
> /proc/kmsg has rights "-r--------", so I think there is error here.
>
> ---------------
>
> Finally, I have questions:
>
> - If unprivileged user created process, which is impossible to kill
> by privileged process, is this security bug?
Sounds like a bug, we can deal with it that way.
> - If unprivileged user prevents privileged user from suspending
> system, is this security bug?
Physical access of suspending a machine feels like an odd threat model
to be worried about :)
If you have bugs that you feel are security issues like the above,
great, please report them and we can take them on a case-by-case basis.
This document is meant as a starting point for that, and to help remove
a huge number of "this is a security bug!" reports that we keep getting
that are obviously not that.
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v3 2/3] Documentation: security-bugs: explain what is and is not a security bug
From: Askar Safin @ 2026-06-09 8:33 UTC (permalink / raw)
To: w
Cc: corbet, greg, gregkh, leon, linux-doc, linux-kernel, security,
skhan, workflows
In-Reply-To: <20260509094755.2838-3-w@1wt.eu>
Willy Tarreau <w@1wt.eu>:
> +in a way that allows multiple local users to get a fair share of the available
Your "security-bugs.rst" says that we should consult "threat-model.rst" to
determine whether a bug should be sent to secret mailing list.
And "threat-model.rst" says that kernel gives everyone "fair share"
of resources.
This can be interpreted so: if scheduler is not fair enough, then this is
security bug and should be reported to secret mailing list. I don't think
this is what you meant.
> +When hardware fails to maintain its specified isolation (e.g., CPU bugs,
> +side-channels, hardware response to unexpected inputs), the kernel will usually
> +attempt to implement reasonable mitigations. These are best-effort measures
> +intended to reduce the attack surface or elevate the cost of an attack within
> +the limits of the hardware's facilities; they do not constitute a
> +kernel-provided safety guarantee.
"best-effort measures" and "they do not constitute a kernel-provided safety
guarantee" can be interpreted so: if someone finds yet another Meltdown-like
side-channel CPU bug, then this is not security bug, and should be
reported openly. I don't think this is what you meant.
> + affect the system's availability (shutdown, reboot, panic, hang, or making
> + the system unresponsive via unbounded resource exhaustion).
So if unprivileged process can crash system, then this is security bug?
Also I'm not sure "unbounded resource exhaustion" is correct here.
As well as I understand, by default kernel and distros don't set any
memory limits or limits for number of processes for unprivileged processes,
so unprivileged process can easily cause resource exhaustion by
allocating a lot of memory or by fork bomb.
So, I think you should instead say that unprivileged process, which
has memory limit (and other limits) set using cgroups, should not
be able to cause resource exhaustion.
> +are designed to be accessible to regular local users with a low risk (e.g.
> +kernel logs via ``/proc/kmsg``), some would expose enough information to
/proc/kmsg has rights "-r--------", so I think there is error here.
---------------
Finally, I have questions:
- If unprivileged user created process, which is impossible to kill
by privileged process, is this security bug?
- If unprivileged user prevents privileged user from suspending
system, is this security bug?
--
Askar Safin
^ permalink raw reply
* [PATCH v4 2/2] kunit: tool: Add (primitive) support for outputting JUnit XML
From: David Gow @ 2026-06-06 1:38 UTC (permalink / raw)
To: Brendan Higgins, Rae Moar, Shuah Khan, Thomas Weißschuh
Cc: David Gow, kunit-dev, linux-kernel, linux-kselftest, workflows
In-Reply-To: <20260606013827.240790-1-david@davidgow.net>
This is used by things like Jenkins and other CI systems, which can
pretty-print the test output and potentially provide test-level comparisons
between runs.
The implementation here is pretty basic: it only provides the raw results,
split into tests and test suites, and doesn't provide any overall metadata.
However, CI systems like Jenkins can ingest it and it is already useful.
Reviewed-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
Signed-off-by: David Gow <david@davidgow.net>
---
Only patch 1 changed, so v4 is the same as v3. I'm reserving the right to
switch to a more event-based XML writer in a follow-up, but that'd
probably happen as a part of a more broad refactoring of the various output
formats.
No changes since v3:
https://lore.kernel.org/all/20260604123207.2615485-2-david@davidgow.net/
Changes since v2:
https://lore.kernel.org/all/20260502024918.1056954-1-david@davidgow.net/
- Rework to use Python's ElementTree XML writing API (Thanks Thomas)
- Output the reason for a test to be skipped.
Changes since v1:
https://lore.kernel.org/all/20260119073426.1952867-1-davidgow@google.com/
- Use python's provided XML quote escaping, rather than coding our own (Thanks Thomas)
- Output proper <skipped> tags for skipped tests
- Report crashed tests as <error>
- Don't output <system-out> tags if there are no lines of log data
---
Documentation/dev-tools/kunit/run_wrapper.rst | 3 +
tools/testing/kunit/kunit.py | 21 ++++++-
tools/testing/kunit/kunit_junit.py | 61 +++++++++++++++++++
tools/testing/kunit/kunit_tool_test.py | 39 +++++++++++-
4 files changed, 120 insertions(+), 4 deletions(-)
create mode 100644 tools/testing/kunit/kunit_junit.py
diff --git a/Documentation/dev-tools/kunit/run_wrapper.rst b/Documentation/dev-tools/kunit/run_wrapper.rst
index 770bb09a475a..cecc110a3399 100644
--- a/Documentation/dev-tools/kunit/run_wrapper.rst
+++ b/Documentation/dev-tools/kunit/run_wrapper.rst
@@ -324,6 +324,9 @@ command line arguments:
- ``--json``: If set, stores the test results in a JSON format and prints to `stdout` or
saves to a file if a filename is specified.
+- ``--junit``: If set, stores the test results in JUnit XML format and prints to `stdout` or
+ saves to a file if a filename is specified.
+
- ``--filter``: Specifies filters on test attributes, for example, ``speed!=slow``.
Multiple filters can be used by wrapping input in quotes and separating filters
by commas. Example: ``--filter "speed>slow, module=example"``.
diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py
index 742f5c555666..ac3f7159e67f 100755
--- a/tools/testing/kunit/kunit.py
+++ b/tools/testing/kunit/kunit.py
@@ -21,6 +21,7 @@ from enum import Enum, auto
from typing import Iterable, List, Optional, Sequence, Tuple
import kunit_json
+import kunit_junit
import kunit_kernel
import kunit_parser
from kunit_printer import stdout, null_printer
@@ -49,6 +50,7 @@ class KunitBuildRequest(KunitConfigRequest):
class KunitParseRequest:
raw_output: Optional[str]
json: Optional[str]
+ junit: Optional[str]
summary: bool
failed: bool
@@ -268,6 +270,13 @@ def parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input
stdout.print_with_timestamp("Test results stored in %s" %
os.path.abspath(request.json))
+ if request.junit:
+ if request.junit == 'stdout':
+ kunit_junit.print_junit_result(test=test)
+ else:
+ kunit_junit.write_junit_result(test=test,filename=request.junit)
+ stdout.print_with_timestamp(f"Test results stored in {os.path.abspath(request.junit)}")
+
if test.status != kunit_parser.TestStatus.SUCCESS:
return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
@@ -309,6 +318,7 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree,
# So we hackily automatically rewrite --json => --json=stdout
pseudo_bool_flag_defaults = {
'--json': 'stdout',
+ '--junit': 'stdout',
'--raw_output': 'kunit',
}
def massage_argv(argv: Sequence[str]) -> Sequence[str]:
@@ -459,6 +469,11 @@ def add_parse_opts(parser: argparse.ArgumentParser) -> None:
help='Prints parsed test results as JSON to stdout or a file if '
'a filename is specified. Does nothing if --raw_output is set.',
type=str, const='stdout', default=None, metavar='FILE')
+ parser.add_argument('--junit',
+ nargs='?',
+ help='Prints parsed test results as JUnit XML to stdout or a file if '
+ 'a filename is specified. Does nothing if --raw_output is set.',
+ type=str, const='stdout', default=None, metavar='FILE')
parser.add_argument('--summary',
help='Prints only the summary line for parsed test results.'
'Does nothing if --raw_output is set.',
@@ -502,6 +517,7 @@ def run_handler(cli_args: argparse.Namespace) -> None:
jobs=cli_args.jobs,
raw_output=cli_args.raw_output,
json=cli_args.json,
+ junit=cli_args.junit,
summary=cli_args.summary,
failed=cli_args.failed,
timeout=cli_args.timeout,
@@ -552,6 +568,7 @@ def exec_handler(cli_args: argparse.Namespace) -> None:
exec_request = KunitExecRequest(raw_output=cli_args.raw_output,
build_dir=cli_args.build_dir,
json=cli_args.json,
+ junit=cli_args.junit,
summary=cli_args.summary,
failed=cli_args.failed,
timeout=cli_args.timeout,
@@ -580,7 +597,9 @@ def parse_handler(cli_args: argparse.Namespace) -> None:
# We know nothing about how the result was created!
metadata = kunit_json.Metadata()
request = KunitParseRequest(raw_output=cli_args.raw_output,
- json=cli_args.json, summary=cli_args.summary,
+ json=cli_args.json,
+ junit=cli_args.junit,
+ summary=cli_args.summary,
failed=cli_args.failed)
result, _ = parse_tests(request, metadata, kunit_output)
if result.status != KunitStatus.SUCCESS:
diff --git a/tools/testing/kunit/kunit_junit.py b/tools/testing/kunit/kunit_junit.py
new file mode 100644
index 000000000000..3622070358e7
--- /dev/null
+++ b/tools/testing/kunit/kunit_junit.py
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Generates JUnit XML files from KUnit test results
+#
+# Copyright (C) 2026, Google LLC and David Gow.
+
+from xml.sax.saxutils import quoteattr, XMLGenerator
+import xml.etree.ElementTree as ET
+from kunit_parser import Test, TestStatus
+from typing import Optional
+
+# Get a string representing a tes suite (including subtests) in JUnit XML
+def get_test_suite(test: Test, parent: Optional[ET.Element]) -> ET.Element:
+ suite_attrs = {
+ 'name': test.name,
+ 'tests': str(test.counts.total()),
+ 'failures': str(test.counts.failed),
+ 'skipped': str(test.counts.skipped),
+ 'errors': str(test.counts.crashed + test.counts.errors),
+ }
+
+ if parent is not None:
+ test_suite_element = ET.SubElement(parent, 'testsuite', suite_attrs)
+ else:
+ test_suite_element = ET.Element('testsuite', suite_attrs)
+
+ for subtest in test.subtests:
+ if subtest.subtests:
+ get_test_suite(subtest, test_suite_element)
+ continue
+ test_case_element = ET.SubElement(test_suite_element, 'testcase', {'name': subtest.name})
+ if subtest.status == TestStatus.FAILURE:
+ ET.SubElement(test_case_element, 'failure', {}).text = 'Test Failed'
+ elif subtest.status == TestStatus.SKIPPED:
+ ET.SubElement(test_case_element, 'skipped', {}).text = subtest.skip_reason
+ elif subtest.status == TestStatus.TEST_CRASHED:
+ ET.SubElement(test_case_element, 'error', {}).text = 'Test Crashed'
+
+ if subtest.log:
+ ET.SubElement(test_case_element, 'system-out', {}).text = "\n".join(subtest.log)
+
+ return test_suite_element
+
+# Get a string for an entire XML file for the test structure starting at test
+def get_junit_result(test: Test) -> str:
+ root_element = get_test_suite(test, None)
+ ET.indent(root_element)
+ return ET.tostring(root_element, encoding="unicode", xml_declaration=True)
+
+# Print a JUnit result to stdout.
+def print_junit_result(test: Test) -> None:
+ root_element = get_test_suite(test, None)
+ ET.indent(root_element)
+ ET.dump(root_element)
+
+# Write an entire XML file for the test structure starting at test
+def write_junit_result(test: Test, filename: str) -> None:
+ root_element = get_test_suite(test, None)
+ ET.indent(root_element)
+ root_et = ET.ElementTree(root_element)
+ root_et.write(filename, encoding='utf-8', xml_declaration=True)
diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
index 5ebd551b5072..da88c3a1651d 100755
--- a/tools/testing/kunit/kunit_tool_test.py
+++ b/tools/testing/kunit/kunit_tool_test.py
@@ -24,6 +24,7 @@ import kunit_config
import kunit_parser
import kunit_kernel
import kunit_json
+import kunit_junit
import kunit
from kunit_printer import stdout
@@ -693,6 +694,38 @@ class StrContains(str):
def __eq__(self, other):
return self in other
+class KUnitJUnitTest(unittest.TestCase):
+ def setUp(self):
+ self.print_mock = mock.patch('kunit_printer.Printer.print').start()
+ self.addCleanup(mock.patch.stopall)
+
+ def _junit_string(self, log_file):
+ with open(_test_data_path(log_file)) as file:
+ test_result = kunit_parser.parse_run_tests(file, stdout)
+ junit_string = kunit_junit.get_junit_result(
+ test=test_result)
+ print(junit_string)
+ return junit_string
+
+ def test_failed_test_junit(self):
+ result = self._junit_string('test_is_test_passed-failure.log')
+ self.assertTrue("<failure>" in result)
+
+ def test_skipped_test_junit(self):
+ result = self._junit_string('test_skip_tests.log')
+ self.assertTrue("<skipped>" in result)
+ self.assertTrue("skipped=\"1\"" in result)
+
+ def test_crashed_test_junit(self):
+ result = self._junit_string('test_kernel_panic_interrupt.log')
+ self.assertTrue("<error>" in result);
+
+ def test_no_tests_junit(self):
+ result = self._junit_string('test_is_test_passed-no_tests_run_with_header.log')
+ self.assertTrue("tests=\"0\"" in result)
+ self.assertFalse("testcase" in result)
+
+
class KUnitMainTest(unittest.TestCase):
def setUp(self):
path = _test_data_path('test_is_test_passed-all_passed.log')
@@ -940,7 +973,7 @@ class KUnitMainTest(unittest.TestCase):
self.linux_source_mock.run_kernel.return_value = ['TAP version 14', 'init: random output'] + want
got = kunit._list_tests(self.linux_source_mock,
- kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False))
+ kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False))
self.assertEqual(got, want)
# Should respect the user's filter glob when listing tests.
self.linux_source_mock.run_kernel.assert_called_once_with(
@@ -953,7 +986,7 @@ class KUnitMainTest(unittest.TestCase):
# Should respect the user's filter glob when listing tests.
mock_tests.assert_called_once_with(mock.ANY,
- kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False, False))
+ kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False, False))
self.linux_source_mock.run_kernel.assert_has_calls([
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', filter='', filter_action=None, timeout=300),
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', filter='', filter_action=None, timeout=300),
@@ -966,7 +999,7 @@ class KUnitMainTest(unittest.TestCase):
# Should respect the user's filter glob when listing tests.
mock_tests.assert_called_once_with(mock.ANY,
- kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'test', False, False, False))
+ kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'test', False, False, False))
self.linux_source_mock.run_kernel.assert_has_calls([
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', filter='', filter_action=None, timeout=300),
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', filter='', filter_action=None, timeout=300),
--
2.54.0
^ permalink raw reply related
* [PATCH v4 1/2] kunit: tool: Parse and print the reason tests are skipped
From: David Gow @ 2026-06-06 1:38 UTC (permalink / raw)
To: Brendan Higgins, Rae Moar, Shuah Khan, Thomas Weißschuh
Cc: David Gow, kunit-dev, linux-kernel, linux-kselftest, workflows
When a KUnit test (or other KTAP test) is skipped, a "skip reason" can be
provided. kunit.py has never done anything with this, ignoring anything
included in the KTAP output after the 'SKIP' directive.
Since we have it, and it's used, print it in a nice friendly yellow in
parentheses after a skipped test's name.
(And, by parsing it, it can be included in the JUnit results as well.)
Signed-off-by: David Gow <david@davidgow.net>
---
There are a few bits of KTAP that kunit.py has never actually parsed, and
this is one of them. It's also nice to have a good way of quickly seeing
why a test has been skipped, given most tests do provide good reason
strings.
Happy to hear comments about the style: yellow in parentheses looks pretty
good here, but could be a bit confusing if the test name includes parentheses
and there's no colour support.
Changes since v3:
https://lore.kernel.org/all/20260604123207.2615485-1-david@davidgow.net/
- Make the parentheses yellow as well (Thanks, Thomas)
- Add some tests for the actual parsing of the SKIP reason. (Thanks, Thomas)
This patch was new in v2.
---
tools/testing/kunit/kunit_parser.py | 15 ++++++++++-----
tools/testing/kunit/kunit_tool_test.py | 17 +++++++++++++++++
2 files changed, 27 insertions(+), 5 deletions(-)
diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py
index 0e1d2f4985eb..7a021517f58b 100644
--- a/tools/testing/kunit/kunit_parser.py
+++ b/tools/testing/kunit/kunit_parser.py
@@ -44,11 +44,12 @@ class Test:
self.subtests = [] # type: List[Test]
self.log = [] # type: List[str]
self.counts = TestCounts()
+ self.skip_reason = ''
def __str__(self) -> str:
"""Returns string representation of a Test class object."""
return (f'Test({self.status}, {self.name}, {self.expected_count}, '
- f'{self.subtests}, {self.log}, {self.counts})')
+ f'{self.subtests}, {self.log}, {self.counts}, {self.skip_reason})')
def __repr__(self) -> str:
"""Returns string representation of a Test class object."""
@@ -352,9 +353,9 @@ def parse_test_plan(lines: LineStream, test: Test) -> bool:
lines.pop()
return True
-TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(- )?([^#]*)( # .*)?$')
+TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(:?- )?([^#]*)( # .*)?$')
-TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(- )?(.*) # SKIP ?(.*)$')
+TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(:?- )?(.*) # SKIP ?(.*)$')
def peek_test_name_match(lines: LineStream, test: Test) -> bool:
"""
@@ -418,7 +419,7 @@ def parse_test_result(lines: LineStream, test: Test,
# Set name of test object
if skip_match:
- test.name = skip_match.group(4) or skip_match.group(5)
+ test.name = skip_match.group(4)
else:
test.name = match.group(4)
@@ -431,6 +432,7 @@ def parse_test_result(lines: LineStream, test: Test,
status = match.group(1)
if skip_match:
test.status = TestStatus.SKIPPED
+ test.skip_reason = skip_match.group(5) or ''
elif status == 'ok':
test.status = TestStatus.SUCCESS
else:
@@ -539,7 +541,10 @@ def format_test_result(test: Test, printer: Printer) -> str:
if test.status == TestStatus.SUCCESS:
return printer.green('[PASSED] ') + test.name
if test.status == TestStatus.SKIPPED:
- return printer.yellow('[SKIPPED] ') + test.name
+ skip_message = printer.yellow('[SKIPPED] ') + test.name
+ if test.skip_reason != '':
+ skip_message += printer.yellow(' (' + test.skip_reason + ')')
+ return skip_message
if test.status == TestStatus.NO_TESTS:
return printer.yellow('[NO TESTS RUN] ') + test.name
if test.status == TestStatus.TEST_CRASHED:
diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
index 267c33cecf87..5ebd551b5072 100755
--- a/tools/testing/kunit/kunit_tool_test.py
+++ b/tools/testing/kunit/kunit_tool_test.py
@@ -235,10 +235,27 @@ class KUnitParserTest(unittest.TestCase):
with open(skipped_log) as file:
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
+ # The test result is skipped, and the skip reason is valid
+ self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[1].subtests[1].status)
+ self.assertEqual("this test should be skipped", result.subtests[1].subtests[1].skip_reason)
+
# A skipped test does not fail the whole suite.
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=4, skipped=1))
+ def test_skipped_reason_parse(self):
+ skipped_log = _test_data_path('test_skip_all_tests.log')
+ with open(skipped_log) as file:
+ result = kunit_parser.parse_run_tests(file.readlines(), stdout)
+
+ # The first test is skipped, with the correct reaons
+ self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[0].subtests[0].status)
+ self.assertEqual("all tests skipped", result.subtests[0].subtests[0].skip_reason)
+
+ # The first suite is skipped, with no reason
+ self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[0].status)
+ self.assertEqual("", result.subtests[0].skip_reason)
+
def test_skipped_all_tests(self):
skipped_log = _test_data_path('test_skip_all_tests.log')
with open(skipped_log) as file:
--
2.54.0
^ permalink raw reply related
* Re: [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values
From: Nicolas Schier @ 2026-06-03 19:25 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, Dmitry Vyukov,
Andrey Konovalov, Andrew Morton, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, 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: <20260603-kcov-dataflow-next-20260603-v2-1-fee0939de2c4@est.tech>
On Wed, Jun 03, 2026 at 07:43:28PM +0200, Yunseong Kim wrote:
> Add a new KCOV subsystem that captures function arguments at entry and
> return values at exit, with automatic struct field expansion using
> compiler-generated DebugInfo metadata.
>
> Key components:
> - CONFIG_KCOV_DATAFLOW_ARGS: enables argument capture
> - CONFIG_KCOV_DATAFLOW_RET: enables return value capture
> - /sys/kernel/debug/kcov_dataflow: separate device from legacy kcov
> - Ioctl namespace 'd' (KCOV_DF_INIT_TRACE, KCOV_DF_ENABLE, KCOV_DF_DISABLE)
> - Per-task buffer: task->kcov_df_area with atomic xadd reservation
> - Fault-tolerant: all reads via copy_from_kernel_nofault()
> - Recursion-safe: notrace __no_sanitize_coverage noinline
> - ERR_PTR aware: skips struct expansion for error pointers
>
> 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.
>
> 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.
> + * Own buffer, own ioctl, own mmap. No printk — buffer only.
> + *
> + * TLV record layout (all u64):
> + * area[0]: total u64 words written (atomic counter)
> + * [pos+0]: type_and_seq (0xE=entry|0xF=return in upper 4 bits, seq in lower 24)
> + * [pos+1]: PC
> + * [pos+2]: raw pointer | (arg_idx << 56) | (arg_size << 48) for entry
> + * [pos+3..N]: field values (or scalar value if num_fields=0)
> + */
> +#define KCOV_DF_TYPE_ENTRY 0xE0000000ULL
> +#define KCOV_DF_TYPE_RET 0xF0000000ULL
> +#define KCOV_DF_MAGIC_BAD 0xBADADD85ULL
> +#define KCOV_DF_IS_ERR(p) ((unsigned long)(p) >= (unsigned long)-4095UL)
> +
> +/* Ioctl commands for /sys/kernel/debug/kcov_dataflow */
> +#define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
> +#define KCOV_DF_ENABLE _IO('d', 100)
> +#define KCOV_DF_DISABLE _IO('d', 101)
> +
> +struct kcov_dataflow {
> + refcount_t refcount;
> + spinlock_t lock;
> + unsigned int size; /* in u64 words */
> + void *area;
> + struct task_struct *t;
> +};
> +
> +static void kcov_df_put(struct kcov_dataflow *df)
> +{
> + if (refcount_dec_and_test(&df->refcount)) {
> + vfree(df->area);
> + kfree(df);
> + }
> +}
> +
> +/*
> + * Core write function — no printk, no locks, just atomic buffer write.
> + * Called from __sanitizer_cov_trace_args/ret in instrumented code.
> + */
> +static noinline notrace __no_sanitize_coverage void
> +kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
> + u64 *offsets, u32 num_fields)
> +{
> + struct task_struct *t = current;
> + u64 *area;
> + unsigned long pos, max_pos;
> + u32 record_len, seq, i;
> +
> + if (!t->kcov_df_enabled)
> + return;
> +
> + area = (u64 *)t->kcov_df_area;
> + if (!area)
> + return;
> +
> + max_pos = t->kcov_df_size;
> +
> + /* Record: header(1) + pc(1) + meta(1) + fields or scalar(max 1) */
> + record_len = 3 + (num_fields > 0 ? num_fields : 1);
> +
> + /* 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;
> + }
> +
> + seq = ++t->kcov_dataflow_seq;
> + area[pos] = type_marker | (seq & 0x00FFFFFFULL);
> + area[pos + 1] = pc;
> + area[pos + 2] = meta;
> +
> + if (num_fields == 0) {
> + /* Scalar: read value from ptr using size from meta */
> + u64 val = 0;
> + u32 sz = (meta >> 48) & 0xFF;
> +
> + if (sz > sizeof(val))
> + sz = sizeof(val);
> + if (ptr && !KCOV_DF_IS_ERR(ptr))
> + copy_from_kernel_nofault(&val, ptr, sz);
> + area[pos + 3] = val;
> + } else {
> + /* Struct fields */
> + if (KCOV_DF_IS_ERR(ptr)) {
> + for (i = 0; i < num_fields; i++)
> + area[pos + 3 + i] = KCOV_DF_MAGIC_BAD;
> + return;
> + }
> + for (i = 0; i < num_fields; i++) {
> + u64 off, sz, val = KCOV_DF_MAGIC_BAD;
> + void *fa;
> +
> + if (copy_from_kernel_nofault(&off, &offsets[i * 2], sizeof(off)) ||
> + copy_from_kernel_nofault(&sz, &offsets[i * 2 + 1], sizeof(sz))) {
> + area[pos + 3 + i] = KCOV_DF_MAGIC_BAD;
> + continue;
> + }
> + fa = (void *)((unsigned long)ptr + off);
> + val = 0;
> + if (sz <= sizeof(val))
> + copy_from_kernel_nofault(&val, fa, sz);
> + else
> + copy_from_kernel_nofault(&val, fa, sizeof(val));
> + area[pos + 3 + i] = val;
> + }
> + }
> +}
> +
> +#ifdef CONFIG_KCOV_DATAFLOW_ARGS
> +noinline void notrace __no_sanitize_coverage
> +__sanitizer_cov_trace_args(u64 pc, u32 arg_idx, u32 arg_size, void *arg_ptr,
> + u64 *offsets, u32 num_fields);
> +
> +noinline void notrace __no_sanitize_coverage
> +__sanitizer_cov_trace_args(u64 pc, u32 arg_idx, u32 arg_size, void *arg_ptr,
> + u64 *offsets, u32 num_fields)
> +{
> + /* meta: [arg_idx(8) | arg_size(8) | ptr(48)] */
> + u64 meta = ((u64)arg_idx << 56) | ((u64)arg_size << 48) |
> + ((u64)(unsigned long)arg_ptr & 0xFFFFFFFFFFFFULL);
> + kcov_df_write(KCOV_DF_TYPE_ENTRY, pc, meta, arg_ptr,
> + offsets, num_fields);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_args);
> +#endif
> +
> +#ifdef CONFIG_KCOV_DATAFLOW_RET
> +noinline void notrace __no_sanitize_coverage
> +__sanitizer_cov_trace_ret(u64 pc, u32 ret_size, void *ret_val,
> + u64 *offsets, u32 num_fields);
> +
> +noinline void notrace __no_sanitize_coverage
> +__sanitizer_cov_trace_ret(u64 pc, u32 ret_size, void *ret_val,
> + u64 *offsets, u32 num_fields)
> +{
> + u64 meta = ((u64)ret_size << 48) |
> + ((u64)(unsigned long)ret_val & 0xFFFFFFFFFFFFULL);
> + kcov_df_write(KCOV_DF_TYPE_RET, pc, meta, ret_val,
> + offsets, num_fields);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_ret);
> +#endif
> +
> +/* --- /sys/kernel/debug/kcov_dataflow file operations --- */
> +
> +static int kcov_df_open(struct inode *inode, struct file *filep)
> +{
> + struct kcov_dataflow *df;
> +
> + df = kzalloc(sizeof(*df), GFP_KERNEL);
> + if (!df)
> + return -ENOMEM;
> + spin_lock_init(&df->lock);
> + refcount_set(&df->refcount, 1);
> + filep->private_data = df;
> + return nonseekable_open(inode, filep);
> +}
> +
> +static int kcov_df_close(struct inode *inode, struct file *filep)
> +{
> + struct kcov_dataflow *df = filep->private_data;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&df->lock, flags);
> + if (df->t == current) {
> + current->kcov_df_enabled = false;
> + current->kcov_df_area = NULL;
> + current->kcov_df_size = 0;
> + df->t = NULL;
> + }
> + spin_unlock_irqrestore(&df->lock, flags);
> + kcov_df_put(df);
> + return 0;
> +}
> +
> +static int kcov_df_mmap(struct file *filep, struct vm_area_struct *vma)
> +{
> + struct kcov_dataflow *df = filep->private_data;
> + unsigned long size, off;
> + struct page *page;
> + unsigned long flags;
> + void *area;
> + int res = 0;
> +
> + spin_lock_irqsave(&df->lock, flags);
> + size = df->size * sizeof(u64);
> + if (!df->area || vma->vm_pgoff != 0 ||
> + vma->vm_end - vma->vm_start != size) {
> + res = -EINVAL;
> + goto out;
> + }
> + area = df->area;
> + spin_unlock_irqrestore(&df->lock, flags);
> +
> + vm_flags_set(vma, VM_DONTEXPAND);
> + for (off = 0; off < size; off += PAGE_SIZE) {
> + page = vmalloc_to_page(area + off);
> + res = vm_insert_page(vma, vma->vm_start + off, page);
> + if (res)
> + return res;
> + }
> + return 0;
> +out:
> + spin_unlock_irqrestore(&df->lock, flags);
> + return res;
> +}
> +
> +static long kcov_df_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
> +{
> + struct kcov_dataflow *df = filep->private_data;
> + unsigned long flags;
> + unsigned long size;
> + int res = 0;
> +
> + spin_lock_irqsave(&df->lock, flags);
> + switch (cmd) {
> + case KCOV_DF_INIT_TRACE:
> + if (df->area) {
> + res = -EBUSY;
> + break;
> + }
> + size = arg;
> + if (size < 2 || size > (128 << 20) / sizeof(u64)) {
> + res = -EINVAL;
> + break;
> + }
> + spin_unlock_irqrestore(&df->lock, flags);
> + df->area = vmalloc_user(size * sizeof(u64));
> + if (!df->area)
> + return -ENOMEM;
> + spin_lock_irqsave(&df->lock, flags);
> + df->size = size;
> + break;
> +
> + case KCOV_DF_ENABLE:
> + if (!df->area || df->t) {
> + res = -EINVAL;
> + break;
> + }
> + df->t = current;
> + current->kcov_df_area = df->area;
> + current->kcov_df_size = df->size;
> + current->kcov_dataflow_seq = 0;
> + /* Barrier before enabling */
> + barrier();
> + current->kcov_df_enabled = true;
> + break;
> +
> + case KCOV_DF_DISABLE:
> + if (df->t != current) {
> + res = -EINVAL;
> + break;
> + }
> + current->kcov_df_enabled = false;
> + barrier();
> + current->kcov_df_area = NULL;
> + current->kcov_df_size = 0;
> + df->t = NULL;
> + break;
> +
> + default:
> + res = -ENOTTY;
> + }
> + spin_unlock_irqrestore(&df->lock, flags);
> + return res;
> +}
> +
> +static const struct file_operations kcov_df_fops = {
> + .open = kcov_df_open,
> + .unlocked_ioctl = kcov_df_ioctl,
> + .compat_ioctl = kcov_df_ioctl,
> + .mmap = kcov_df_mmap,
> + .release = kcov_df_close,
> +};
> +#endif /* CONFIG_KCOV_DATAFLOW_ARGS || CONFIG_KCOV_DATAFLOW_RET */
> +
> static void kcov_start(struct task_struct *t, struct kcov *kcov,
> unsigned int size, void *area, enum kcov_mode mode,
> int sequence)
> @@ -1146,6 +1428,15 @@ static int __init kcov_init(void)
> */
> debugfs_create_file_unsafe("kcov", 0600, NULL, NULL, &kcov_fops);
>
> +#if defined(CONFIG_KCOV_DATAFLOW_ARGS) || defined(CONFIG_KCOV_DATAFLOW_RET)
> + /*
> + * Toggle verbose printk: echo 1 > /sys/kernel/debug/kcov_dataflow_verbose
> + * Default off — zero overhead when not debugging.
> + */
> + debugfs_create_file_unsafe("kcov_dataflow", 0600, NULL, NULL,
> + &kcov_df_fops);
> +#endif
> +
> #ifdef CONFIG_KCOV_SELFTEST
> selftest();
> #endif
> 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.
>
> +
> +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.
You might want to add an empty line here.
--
Nicolas
^ permalink raw reply
* Re: [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
From: Alexander Potapenko @ 2026-06-05 16:20 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, 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: <20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech>
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,
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?
- The test modules seem to be used only in manual testing. Can you
convert them to kselftests or remove them?
- At this point, long dashes in the kernel codebase are quite rare,
and I don't see a reason to add more.
^ permalink raw reply
* Re: [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values
From: Alexander Potapenko @ 2026-06-05 16:05 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, 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: <20260603-kcov-dataflow-next-20260603-v2-1-fee0939de2c4@est.tech>
> - 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.
>
> 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?
>
> 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.
> + * Own buffer, own ioctl, own mmap. No printk — buffer only.
Can you please not use these long dashes in C code?
> +/*
> + * 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?
^ permalink raw reply
* Re: [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation
From: Alexander Potapenko @ 2026-06-05 15:29 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, 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: <20260603-kcov-dataflow-next-20260603-v2-2-fee0939de2c4@est.tech>
On Wed, Jun 3, 2026 at 7:43 PM Yunseong Kim <yunseong.kim@est.tech> wrote:
>
> Add CFLAGS_KCOV_DATAFLOW and RUSTFLAGS_KCOV_DATAFLOW exports to
> scripts/Makefile.kcov, containing:
> -fsanitize-coverage=dataflow-args,dataflow-ret -g
> (with optional -fno-inline via CONFIG_KCOV_DATAFLOW_NO_INLINE)
>
> scripts/Makefile.lib applies these flags when a module's Makefile sets:
> KCOV_DATAFLOW_file.o := y (per-file)
> KCOV_DATAFLOW := y (per-directory)
>
> Also supports CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL for global enablement.
> The flags are only applied to kernel objects (same guard as basic KCOV).
>
> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
> ---
> scripts/Makefile.kcov | 6 ++++++
> scripts/Makefile.lib | 7 +++++++
> 2 files changed, 13 insertions(+)
>
> diff --git a/scripts/Makefile.kcov b/scripts/Makefile.kcov
> index 78305a84ba9d..101173fe194b 100644
> --- a/scripts/Makefile.kcov
> +++ b/scripts/Makefile.kcov
> @@ -2,10 +2,16 @@
> kcov-flags-y += -fsanitize-coverage=trace-pc
> kcov-flags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -fsanitize-coverage=trace-cmp
>
> +# KCOV dataflow: trace function args and return values
> +kcov-dataflow-flags-y := -fsanitize-coverage=dataflow-args,dataflow-ret -g
Instead of adding debug info here, consider adding a dependency on
CONFIG_DEBUG_INFO.
> export CFLAGS_KCOV := $(kcov-flags-y)
> +export CFLAGS_KCOV_DATAFLOW := $(kcov-dataflow-flags-y)
> +export RUSTFLAGS_KCOV_DATAFLOW := -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 -Cllvm-args=-sanitizer-coverage-dataflow-args -Cllvm-args=-sanitizer-coverage-dataflow-ret -Cdebuginfo=2
Ditto.
^ permalink raw reply
* Re: [RFC PATCH v2 4/6] tools/kcov-dataflow: add userspace consumer and test modules
From: Alexander Potapenko @ 2026-06-05 15:19 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, 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: <20260603-kcov-dataflow-next-20260603-v2-4-fee0939de2c4@est.tech>
On Wed, Jun 3, 2026 at 7:43 PM Yunseong Kim <yunseong.kim@est.tech> wrote:
>
> Add tools/kcov-dataflow/ with:
>
> - trigger.c: userspace consumer that opens /sys/kernel/debug/kcov_dataflow,
> mmaps the buffer, enables recording, triggers a kernel path, and dumps
> the captured TLV records.
>
> - kcov-view.py: visualization tool that parses and pretty-prints the
> binary TLV buffer with struct field expansion and symbol resolution.
>
> - eight_args_c/eight_args_mod.c: stress test with 1-8 argument functions
> verifying correct capture of register and stack-passed arguments.
I think the tests should better go to tools/testing/selftests.
> diff --git a/tools/kcov-dataflow/.gitignore b/tools/kcov-dataflow/.gitignore
> new file mode 100644
> index 000000000000..1f35df8fbd07
> --- /dev/null
> +++ b/tools/kcov-dataflow/.gitignore
I am not sure about the conventions, but a bunch of other .gitignore
files have SPDX headers.
> diff --git a/tools/kcov-dataflow/deep_module/Makefile b/tools/kcov-dataflow/deep_module/Makefile
> new file mode 100644
> index 000000000000..6afed580dc9a
> --- /dev/null
> +++ b/tools/kcov-dataflow/deep_module/Makefile
Makefiles must have SPDX headers.
> diff --git a/tools/kcov-dataflow/deep_module/deep_chain_mod.c b/tools/kcov-dataflow/deep_module/deep_chain_mod.c
> new file mode 100644
> index 000000000000..786e23c5d213
> --- /dev/null
> +++ b/tools/kcov-dataflow/deep_module/deep_chain_mod.c
> @@ -0,0 +1,224 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * deep_chain_mod.c - Demonstrates kcov_dataflow tracing through 10 nested
> + * function calls. An attacker-controlled "offset" value propagates from
> + * the entry point through transformations until it causes an OOB write
> + * in the deepest function.
I don't fully understand the purpose of this file. Are you going to
parse its output for testing?
> + *
> + * Call chain:
> + * entry_handler → parse_request → validate_header → extract_payload →
> + * transform_data → apply_filter → compute_index → lookup_slot →
> + * write_slot → commit_write (BUG: OOB here)
Call me old fashioned, but I think we can easily avoid the non-ASCII
characters here.
> +/* Function 10 (DEEPEST): The vulnerable write */
> +static noinline int commit_write(struct slot_table *table, u32 index, u64 value)
> +{
> + /* BUG: no bounds check on index — if index >= 8, OOB write */
> + table->slots[index] = value;
> + return 0;
> +}
Does this call chain need to be this long?
I assume it was extracted from some real-world example, but maybe
pruning it to 3-5 calls would demonstrate the capabilities of dataflow
tracing just as well?
> +
> + table = kzalloc(sizeof(*table), GFP_KERNEL);
> + if (!table)
> + return -ENOMEM;
> + table->num_slots = 8;
> +
> + /* The tainted data flow:
Please make sure to conform to
https://docs.kernel.org/process/coding-style.html#commenting
> +
> +/* Trigger: constructs a malicious request that causes index=12 (OOB) */
> +static ssize_t deep_trigger_write(struct file *file, const char __user *ubuf,
> + size_t count, loff_t *ppos)
> +{
> + u8 *buf;
> + struct request_header *hdr;
> + struct payload *pl;
> +
> + buf = kzalloc(256, GFP_KERNEL);
> + if (!buf)
> + return -ENOMEM;
> +
> + /* Craft malicious request */
> + hdr = (struct request_header *)buf;
> + hdr->magic = 0x50524F54; /* valid magic */
> + hdr->version = 1; /* valid version */
> + hdr->payload_offset = 16; /* offset to payload (valid position) */
> + hdr->payload_size = sizeof(struct payload);
> +
> + /* Craft payload that will produce OOB index */
> + pl = (struct payload *)(buf + 16);
> + pl->session_id = 0xAAAABBBBCCCCDDDDULL;
Much is happening in this code, making it hard to review, but all
these values do not seem to be used anywhere.
> +
> +noinline u64 func2(u64 a1, u64 a2)
> +{
> + return a1 + a2;
> +}
> +EXPORT_SYMBOL(func2);
> +
> +noinline u64 func3(u64 a1, u64 a2, u64 a3)
> +{
> + return a1 + a2 + a3;
> +}
> +EXPORT_SYMBOL(func3);
Would it be more readable to use some macro magic here?
E.g.:
```
#define DEFINE_SUM_FUNC(n, sum_expr, ...) \
noinline u64 func##n(__VA_ARGS__) \
{ \
return sum_expr; \
} \
EXPORT_SYMBOL(func##n)
DEFINE_SUM_FUNC(1, a1, u64 a1);
DEFINE_SUM_FUNC(2, a1 + a2, u64 a1, u64 a2);
DEFINE_SUM_FUNC(3, a1 + a2 + a3, u64 a1, u64 a2, u64 a3);
DEFINE_SUM_FUNC(4, a1 + a2 + a3 + a4, u64 a1, u64 a2, u64 a3, u64 a4);
DEFINE_SUM_FUNC(5, a1 + a2 + a3 + a4 + a5, u64 a1, u64 a2, u64 a3, u64
a4, u64 a5);
DEFINE_SUM_FUNC(6, a1 + a2 + a3 + a4 + a5 + a6, u64 a1, u64 a2, u64
a3, u64 a4, u64 a5, u64 a6);
DEFINE_SUM_FUNC(7, a1 + a2 + a3 + a4 + a5 + a6 + a7, u64 a1, u64 a2,
u64 a3, u64 a4, u64 a5, u64 a6, u64 a7);
DEFINE_SUM_FUNC(8, a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8, u64 a1, u64
a2, u64 a3, u64 a4, u64 a5, u64 a6, u64 a7, u64 a8);
```
You could as well define the function prototypes instead of applying
-Wno-missing-prototypes.
> +{
> + pr_info("func1(0x11)=0x%llx\n", func1(0x11));
> + pr_info("func2(0x11,0x22)=0x%llx\n", func2(0x11, 0x22));
> + pr_info("func3(0x11,0x22,0x33)=0x%llx\n",
> + func3(0x11, 0x22, 0x33));
> + pr_info("func4(0x11,..,0x44)=0x%llx\n",
> + func4(0x11, 0x22, 0x33, 0x44));
> + pr_info("func5(0x11,..,0x55)=0x%llx\n",
> + func5(0x11, 0x22, 0x33, 0x44, 0x55));
> + pr_info("func6(0x11,..,0x66)=0x%llx\n",
> + func6(0x11, 0x22, 0x33, 0x44, 0x55, 0x66));
> + pr_info("func7(0x11,..,0x77)=0x%llx\n",
> + func7(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77));
> + pr_info("func8(0x11,..,0x88)=0x%llx\n",
> + func8(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88));
> + return count;
> +}
The inconsistent output format here suggests that the output is never validated.
> +
> +static const struct proc_ops ops = { .proc_write = trigger_write };
This file should belong to debugfs rather than /proc/.
> diff --git a/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs b/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs
> new file mode 100644
> index 000000000000..11bbe1449eaf
> --- /dev/null
> +++ b/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs
> @@ -0,0 +1,114 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//! Verify kcov_dataflow captures 1-arg through 8-arg functions.
> +//! Write to /sys/kernel/debug/test_args_rust to trigger all 8.
Please make sure triggers for all your tests are consistent,
preferably somewhere under /sys/kernel/debug/kcov_testing/ or similar.
> +
> +Reads both /sys/kernel/debug/kcov (PC trace) and /sys/kernel/debug/kcov_dataflow
> +(args/ret), correlates by PC, and produces a human-readable call trace with
> +argument values and struct field expansion.
> +
> +Usage (inside guest or with appropriate permissions):
> + python3 kcov-view.py <trigger_command>
> +
> +Example:
> + python3 kcov-view.py "echo x > /proc/uaf_trigger"
> +
> +Output:
> + func+0x0 [module]
> + → a(arg[0]=0x1, arg[1]=0x2, arg[2]=0x3, arg[3]=struct{.f[0]=1, .f[1]=2, .f[2]=3})
> + ← ret = struct{.f[0]=1, .f[1]=2, .f[2]=3}
> + → a(arg[0]=0x0, arg[1]=0x0, arg[2]=0x1, arg[3]=NULL)
> + ← ret = 0x0
> +"""
> +import os, sys, struct, mmap, fcntl, subprocess, re, ctypes
> +from collections import defaultdict
> +
> +# Ioctl definitions (x86_64)
Why is this code x86-specific?
> +def parse_dataflow(buf, n):
> + """Parse TLV records from kcov_dataflow buffer into a list of events."""
> + events = []
> + i = 1
> + while i <= n and i < BUF_SIZE:
> + hdr = buf[i]
> + typ = hdr & 0xF0000000
> + seq = hdr & 0x00FFFFFF
These numbers are used more than once - please declare them as
constants instead.
> +
> + if typ not in (0xE0000000, 0xF0000000):
Same here, no one knows what these numbers stand for.
> + while i <= n and i < BUF_SIZE:
> + v = buf[i]
> + vtype = v & 0xF0000000
Please use helper functions to extract the type, size and whatnot.
> + if vtype == 0xE0000000 or vtype == 0xF0000000:
> + break
> + fields.append(v)
> + i += 1
> +
> + if typ == 0xE0000000:
> + arg_idx = (meta >> 56) & 0xFF
> + arg_sz = (meta >> 48) & 0xFF
> + ptr = meta & 0xFFFFFFFFFFFF
> + events.append({
> + "type": "entry", "seq": seq, "pc": pc,
> + "arg_idx": arg_idx, "arg_size": arg_sz,
> + "ptr": ptr, "fields": fields
> + })
This looks like it could benefit from OOP.
> diff --git a/tools/kcov-dataflow/trigger.c b/tools/kcov-dataflow/trigger.c
> new file mode 100644
> index 000000000000..7fa7b4414770
> --- /dev/null
> +++ b/tools/kcov-dataflow/trigger.c
> @@ -0,0 +1,125 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * trigger.c - Uses /sys/kernel/debug/kcov_dataflow to capture
This code seems to partially duplicate kcov-view.py, do you need both?
> +#define COVER_SIZE (64 * 1024) /* 64K u64 words = 512KB */
> +
> +static void dump_buffer(uint64_t *cover, uint64_t n)
> +{
> + uint64_t i = 1;
> +
> + printf("=== KCOV Dataflow TLV Dump (%lu words) ===\n", n);
> + while (i <= n && i < COVER_SIZE) {
> + uint64_t hdr = cover[i];
> + uint64_t type = hdr & 0xF0000000ULL;
Maybe we could use unions to unpack the header?
I don't mind the masks either, but please declare them as constants.
> +int main(int argc, char **argv)
> +{
> + const char *trigger_path = "/proc/uaf_trigger";
I couldn't find /proc/uaf_trigger anywhere, does it belong to this series?
If trigger.c is a general-purpose tool rather than a test script, it
shouldn't depend on the test modules.
--
Alexander Potapenko
Software Engineer
Google Germany GmbH
Erika-Mann-Straße, 33
80636 München
Geschäftsführer: Paul Manicle, Liana Sebastian
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
^ permalink raw reply
* Re: [PATCH v3 2/2] kunit: tool: Add (primitive) support for outputting JUnit XML
From: Thomas Weißschuh @ 2026-06-05 7:11 UTC (permalink / raw)
To: David Gow
Cc: Brendan Higgins, Rae Moar, Shuah Khan, kunit-dev, linux-kernel,
linux-kselftest, workflows
In-Reply-To: <20260604123207.2615485-2-david@davidgow.net>
On Thu, Jun 04, 2026 at 08:32:05PM +0800, David Gow wrote:
> This is used by things like Jenkins and other CI systems, which can
> pretty-print the test output and potentially provide test-level comparisons
> between runs.
>
> The implementation here is pretty basic: it only provides the raw results,
> split into tests and test suites, and doesn't provide any overall metadata.
> However, CI systems like Jenkins can ingest it and it is already useful.
>
> Signed-off-by: David Gow <david@davidgow.net>
Reviewed-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
> ---
> Here's version 3 of the JUnit support patch. This one uses python's ElementTree
> API to generate the XML, rather than writing strings directly. The only real
> difference in the output is that this escapes any log lines, rather than using
> CDATA sections. (There are both advantages and disadvantages to this, but
> they're all pretty minor. Equally, the option of using the
> xml.sax.saxutils.XMLGenerator API instead is a possibility. ElementTree ended
> up being slightly more aligned with the way the JSON version worked.)
I still somewhat prefer an event-based formatting.
(There is also xml.etree.ElementTree.TreeBuilder)
But if the JSON formatter works the same, its fine for me.
(...)
^ permalink raw reply
* Re: [PATCH v3 1/2] kunit: tool: Parse and print the reason tests are skipped
From: Thomas Weißschuh @ 2026-06-05 6:58 UTC (permalink / raw)
To: David Gow
Cc: Brendan Higgins, Rae Moar, Shuah Khan, kunit-dev, linux-kernel,
linux-kselftest, workflows
In-Reply-To: <20260604123207.2615485-1-david@davidgow.net>
On Thu, Jun 04, 2026 at 08:32:04PM +0800, David Gow wrote:
> When a KUnit test (or other KTAP test) is skipped, a "skip reason" can be
> provided. kunit.py has never done anything with this, ignoring anything
> included in the KTAP output after the 'SKIP' directive.
>
> Since we have it, and it's used, print it in a nice friendly yellow in
> parentheses after a skipped test's name.
>
> (And, by parsing it, it can be included in the JUnit results as well.)
Some tests would be nice.
> Signed-off-by: David Gow <david@davidgow.net>
> ---
>
> There are a few bits of KTAP that kunit.py has never actually parsed, and
> this is one of them. It's also nice to have a good way of quickly seeing
> why a test has been skipped, given most tests do provide good reason
> strings.
>
> Happy to hear comments about the style: yellow in parentheses looks pretty
> good here, but could be a bit confusing if KUnit do
Something is missing here?
For [SKIPPED] the brackets are colored, too.
> This is the first version of this patch, as it's a new dependency of v3 of
> patch 2, the JUnit support, which also includes the skip reason.
>
> --
> tools/testing/kunit/kunit_parser.py | 12 +++++++-----
> 1 file changed, 7 insertions(+), 5 deletions(-)
(...)
^ permalink raw reply
* Re: [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation
From: Nathan Chancellor @ 2026-06-04 21:48 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Yunseong Kim, 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, 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: <20260604084519.GA3126523@noisy.programming.kicks-ass.net>
On Thu, Jun 04, 2026 at 10:45:19AM +0200, Peter Zijlstra wrote:
> On Wed, Jun 03, 2026 at 07:43:29PM +0200, Yunseong Kim wrote:
> > Add CFLAGS_KCOV_DATAFLOW and RUSTFLAGS_KCOV_DATAFLOW exports to
> > scripts/Makefile.kcov, containing:
> > -fsanitize-coverage=dataflow-args,dataflow-ret -g
> > (with optional -fno-inline via CONFIG_KCOV_DATAFLOW_NO_INLINE)
> >
> > scripts/Makefile.lib applies these flags when a module's Makefile sets:
> > KCOV_DATAFLOW_file.o := y (per-file)
> > KCOV_DATAFLOW := y (per-directory)
> >
> > Also supports CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL for global enablement.
> > The flags are only applied to kernel objects (same guard as basic KCOV).
> >
> > Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
> > ---
> > scripts/Makefile.kcov | 6 ++++++
> > scripts/Makefile.lib | 7 +++++++
> > 2 files changed, 13 insertions(+)
> >
> > diff --git a/scripts/Makefile.kcov b/scripts/Makefile.kcov
> > index 78305a84ba9d..101173fe194b 100644
> > --- a/scripts/Makefile.kcov
> > +++ b/scripts/Makefile.kcov
> > @@ -2,10 +2,16 @@
> > kcov-flags-y += -fsanitize-coverage=trace-pc
> > kcov-flags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -fsanitize-coverage=trace-cmp
> >
> > +# KCOV dataflow: trace function args and return values
> > +kcov-dataflow-flags-y := -fsanitize-coverage=dataflow-args,dataflow-ret -g
> > +kcov-dataflow-flags-$(CONFIG_KCOV_DATAFLOW_NO_INLINE) += -fno-inline
>
> https://clang.llvm.org/docs/ClangCommandLineReference.html
>
> Has no mention of -fno-inline, furthermore, what are the exact
> semantics? Does it inhibit __always_inline?
>
Based on clang/test/CodeGen/always-inline.c [1], I believe the semantics
are the same as GCC's '-fno-inline' [2], which avoids inlining except
for always_inline functions.
[1]: https://github.com/llvm/llvm-project/blob/1d13b74cf086629d5cdae5f44ef4a62cebcaf3ff/clang/test/CodeGen/always-inline.c
[2]: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fno-inline
--
Cheers,
Nathan
^ permalink raw reply
* [PATCH v3 2/2] kunit: tool: Add (primitive) support for outputting JUnit XML
From: David Gow @ 2026-06-04 12:32 UTC (permalink / raw)
To: Brendan Higgins, Rae Moar, Shuah Khan, Thomas Weißschuh
Cc: David Gow, kunit-dev, linux-kernel, linux-kselftest, workflows
In-Reply-To: <20260604123207.2615485-1-david@davidgow.net>
This is used by things like Jenkins and other CI systems, which can
pretty-print the test output and potentially provide test-level comparisons
between runs.
The implementation here is pretty basic: it only provides the raw results,
split into tests and test suites, and doesn't provide any overall metadata.
However, CI systems like Jenkins can ingest it and it is already useful.
Signed-off-by: David Gow <david@davidgow.net>
---
Here's version 3 of the JUnit support patch. This one uses python's ElementTree
API to generate the XML, rather than writing strings directly. The only real
difference in the output is that this escapes any log lines, rather than using
CDATA sections. (There are both advantages and disadvantages to this, but
they're all pretty minor. Equally, the option of using the
xml.sax.saxutils.XMLGenerator API instead is a possibility. ElementTree ended
up being slightly more aligned with the way the JSON version worked.)
The other big change is that skipped tests now include their skip reason, rather
than hardcoding "Test skipped", hence the introduction of patch 1.
Changes since v2:
https://lore.kernel.org/all/20260502024918.1056954-1-david@davidgow.net/
- Rework to use Python's ElementTree XML writing API (Thanks Thomas)
- Output the reason for a test to be skipped.
Changes since v1:
https://lore.kernel.org/all/20260119073426.1952867-1-davidgow@google.com/
- Use python's provided XML quote escaping, rather than coding our own (Thanks Thomas)
- Output proper <skipped> tags for skipped tests
- Report crashed tests as <error>
- Don't output <system-out> tags if there are no lines of log data
Documentation/dev-tools/kunit/run_wrapper.rst | 3 +
tools/testing/kunit/kunit.py | 21 ++++++-
tools/testing/kunit/kunit_junit.py | 61 +++++++++++++++++++
tools/testing/kunit/kunit_tool_test.py | 39 +++++++++++-
4 files changed, 120 insertions(+), 4 deletions(-)
create mode 100644 tools/testing/kunit/kunit_junit.py
diff --git a/Documentation/dev-tools/kunit/run_wrapper.rst b/Documentation/dev-tools/kunit/run_wrapper.rst
index 770bb09a475a..cecc110a3399 100644
--- a/Documentation/dev-tools/kunit/run_wrapper.rst
+++ b/Documentation/dev-tools/kunit/run_wrapper.rst
@@ -324,6 +324,9 @@ command line arguments:
- ``--json``: If set, stores the test results in a JSON format and prints to `stdout` or
saves to a file if a filename is specified.
+- ``--junit``: If set, stores the test results in JUnit XML format and prints to `stdout` or
+ saves to a file if a filename is specified.
+
- ``--filter``: Specifies filters on test attributes, for example, ``speed!=slow``.
Multiple filters can be used by wrapping input in quotes and separating filters
by commas. Example: ``--filter "speed>slow, module=example"``.
diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py
index 742f5c555666..ac3f7159e67f 100755
--- a/tools/testing/kunit/kunit.py
+++ b/tools/testing/kunit/kunit.py
@@ -21,6 +21,7 @@ from enum import Enum, auto
from typing import Iterable, List, Optional, Sequence, Tuple
import kunit_json
+import kunit_junit
import kunit_kernel
import kunit_parser
from kunit_printer import stdout, null_printer
@@ -49,6 +50,7 @@ class KunitBuildRequest(KunitConfigRequest):
class KunitParseRequest:
raw_output: Optional[str]
json: Optional[str]
+ junit: Optional[str]
summary: bool
failed: bool
@@ -268,6 +270,13 @@ def parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input
stdout.print_with_timestamp("Test results stored in %s" %
os.path.abspath(request.json))
+ if request.junit:
+ if request.junit == 'stdout':
+ kunit_junit.print_junit_result(test=test)
+ else:
+ kunit_junit.write_junit_result(test=test,filename=request.junit)
+ stdout.print_with_timestamp(f"Test results stored in {os.path.abspath(request.junit)}")
+
if test.status != kunit_parser.TestStatus.SUCCESS:
return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
@@ -309,6 +318,7 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree,
# So we hackily automatically rewrite --json => --json=stdout
pseudo_bool_flag_defaults = {
'--json': 'stdout',
+ '--junit': 'stdout',
'--raw_output': 'kunit',
}
def massage_argv(argv: Sequence[str]) -> Sequence[str]:
@@ -459,6 +469,11 @@ def add_parse_opts(parser: argparse.ArgumentParser) -> None:
help='Prints parsed test results as JSON to stdout or a file if '
'a filename is specified. Does nothing if --raw_output is set.',
type=str, const='stdout', default=None, metavar='FILE')
+ parser.add_argument('--junit',
+ nargs='?',
+ help='Prints parsed test results as JUnit XML to stdout or a file if '
+ 'a filename is specified. Does nothing if --raw_output is set.',
+ type=str, const='stdout', default=None, metavar='FILE')
parser.add_argument('--summary',
help='Prints only the summary line for parsed test results.'
'Does nothing if --raw_output is set.',
@@ -502,6 +517,7 @@ def run_handler(cli_args: argparse.Namespace) -> None:
jobs=cli_args.jobs,
raw_output=cli_args.raw_output,
json=cli_args.json,
+ junit=cli_args.junit,
summary=cli_args.summary,
failed=cli_args.failed,
timeout=cli_args.timeout,
@@ -552,6 +568,7 @@ def exec_handler(cli_args: argparse.Namespace) -> None:
exec_request = KunitExecRequest(raw_output=cli_args.raw_output,
build_dir=cli_args.build_dir,
json=cli_args.json,
+ junit=cli_args.junit,
summary=cli_args.summary,
failed=cli_args.failed,
timeout=cli_args.timeout,
@@ -580,7 +597,9 @@ def parse_handler(cli_args: argparse.Namespace) -> None:
# We know nothing about how the result was created!
metadata = kunit_json.Metadata()
request = KunitParseRequest(raw_output=cli_args.raw_output,
- json=cli_args.json, summary=cli_args.summary,
+ json=cli_args.json,
+ junit=cli_args.junit,
+ summary=cli_args.summary,
failed=cli_args.failed)
result, _ = parse_tests(request, metadata, kunit_output)
if result.status != KunitStatus.SUCCESS:
diff --git a/tools/testing/kunit/kunit_junit.py b/tools/testing/kunit/kunit_junit.py
new file mode 100644
index 000000000000..3622070358e7
--- /dev/null
+++ b/tools/testing/kunit/kunit_junit.py
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Generates JUnit XML files from KUnit test results
+#
+# Copyright (C) 2026, Google LLC and David Gow.
+
+from xml.sax.saxutils import quoteattr, XMLGenerator
+import xml.etree.ElementTree as ET
+from kunit_parser import Test, TestStatus
+from typing import Optional
+
+# Get a string representing a tes suite (including subtests) in JUnit XML
+def get_test_suite(test: Test, parent: Optional[ET.Element]) -> ET.Element:
+ suite_attrs = {
+ 'name': test.name,
+ 'tests': str(test.counts.total()),
+ 'failures': str(test.counts.failed),
+ 'skipped': str(test.counts.skipped),
+ 'errors': str(test.counts.crashed + test.counts.errors),
+ }
+
+ if parent is not None:
+ test_suite_element = ET.SubElement(parent, 'testsuite', suite_attrs)
+ else:
+ test_suite_element = ET.Element('testsuite', suite_attrs)
+
+ for subtest in test.subtests:
+ if subtest.subtests:
+ get_test_suite(subtest, test_suite_element)
+ continue
+ test_case_element = ET.SubElement(test_suite_element, 'testcase', {'name': subtest.name})
+ if subtest.status == TestStatus.FAILURE:
+ ET.SubElement(test_case_element, 'failure', {}).text = 'Test Failed'
+ elif subtest.status == TestStatus.SKIPPED:
+ ET.SubElement(test_case_element, 'skipped', {}).text = subtest.skip_reason
+ elif subtest.status == TestStatus.TEST_CRASHED:
+ ET.SubElement(test_case_element, 'error', {}).text = 'Test Crashed'
+
+ if subtest.log:
+ ET.SubElement(test_case_element, 'system-out', {}).text = "\n".join(subtest.log)
+
+ return test_suite_element
+
+# Get a string for an entire XML file for the test structure starting at test
+def get_junit_result(test: Test) -> str:
+ root_element = get_test_suite(test, None)
+ ET.indent(root_element)
+ return ET.tostring(root_element, encoding="unicode", xml_declaration=True)
+
+# Print a JUnit result to stdout.
+def print_junit_result(test: Test) -> None:
+ root_element = get_test_suite(test, None)
+ ET.indent(root_element)
+ ET.dump(root_element)
+
+# Write an entire XML file for the test structure starting at test
+def write_junit_result(test: Test, filename: str) -> None:
+ root_element = get_test_suite(test, None)
+ ET.indent(root_element)
+ root_et = ET.ElementTree(root_element)
+ root_et.write(filename, encoding='utf-8', xml_declaration=True)
diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
index 267c33cecf87..9797c26d981f 100755
--- a/tools/testing/kunit/kunit_tool_test.py
+++ b/tools/testing/kunit/kunit_tool_test.py
@@ -24,6 +24,7 @@ import kunit_config
import kunit_parser
import kunit_kernel
import kunit_json
+import kunit_junit
import kunit
from kunit_printer import stdout
@@ -676,6 +677,38 @@ class StrContains(str):
def __eq__(self, other):
return self in other
+class KUnitJUnitTest(unittest.TestCase):
+ def setUp(self):
+ self.print_mock = mock.patch('kunit_printer.Printer.print').start()
+ self.addCleanup(mock.patch.stopall)
+
+ def _junit_string(self, log_file):
+ with open(_test_data_path(log_file)) as file:
+ test_result = kunit_parser.parse_run_tests(file, stdout)
+ junit_string = kunit_junit.get_junit_result(
+ test=test_result)
+ print(junit_string)
+ return junit_string
+
+ def test_failed_test_junit(self):
+ result = self._junit_string('test_is_test_passed-failure.log')
+ self.assertTrue("<failure>" in result)
+
+ def test_skipped_test_junit(self):
+ result = self._junit_string('test_skip_tests.log')
+ self.assertTrue("<skipped>" in result)
+ self.assertTrue("skipped=\"1\"" in result)
+
+ def test_crashed_test_junit(self):
+ result = self._junit_string('test_kernel_panic_interrupt.log')
+ self.assertTrue("<error>" in result);
+
+ def test_no_tests_junit(self):
+ result = self._junit_string('test_is_test_passed-no_tests_run_with_header.log')
+ self.assertTrue("tests=\"0\"" in result)
+ self.assertFalse("testcase" in result)
+
+
class KUnitMainTest(unittest.TestCase):
def setUp(self):
path = _test_data_path('test_is_test_passed-all_passed.log')
@@ -923,7 +956,7 @@ class KUnitMainTest(unittest.TestCase):
self.linux_source_mock.run_kernel.return_value = ['TAP version 14', 'init: random output'] + want
got = kunit._list_tests(self.linux_source_mock,
- kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False))
+ kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False))
self.assertEqual(got, want)
# Should respect the user's filter glob when listing tests.
self.linux_source_mock.run_kernel.assert_called_once_with(
@@ -936,7 +969,7 @@ class KUnitMainTest(unittest.TestCase):
# Should respect the user's filter glob when listing tests.
mock_tests.assert_called_once_with(mock.ANY,
- kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False, False))
+ kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False, False))
self.linux_source_mock.run_kernel.assert_has_calls([
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', filter='', filter_action=None, timeout=300),
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', filter='', filter_action=None, timeout=300),
@@ -949,7 +982,7 @@ class KUnitMainTest(unittest.TestCase):
# Should respect the user's filter glob when listing tests.
mock_tests.assert_called_once_with(mock.ANY,
- kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'test', False, False, False))
+ kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'test', False, False, False))
self.linux_source_mock.run_kernel.assert_has_calls([
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', filter='', filter_action=None, timeout=300),
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', filter='', filter_action=None, timeout=300),
--
2.54.0
^ permalink raw reply related
* [PATCH v3 1/2] kunit: tool: Parse and print the reason tests are skipped
From: David Gow @ 2026-06-04 12:32 UTC (permalink / raw)
To: Brendan Higgins, Rae Moar, Shuah Khan, Thomas Weißschuh
Cc: David Gow, kunit-dev, linux-kernel, linux-kselftest, workflows
When a KUnit test (or other KTAP test) is skipped, a "skip reason" can be
provided. kunit.py has never done anything with this, ignoring anything
included in the KTAP output after the 'SKIP' directive.
Since we have it, and it's used, print it in a nice friendly yellow in
parentheses after a skipped test's name.
(And, by parsing it, it can be included in the JUnit results as well.)
Signed-off-by: David Gow <david@davidgow.net>
---
There are a few bits of KTAP that kunit.py has never actually parsed, and
this is one of them. It's also nice to have a good way of quickly seeing
why a test has been skipped, given most tests do provide good reason
strings.
Happy to hear comments about the style: yellow in parentheses looks pretty
good here, but could be a bit confusing if KUnit do
This is the first version of this patch, as it's a new dependency of v3 of
patch 2, the JUnit support, which also includes the skip reason.
--
tools/testing/kunit/kunit_parser.py | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py
index 0e1d2f4985eb..22b8464c6383 100644
--- a/tools/testing/kunit/kunit_parser.py
+++ b/tools/testing/kunit/kunit_parser.py
@@ -44,11 +44,12 @@ class Test:
self.subtests = [] # type: List[Test]
self.log = [] # type: List[str]
self.counts = TestCounts()
+ self.skip_reason = ''
def __str__(self) -> str:
"""Returns string representation of a Test class object."""
return (f'Test({self.status}, {self.name}, {self.expected_count}, '
- f'{self.subtests}, {self.log}, {self.counts})')
+ f'{self.subtests}, {self.log}, {self.counts}, {self.skip_reason})')
def __repr__(self) -> str:
"""Returns string representation of a Test class object."""
@@ -352,9 +353,9 @@ def parse_test_plan(lines: LineStream, test: Test) -> bool:
lines.pop()
return True
-TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(- )?([^#]*)( # .*)?$')
+TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(:?- )?([^#]*)( # .*)?$')
-TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(- )?(.*) # SKIP ?(.*)$')
+TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(:?- )?(.*) # SKIP ?(.*)$')
def peek_test_name_match(lines: LineStream, test: Test) -> bool:
"""
@@ -418,7 +419,7 @@ def parse_test_result(lines: LineStream, test: Test,
# Set name of test object
if skip_match:
- test.name = skip_match.group(4) or skip_match.group(5)
+ test.name = skip_match.group(4)
else:
test.name = match.group(4)
@@ -431,6 +432,7 @@ def parse_test_result(lines: LineStream, test: Test,
status = match.group(1)
if skip_match:
test.status = TestStatus.SKIPPED
+ test.skip_reason = skip_match.group(5) or ''
elif status == 'ok':
test.status = TestStatus.SUCCESS
else:
@@ -539,7 +541,7 @@ def format_test_result(test: Test, printer: Printer) -> str:
if test.status == TestStatus.SUCCESS:
return printer.green('[PASSED] ') + test.name
if test.status == TestStatus.SKIPPED:
- return printer.yellow('[SKIPPED] ') + test.name
+ return printer.yellow('[SKIPPED] ') + test.name + ' (' + printer.yellow(test.skip_reason) + ')'
if test.status == TestStatus.NO_TESTS:
return printer.yellow('[NO TESTS RUN] ') + test.name
if test.status == TestStatus.TEST_CRASHED:
--
2.54.0
^ permalink raw reply related
* Re: [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
From: Yunseong Kim @ 2026-06-04 9:29 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, 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
Cc: linux-kernel, kasan-dev, llvm, linux-kbuild, rust-for-linux,
workflows, linux-doc, Yunseong Kim
In-Reply-To: <20260603-kcov-dataflow-next-20260603-v2-0-fee0939de2c4@est.tech>
Hi,
I would like to make a correction to my cover letter.
Note: v1 was sent as "RFC v2" due to my b4 misconfiguration.
On 6/3/26 19:43, Yunseong Kim 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.
>
> [snip...]
kcov-dataflow are not in conflict with "KASAN_GENERIC + KCOV_DATAFLOW_INSTRUMENT_ALL"
> Prerequisites / Toolchain
> =========================
>
> This kernel patch relies on a custom LLVM SanitizerCoverage pass that
> emits __sanitizer_cov_trace_args() and __sanitizer_cov_trace_ret()
> callbacks at function boundaries, extracting struct field layouts from
> DWARF debug metadata at compile time.
>
> To build and test this patchset, compile the kernel using the modified
> toolchain:
>
> 1. LLVM/Clang (adds -fsanitize-coverage=dataflow-args,dataflow-ret):
> https://github.com/llvm/llvm-project/pull/201410
>
> 2. Rust (rustc 1.98 built against the above LLVM 23, for Rust module support):
> https://github.com/yskzalloc/rust
>
> Build instructions:
>
> # Build the modified clang
> cd llvm-project && cmake -G Ninja -S llvm -B build \
> -DLLVM_ENABLE_PROJECTS="clang;lld" -DCMAKE_BUILD_TYPE=Release
> ninja -C build clang
>
> # Build the kernel with dataflow support
> export PATH=$HOME/llvm-project/build/bin:$PATH
> export RUSTC=$HOME/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc
> export RUST_LIB_SRC=$HOME/rust/library
>
> make LLVM=1 defconfig
> scripts/config --enable KCOV \
> --enable KCOV_DATAFLOW_ARGS \
> --enable KCOV_DATAFLOW_RET
> make LLVM=1 olddefconfig
> make LLVM=1 -j$(nproc)
>
> Note: CONFIG_KCOV_DATAFLOW_ARGS and CONFIG_KCOV_DATAFLOW_RET depend on
> CONFIG_KCOV and use $(cc-option) to verify the compiler supports the
> new flags. With standard (unpatched) clang, these options will not
> appear in menuconfig and silently remain disabled.
>
> Optional configs:
> --enable KCOV_DATAFLOW_INSTRUMENT_ALL (instrument entire kernel)
> --enable KCOV_DATAFLOW_NO_INLINE (enabled by default)
I checked, and it’s better to use CONFIG_FRAME_WARN=0 when using KASAN
together regarding flood of -Wframe-larger-than warnings.
> --set-val FRAME_WARN 4096 (needed for INSTRUMENT_ALL)
> --disable KASAN (conflicts with INSTRUMENT_ALL) --enable KASAN (no conflicts; works well, but slower, a lot of dataflow)
What I meant by that conflicts is that I was using it for kernel-space
behavior only for the "offensive kernel vulnerability auditing",
and in that case, sometime it’s better to disable KASAN.
Tested CONFIG_KASAN=y + CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y together
(FRAME_WARN=4096, virtme-ng 1 vCPU 1GB on my Intel CPU Dell Pro 14
Premium PA14250):
- Builds clean, no frame size warnings
- Boots and runs without KASAN errors
- kcov-dataflow captures correctly under KASAN:
getpid(): 6,682 records
open+close: 16,382 records (filled 64K buffer)
Time to "Freeing unused kernel image (initmem)":
KCOV_DATAFLOW_INSTRUMENT_ALL only: ~0.95s (+40%)
KASAN + KCOV_DATAFLOW_INSTRUMENT_ALL: ~2.02s (+197%)
Sorry for any confusion the earlier wording may have caused.
Thank you!
Kind regards,
Yunseong
^ permalink raw reply
* Re: [RFC PATCH v2 6/6] kcov: add recursion guard and documentation for kcov-dataflow
From: Peter Zijlstra @ 2026-06-04 8:52 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: <20260603-kcov-dataflow-next-20260603-v2-6-fee0939de2c4@est.tech>
On Wed, Jun 03, 2026 at 07:43:33PM +0200, Yunseong Kim wrote:
> Add a per-task recursion guard to kcov_df_write() using the high bit of
> kcov_dataflow_seq. This prevents infinite recursion when
> CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL is enabled: functions called by the
> callback itself (copy_from_kernel_nofault, xadd helpers) are also
> instrumented and would re-enter kcov_df_write() without this guard.
>
> The guard uses the sequence counter's bit 31 as a re-entrancy flag.
> The low 24 bits (used for TLV record sequence numbers) are unaffected.
>
> Also:
> - Exclude kcov.o, extable.o, softirq.o from dataflow instrumentation
> (same pattern as KCOV_INSTRUMENT exclusions)
> - Add Documentation/dev-tools/kcov-dataflow.rst with:
> - Prerequisites and Kconfig options
> - Per-module instrumentation instructions
> - Complete C example for data collection
> - Ring buffer format specification
> - Ioctl interface reference
> - Fork interception example for child process tracing
> - Rust module support via post-compilation pipeline
>
> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
Another really weird patch. Adding Documentation is okay I suppose,
although I still utterly detest this rst crap. I still fail to see
what's wrong with plain text :-(
But then you do these two other random things in the same patch. Which
make no sense.
> diff --git a/kernel/Makefile b/kernel/Makefile
> index 1e1a31673577..9c56421c5390 100644
> --- a/kernel/Makefile
> +++ b/kernel/Makefile
> @@ -37,6 +37,7 @@ KCOV_INSTRUMENT_extable.o := n
> KCOV_INSTRUMENT_stacktrace.o := n
> # Don't self-instrument.
> KCOV_INSTRUMENT_kcov.o := n
> +KCOV_DATAFLOW_kcov.o := n
> # If sanitizers detect any issues in kcov, it may lead to recursion
> # via printk, etc.
> KASAN_SANITIZE_kcov.o := n
> @@ -207,3 +208,5 @@ $(obj)/kheaders.md5: $(obj)/kheaders-srclist FORCE
> $(call filechk,kheaders_md5sum)
>
> clean-files := kheaders.md5 kheaders-srclist kheaders-objlist
> +KCOV_DATAFLOW_extable.o := n
> +KCOV_DATAFLOW_softirq.o := n
Why?!?! You Changelog also does not elucidate.
> diff --git a/kernel/kcov.c b/kernel/kcov.c
> index 373b8034ca5c..8d9d5e33549f 100644
> --- a/kernel/kcov.c
> +++ b/kernel/kcov.c
> @@ -413,6 +413,16 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
> if (!in_task())
> return;
>
> + /*
> + * Prevent recursion: functions called by this callback
> + * (copy_from_kernel_nofault, xadd helpers) may be instrumented
> + * with INSTRUMENT_ALL. Use a per-task guard via the sequence
> + * counter's high bit.
> + */
> + if (t->kcov_dataflow_seq & (1U << 31))
> + return;
> + t->kcov_dataflow_seq |= (1U << 31);
> +
> area = (u64 *)t->kcov_df_area;
> if (!area)
> return;
> @@ -449,7 +459,7 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
> if (KCOV_DF_IS_ERR(ptr)) {
> for (i = 0; i < num_fields; i++)
> area[pos + 3 + i] = KCOV_DF_MAGIC_BAD;
> - return;
> + goto out;
> }
> for (i = 0; i < num_fields; i++) {
> u64 off, sz, val = KCOV_DF_MAGIC_BAD;
> @@ -469,6 +479,8 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
> area[pos + 3 + i] = val;
> }
> }
> +out:
> + t->kcov_dataflow_seq &= ~(1U << 31);
> }
>
> #ifdef CONFIG_KCOV_DATAFLOW_ARGS
This needs barrier()s to be functional. And should be in a separate
patch.
^ permalink raw reply
* Re: [RFC PATCH v2 5/6] kcov: add interrupt context guard to kcov_df_write()
From: Peter Zijlstra @ 2026-06-04 8:48 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: <20260603-kcov-dataflow-next-20260603-v2-5-fee0939de2c4@est.tech>
On Wed, Jun 03, 2026 at 07:43:32PM +0200, Yunseong Kim wrote:
> The KCOV-Dataflow write path (kcov_df_write) only checks
> t->kcov_df_enabled before writing to the shared ring buffer. Unlike
> the standard KCOV check_kcov_mode() which rejects interrupt context,
> kcov_df_write() has no such protection. This means instrumented code
> running in hardirq, softirq, or NMI context that interrupts a task
> mid-write can re-enter kcov_df_write(), causing:
>
> - Data corruption in the ring buffer (interleaved records)
> - Out-of-order sequence counter increments
> - Potential faults from nested pointer dereferences
>
> Add an in_task() check to reject calls from non-task context, matching
> the safety model of the standard KCOV tracing path.
>
> Also suppress -Wmissing-prototypes in the eight_args_c test module
> Makefile, as the exported test functions intentionally lack a shared
> header.
>
> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
> ---
> kernel/kcov.c | 4 ++++
> tools/kcov-dataflow/eight_args_c/Makefile | 1 +
> 2 files changed, 5 insertions(+)
>
> diff --git a/kernel/kcov.c b/kernel/kcov.c
> index d3c9c0efe961..373b8034ca5c 100644
> --- a/kernel/kcov.c
> +++ b/kernel/kcov.c
> @@ -409,6 +409,10 @@ kcov_df_write(u64 type_marker, u64 pc, u64 meta, void *ptr,
> if (!t->kcov_df_enabled)
> return;
>
> + /* Reject calls from hardirq/softirq/NMI to prevent reentrant corruption. */
> + if (!in_task())
> + return;
> +
> area = (u64 *)t->kcov_df_area;
> if (!area)
> return;
> diff --git a/tools/kcov-dataflow/eight_args_c/Makefile b/tools/kcov-dataflow/eight_args_c/Makefile
> index de35bb541f07..038775b49435 100644
> --- a/tools/kcov-dataflow/eight_args_c/Makefile
> +++ b/tools/kcov-dataflow/eight_args_c/Makefile
> @@ -1,2 +1,3 @@
> obj-m := eight_args_mod.o
> KCOV_DATAFLOW_eight_args_mod.o := y
> +ccflags-y += -Wno-missing-prototypes
This is a weird commit and probably should not exist. You introduce
kcov_df_write() a few patches ago, why doesn't it add these few lines
there?
Similarly, you introduce this tools thing a few patches ago, fix the
Makefile there?
^ permalink raw reply
* Re: [RFC PATCH v2 3/6] kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE
From: Peter Zijlstra @ 2026-06-04 8:46 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: <20260603-kcov-dataflow-next-20260603-v2-3-fee0939de2c4@est.tech>
On Wed, Jun 03, 2026 at 07:43:30PM +0200, Yunseong Kim wrote:
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index abd1a94589aa..3b952b6361a8 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -2261,7 +2261,6 @@ config KCOV_SELFTEST
> On test failure, causes the kernel to panic. Recommended to be
> enabled, ensuring critical functionality works as intended.
>
> -
> config KCOV_DATAFLOW_ARGS
> bool "Enable KCOV dataflow: function argument capture"
> depends on KCOV
> @@ -2283,6 +2282,28 @@ config KCOV_DATAFLOW_RET
> 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.
> +
This goes into patch 1
^ permalink raw reply
* Re: [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation
From: Peter Zijlstra @ 2026-06-04 8:45 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: <20260603-kcov-dataflow-next-20260603-v2-2-fee0939de2c4@est.tech>
On Wed, Jun 03, 2026 at 07:43:29PM +0200, Yunseong Kim wrote:
> Add CFLAGS_KCOV_DATAFLOW and RUSTFLAGS_KCOV_DATAFLOW exports to
> scripts/Makefile.kcov, containing:
> -fsanitize-coverage=dataflow-args,dataflow-ret -g
> (with optional -fno-inline via CONFIG_KCOV_DATAFLOW_NO_INLINE)
>
> scripts/Makefile.lib applies these flags when a module's Makefile sets:
> KCOV_DATAFLOW_file.o := y (per-file)
> KCOV_DATAFLOW := y (per-directory)
>
> Also supports CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL for global enablement.
> The flags are only applied to kernel objects (same guard as basic KCOV).
>
> Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
> ---
> scripts/Makefile.kcov | 6 ++++++
> scripts/Makefile.lib | 7 +++++++
> 2 files changed, 13 insertions(+)
>
> diff --git a/scripts/Makefile.kcov b/scripts/Makefile.kcov
> index 78305a84ba9d..101173fe194b 100644
> --- a/scripts/Makefile.kcov
> +++ b/scripts/Makefile.kcov
> @@ -2,10 +2,16 @@
> kcov-flags-y += -fsanitize-coverage=trace-pc
> kcov-flags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -fsanitize-coverage=trace-cmp
>
> +# KCOV dataflow: trace function args and return values
> +kcov-dataflow-flags-y := -fsanitize-coverage=dataflow-args,dataflow-ret -g
> +kcov-dataflow-flags-$(CONFIG_KCOV_DATAFLOW_NO_INLINE) += -fno-inline
https://clang.llvm.org/docs/ClangCommandLineReference.html
Has no mention of -fno-inline, furthermore, what are the exact
semantics? Does it inhibit __always_inline?
^ permalink raw reply
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