* [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
@ 2026-06-03 17:43 Yunseong Kim
2026-06-03 17:43 ` [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values Yunseong Kim
` (7 more replies)
0 siblings, 8 replies; 15+ messages in thread
From: Yunseong Kim @ 2026-06-03 17:43 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: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
Introduces a new KCOV exetened feature that captures function arguments and
return values at kernel function boundaries, enabling per-process visibility
into runtime dataflow.
Motivation
==========
Even for highly experienced developers, it is not straightforward to
determine, at any given moment, which specific kernel paths a user
process is executing or how function arguments and return values evolve
during execution. This lack of visibility makes debugging and security
auditing significantly more challenging.
Limitations of existing tools in per-task dataflow extraction:
- ftrace/kprobes provide dynamic tracing at specific probe points
- eBPF enables programmable in-kernel analysis but requires manual
specification of struct layouts rather than automatic extraction
from compiler debug metadata
- perf provides statistical sampling of hardware/software events,
inherently lossy and designed for performance profiling rather
than deterministic data-flow capture
This is NOT a performance tool. The purpose is auditing and contract
verification — confirming that kernel functions receive and return
expected values at runtime.
Real-World Result: Android Binder Vulnerabilities
=================================================
Using kcov-dataflow with CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL, I
audited the Android binder driver (both C and Rust implementations)
and discovered two exploitable logic bugs:
Bug 1: BINDER_SET_MAX_THREADS accepts 0xFFFFFFFF without validation.
kcov-dataflow showed: set_max_threads(max=0xffffffff) → return 0
Impact: bypasses RLIMIT_NPROC, OOM from unprivileged userspace.
Bug 2: BC_ENTER_LOOPER accepted twice without error.
kcov-dataflow showed: looper_enter() called with ENTERED already set,
no rejection, return=0 on both calls.
Impact: thread pool state corruption.
These bugs are invisible to:
- KASAN: no memory corruption occurs
- Edge coverage: same code paths for valid/invalid values
- ftrace: shows "function called" but not argument values
Only by capturing the actual runtime values at function boundaries
could I detect that 0xFFFFFFFF passes through without rejection, or
that the same state-mutating command succeeds twice.
The fixes are submitted separately:
[PATCH 1/4] binder: cap BINDER_SET_MAX_THREADS at RLIMIT_NPROC
[PATCH 2/4] binder: reject duplicate BC_ENTER_LOOPER commands
[PATCH 3/4] rust_binder: cap set_max_threads at RLIMIT_NPROC
[PATCH 4/4] rust_binder: reject duplicate BC_ENTER_LOOPER in looper_enter
Approach
========
Rather than tracing individual probe points, this patch set enables
continuous per-task extraction of data flow across all instrumented
function boundaries — capturing how argument values enter and return
values exit each function as execution progresses through a subsystem.
The key insight is that function boundaries are natural observation
points: arguments at entry reveal what data enters a subsystem, and
return values reveal what comes out.
The compiler (clang with a SanitizerCoverage extension) inserts
callbacks at function entry/exit that record argument values into a
per-task mmap'd buffer. The kernel backend reads struct fields safely
via copy_from_kernel_nofault(). When not enabled for a task, the
overhead is a single boolean check per instrumented function.
Design
======
- Completely independent from legacy /sys/kernel/debug/kcov
- Separate device: /sys/kernel/debug/kcov_dataflow
- Separate ioctl namespace ('d'), separate per-task buffer
- Per-module opt-in: KCOV_DATAFLOW_file.o := y
- Optional global enablement: CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL
- Supports both C and Rust kernel modules
- Safe in process context; rejects interrupt/NMI via in_task() guard
- Recursion guard via sequence counter bit 31
- Requires clang with -fsanitize-coverage=dataflow-args,dataflow-ret
(Kconfig uses cc-option to verify compiler support)
Performance Note
================
This feature is designed for auditing and security analysis, NOT for
production use or performance measurement. It should not be compared
to runtime tracing tools optimized for low overhead.
Per-module instrumentation (recording active):
~27ns per callback (dominated by LOCK XADD + copy_from_kernel_nofault)
Global instrumentation (INSTRUMENT_ALL, recording disabled):
.text: +9.5%, .data: +44%, boot: +71%, syscall latency: +133%
CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL instruments every function in the
kernel. This incurs significant overhead comparable to KMSAN and is
intended exclusively for:
- Fuzzer-driven whole-kernel auditing (syzkaller integration)
- Full-subsystem contract verification (as demonstrated with binder)
- Capturing complete call-flow data for post-mortem analysis
For targeted auditing, use per-module opt-in (KCOV_DATAFLOW_file.o := y)
which limits overhead to the specific subsystem under investigation.
Patches
=======
1/6: Core kernel implementation (kernel/kcov.c, sched.h, Kconfig)
2/6: Build system support (Makefile.kcov, Makefile.lib)
3/6: CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE
4/6: Userspace tools and test modules
5/6: Harden kcov_df_write() against interrupt reentry
6/6: Recursion guard and documentation
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)
--set-val FRAME_WARN 4096 (needed for INSTRUMENT_ALL)
--disable KASAN (conflicts with INSTRUMENT_ALL)
Testing
=======
Tested on linux-next 7.1.0-rc5 with custom clang/LLVM 23 and
rustc 1.98-nightly (built against the same LLVM). Verified under
virtme-ng (QEMU/KVM, 1GB RAM):
- Per-module C (eight_args_mod): all 8 functions captured
- Per-module C (deep_chain_mod): 10-deep call chain captured
- Per-module Rust (eight_args_rust): all 8 rfunc functions captured
- Interrupt safety: in_task() + recursion guard prevents corruption
- Binder auditing: discovered 2 exploitable bugs (patches separate)
- Standard clang (without patch): Kconfig options correctly hidden
- Global enablement (INSTRUMENT_ALL): kernel boots, 104K records
captured during single copy.fail exploit reproduction
https://github.com/yskzalloc/kcov-dataflow/blob/main/copy.fail/origin/converted.txt
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
Changes in v2:
- EDITME: describe what is new in this series revision.
- EDITME: use bulletpoints and terse descriptions.
- Link to v1: https://patch.msgid.link/20260603-kcov-dataflow-next-20260603-v1-0-e64300bf17cf@est.tech
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: Dmitry Vyukov <dvyukov@google.com>
To: Andrey Konovalov <andreyknvl@gmail.com>
To: Andrew Morton <akpm@linux-foundation.org>
To: Nathan Chancellor <nathan@kernel.org>
To: Nick Desaulniers <nick.desaulniers+lkml@gmail.com>
To: Bill Wendling <morbo@google.com>
To: Justin Stitt <justinstitt@google.com>
To: Nicolas Schier <nsc@kernel.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: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-kernel@vger.kernel.org
Cc: kasan-dev@googlegroups.com
Cc: llvm@lists.linux.dev
Cc: linux-kbuild@vger.kernel.org
Cc: rust-for-linux@vger.kernel.org
Cc: workflows@vger.kernel.org
Cc: linux-doc@vger.kernel.org
---
Yunseong Kim (6):
kcov: add per-task dataflow tracking for function arguments/return values
kcov: add build system support for dataflow instrumentation
kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE
tools/kcov-dataflow: add userspace consumer and test modules
kcov: add interrupt context guard to kcov_df_write()
kcov: add recursion guard and documentation for kcov-dataflow
Documentation/dev-tools/kcov-dataflow.rst | 282 +++++++++++++++++++
include/linux/sched.h | 8 +
kernel/Makefile | 3 +
kernel/kcov.c | 307 +++++++++++++++++++++
lib/Kconfig.debug | 43 +++
rust/Makefile | 1 +
scripts/Makefile.kcov | 6 +
scripts/Makefile.lib | 7 +
tools/kcov-dataflow/.gitignore | 12 +
tools/kcov-dataflow/deep_module/Makefile | 2 +
tools/kcov-dataflow/deep_module/deep_chain_mod.c | 224 +++++++++++++++
tools/kcov-dataflow/eight_args_c/Makefile | 3 +
tools/kcov-dataflow/eight_args_c/eight_args_mod.c | 95 +++++++
tools/kcov-dataflow/eight_args_rust/Makefile | 2 +
.../eight_args_rust/eight_args_rust.rs | 114 ++++++++
tools/kcov-dataflow/kcov-view.py | 272 ++++++++++++++++++
tools/kcov-dataflow/trigger.c | 125 +++++++++
17 files changed, 1506 insertions(+)
---
base-commit: f7af91adc230aa99e23330ecf85bc9badd9780ad
change-id: 20260603-kcov-dataflow-next-20260603-8bf628f98086
Best regards,
--
Yunseong Kim <yunseong.kim@est.tech>
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
@ 2026-06-03 17:43 ` Yunseong Kim
2026-06-04 8:41 ` Peter Zijlstra
2026-06-03 17:43 ` [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation Yunseong Kim
` (6 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Yunseong Kim @ 2026-06-03 17:43 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: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
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.
config DEBUG_AID_FOR_SYZBOT
bool "Additional debug code for syzbot"
default n
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
2026-06-03 17:43 ` [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values Yunseong Kim
@ 2026-06-03 17:43 ` Yunseong Kim
2026-06-04 8:45 ` Peter Zijlstra
2026-06-03 17:43 ` [RFC PATCH v2 3/6] kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE Yunseong Kim
` (5 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Yunseong Kim @ 2026-06-03 17:43 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: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
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
+
kcov-rflags-y += -Cpasses=sancov-module
kcov-rflags-y += -Cllvm-args=-sanitizer-coverage-level=3
kcov-rflags-y += -Cllvm-args=-sanitizer-coverage-trace-pc
kcov-rflags-$(CONFIG_KCOV_ENABLE_COMPARISONS) += -Cllvm-args=-sanitizer-coverage-trace-compares
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
export RUSTFLAGS_KCOV := $(kcov-rflags-y)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 80e127c75a93..519bf651cdcf 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -88,6 +88,13 @@ _c_flags += $(if $(patsubst n%,, \
_rust_flags += $(if $(patsubst n%,, \
$(KCOV_INSTRUMENT_$(target-stem).o)$(KCOV_INSTRUMENT)$(if $(is-kernel-object),$(CONFIG_KCOV_INSTRUMENT_ALL))), \
$(RUSTFLAGS_KCOV))
+# KCOV dataflow: per-file opt-in or global via CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL
+_c_flags += $(if $(patsubst n%,, \
+ $(KCOV_DATAFLOW_$(target-stem).o)$(KCOV_DATAFLOW)$(if $(is-kernel-object),$(CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL))), \
+ $(CFLAGS_KCOV_DATAFLOW))
+_rust_flags += $(if $(patsubst n%,, \
+ $(KCOV_DATAFLOW_$(target-stem).o)$(KCOV_DATAFLOW)$(if $(is-kernel-object),$(CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL))), \
+ $(RUSTFLAGS_KCOV_DATAFLOW))
endif
#
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 3/6] kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
2026-06-03 17:43 ` [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values Yunseong Kim
2026-06-03 17:43 ` [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation Yunseong Kim
@ 2026-06-03 17:43 ` Yunseong Kim
2026-06-04 8:46 ` Peter Zijlstra
2026-06-03 17:43 ` [RFC PATCH v2 4/6] tools/kcov-dataflow: add userspace consumer and test modules Yunseong Kim
` (4 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Yunseong Kim @ 2026-06-03 17:43 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: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
Add two Kconfig options for global dataflow instrumentation control:
- CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL: instruments all kernel objects
with dataflow tracing by default (mirrors CONFIG_KCOV_INSTRUMENT_ALL).
Individual files can opt out with: KCOV_DATAFLOW_file.o := n
- CONFIG_KCOV_DATAFLOW_NO_INLINE: adds -fno-inline to instrumented files
for complete argument visibility (default y). Setting to n allows
global enablement without stack overflow or BUILD_BUG_ON failures.
Overhead with INSTRUMENT_ALL (NO_INLINE=n, KASAN baseline):
.text: +9.5%, .data: +44%, boot: +71%, syscall: +133%
Comparable to KASAN (+100-200%) and acceptable for fuzzing kernels.
rust/Makefile: opt out core.o from dataflow (same as KCOV_INSTRUMENT).
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
lib/Kconfig.debug | 23 ++++++++++++++++++++++-
rust/Makefile | 1 +
2 files changed, 23 insertions(+), 1 deletion(-)
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.
+
+config KCOV_DATAFLOW_INSTRUMENT_ALL
+ bool "Instrument all code with KCOV dataflow by default"
+ depends on KCOV_DATAFLOW_ARGS || KCOV_DATAFLOW_RET
+ help
+ If enabled, all kernel objects are compiled with dataflow
+ instrumentation (like CONFIG_KCOV_INSTRUMENT_ALL for basic KCOV).
+ Individual files can opt out with: KCOV_DATAFLOW_file.o := n
+ Increases compile time and binary size significantly.
+ Suitable for fuzzing and security auditing kernels.
+
+config KCOV_DATAFLOW_NO_INLINE
+ bool "Disable inlining for dataflow-instrumented files"
+ depends on KCOV_DATAFLOW_ARGS || KCOV_DATAFLOW_RET
+ default y
+ help
+ Adds -fno-inline to dataflow-instrumented files for complete
+ argument visibility. Without this, inlined functions will not
+ have their arguments captured individually.
+ Disabling allows global enablement with lower overhead at the
+ cost of missing inlined function traces.
+
config DEBUG_AID_FOR_SYZBOT
bool "Additional debug code for syzbot"
default n
diff --git a/rust/Makefile b/rust/Makefile
index b9e9f512cec3..d122a65226dc 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -656,6 +656,7 @@ ifneq ($(or $(CONFIG_X86_64),$(CONFIG_X86_32)),)
$(obj)/core.o: scripts/target.json
endif
KCOV_INSTRUMENT_core.o := n
+KCOV_DATAFLOW_core.o := n
$(obj)/compiler_builtins.o: private skip_gendwarfksyms = 1
$(obj)/compiler_builtins.o: private rustc_objcopy = -w -W '__*'
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 4/6] tools/kcov-dataflow: add userspace consumer and test modules
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
` (2 preceding siblings ...)
2026-06-03 17:43 ` [RFC PATCH v2 3/6] kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE Yunseong Kim
@ 2026-06-03 17:43 ` Yunseong Kim
2026-06-03 17:43 ` [RFC PATCH v2 5/6] kcov: add interrupt context guard to kcov_df_write() Yunseong Kim
` (3 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Yunseong Kim @ 2026-06-03 17:43 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: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
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.
- eight_args_rust/eight_args_rust.rs: Rust equivalent of the 8-argument
stress test, verifying Rust module dataflow support.
- deep_module/deep_chain_mod.c: 10-level deep call chain demonstrating
taint propagation tracking across function boundaries.
Sample kcov-view.py output (C):
func2+0x0 [eight_args_mod](arg[0]=0x11, arg[1]=0x22)
ret = 0x33
func8+0x0 [eight_args_mod](arg[0]=0x11, .., arg[7]=0x88)
ret = 0x264
Sample kcov-view.py output (Rust):
rfunc2+0x0 [eight_args_rust](arg[0]=0x11, arg[1]=0x22)
ret = 0x33
rfunc8+0x0 [eight_args_rust](arg[0]=0x11, .., arg[7]=0x88)
ret = 0x264
Signed-off-by: Yunseong Kim <yunseong.kim@est.tech>
---
tools/kcov-dataflow/.gitignore | 12 +
tools/kcov-dataflow/deep_module/Makefile | 2 +
tools/kcov-dataflow/deep_module/deep_chain_mod.c | 224 +++++++++++++++++
tools/kcov-dataflow/eight_args_c/Makefile | 2 +
tools/kcov-dataflow/eight_args_c/eight_args_mod.c | 95 +++++++
tools/kcov-dataflow/eight_args_rust/Makefile | 2 +
.../eight_args_rust/eight_args_rust.rs | 114 +++++++++
tools/kcov-dataflow/kcov-view.py | 272 +++++++++++++++++++++
tools/kcov-dataflow/trigger.c | 125 ++++++++++
9 files changed, 848 insertions(+)
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
@@ -0,0 +1,12 @@
+# Built binaries
+test_mock
+test_mock_binary
+trigger
+*.o
+*.ko
+*.mod
+*.mod.c
+Module.symvers
+modules.order
+.module-common.o
+*.ll
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
@@ -0,0 +1,2 @@
+obj-m := deep_chain_mod.o
+KCOV_DATAFLOW_deep_chain_mod.o := y
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.
+ *
+ * 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)
+ */
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+
+/* Simulated protocol structures */
+struct request_header {
+ u32 magic;
+ u32 version;
+ u32 payload_offset; /* ← attacker controls this */
+ u32 payload_size;
+};
+
+struct payload {
+ u64 session_id;
+ u32 transform_key;
+ u32 filter_mask;
+ u8 data[32];
+};
+
+struct slot_table {
+ u32 num_slots;
+ u64 slots[8]; /* only 8 slots! */
+};
+
+static struct proc_dir_entry *proc_deep;
+
+/* === 10 nested functions: deepest first === */
+
+/* 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;
+}
+
+/* Function 9 */
+static noinline int write_slot(struct slot_table *table, u32 slot_idx,
+ u64 session_id)
+{
+ u64 combined = session_id ^ (u64)slot_idx;
+
+ return commit_write(table, slot_idx, combined);
+}
+
+/* Function 8 */
+static noinline u32 lookup_slot(struct slot_table *table, u32 computed_idx)
+{
+ /* Pass through — in real code this might do hash lookup */
+ u32 final_idx = computed_idx % 16; /* BUG: should be % 8 */
+
+ write_slot(table, final_idx, 0xDEADC0DE00000000ULL | final_idx);
+ return final_idx;
+}
+
+/* Function 7 */
+static noinline u32 compute_index(u32 transform_result, u32 filter_output)
+{
+ /* Combines two values into an index */
+ return (transform_result + filter_output) & 0xF; /* 0-15, but table has 8 */
+}
+
+/* Function 6 */
+static noinline u32 apply_filter(struct payload *pl, u32 transformed_val)
+{
+ u32 filtered = transformed_val & pl->filter_mask;
+
+ return filtered >> 1;
+}
+
+/* Function 5 */
+static noinline u32 transform_data(struct payload *pl, u32 raw_offset)
+{
+ /* Transforms the offset using the payload's key */
+ return raw_offset * pl->transform_key;
+}
+
+/* Function 4 */
+static noinline struct payload *extract_payload(void *buf, u32 offset, u32 size)
+{
+ /* In real code: validates and extracts payload from buffer */
+ return (struct payload *)((u8 *)buf + offset);
+}
+
+/* Function 3 */
+static noinline int validate_header(struct request_header *hdr)
+{
+ if (hdr->magic != 0x50524F54) /* "PROT" */
+ return -1;
+ if (hdr->version > 2)
+ return -1;
+ /* BUG: doesn't validate payload_offset bounds! */
+ return 0;
+}
+
+/* Function 2 */
+static noinline int parse_request(void *buf, u32 buf_size,
+ struct request_header **out_hdr,
+ struct payload **out_payload)
+{
+ struct request_header *hdr = (struct request_header *)buf;
+
+ if (validate_header(hdr) < 0)
+ return -1;
+
+ *out_hdr = hdr;
+ *out_payload = extract_payload(buf, hdr->payload_offset, hdr->payload_size);
+ return 0;
+}
+
+/* Function 1 (ENTRY): The syscall handler */
+static noinline int entry_handler(void *user_buf, u32 user_size)
+{
+ struct request_header *hdr;
+ struct payload *pl;
+ struct slot_table *table;
+ u32 transformed, filtered, index, slot;
+
+ if (parse_request(user_buf, user_size, &hdr, &pl) < 0)
+ return -1;
+
+ table = kzalloc(sizeof(*table), GFP_KERNEL);
+ if (!table)
+ return -ENOMEM;
+ table->num_slots = 8;
+
+ /* The tainted data flow:
+ * hdr->payload_offset → extract_payload → pl
+ * pl->transform_key + payload_offset → transform_data → transformed
+ * transformed + pl->filter_mask → apply_filter → filtered
+ * transformed + filtered → compute_index → index
+ * index → lookup_slot → slot (% 16, should be % 8)
+ * slot → write_slot → commit_write (OOB if slot >= 8)
+ */
+ transformed = transform_data(pl, hdr->payload_offset);
+ filtered = apply_filter(pl, transformed);
+ index = compute_index(transformed, filtered);
+ slot = lookup_slot(table, index);
+
+ pr_info("deep_chain: slot=%u (OOB if >= 8)\n", slot);
+
+ kfree(table);
+ return 0;
+}
+
+/* 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;
+ pl->transform_key = 3; /* multiplier */
+ pl->filter_mask = 0xFFFFFFFF; /* no filtering */
+ memcpy(pl->data, "ATTACKER_PAYLOAD_DATA!!!", 24);
+
+ /*
+ * Trace: payload_offset=16, transform_key=3
+ * transformed = 16 * 3 = 48
+ * filtered = (48 & 0xFFFFFFFF) >> 1 = 24
+ * index = (48 + 24) & 0xF = 72 & 0xF = 8
+ * lookup_slot: final_idx = 8 % 16 = 8 ← OOB! (table has slots[0..7])
+ */
+
+ pr_info("deep_chain: triggering 10-deep call chain with offset=%u\n",
+ hdr->payload_offset);
+
+ entry_handler(buf, 256);
+
+ kfree(buf);
+ return count;
+}
+
+static const struct proc_ops deep_proc_ops = {
+ .proc_write = deep_trigger_write,
+};
+
+static int __init deep_chain_init(void)
+{
+ proc_deep = proc_create("deep_trigger", 0200, NULL, &deep_proc_ops);
+ if (!proc_deep)
+ return -ENOMEM;
+ pr_info("deep_chain_mod: loaded. echo x > /proc/deep_trigger\n");
+ return 0;
+}
+
+static void __exit deep_chain_exit(void)
+{
+ proc_remove(proc_deep);
+}
+
+module_init(deep_chain_init);
+module_exit(deep_chain_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("10-deep call chain for kcov_dataflow visualization");
diff --git a/tools/kcov-dataflow/eight_args_c/Makefile b/tools/kcov-dataflow/eight_args_c/Makefile
new file mode 100644
index 000000000000..de35bb541f07
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_c/Makefile
@@ -0,0 +1,2 @@
+obj-m := eight_args_mod.o
+KCOV_DATAFLOW_eight_args_mod.o := y
diff --git a/tools/kcov-dataflow/eight_args_c/eight_args_mod.c b/tools/kcov-dataflow/eight_args_c/eight_args_mod.c
new file mode 100644
index 000000000000..660b27033756
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_c/eight_args_mod.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * eight_args_mod.c - Verify kcov_dataflow captures 1 through 8 argument functions.
+ */
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KCOV dataflow 8-argument stress test module");
+
+noinline u64 func1(u64 a1)
+{
+ return a1;
+}
+EXPORT_SYMBOL(func1);
+
+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);
+
+noinline u64 func4(u64 a1, u64 a2, u64 a3, u64 a4)
+{
+ return a1 + a2 + a3 + a4;
+}
+EXPORT_SYMBOL(func4);
+
+noinline u64 func5(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5)
+{
+ return a1 + a2 + a3 + a4 + a5;
+}
+EXPORT_SYMBOL(func5);
+
+noinline u64 func6(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6)
+{
+ return a1 + a2 + a3 + a4 + a5 + a6;
+}
+EXPORT_SYMBOL(func6);
+
+noinline u64 func7(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6,
+ u64 a7)
+{
+ return a1 + a2 + a3 + a4 + a5 + a6 + a7;
+}
+EXPORT_SYMBOL(func7);
+
+noinline u64 func8(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6,
+ u64 a7, u64 a8)
+{
+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8;
+}
+EXPORT_SYMBOL(func8);
+
+static ssize_t trigger_write(struct file *f, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ 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;
+}
+
+static const struct proc_ops ops = { .proc_write = trigger_write };
+
+static int __init init_mod(void)
+{
+ proc_create("test_args", 0200, NULL, &ops);
+ return 0;
+}
+
+static void __exit exit_mod(void)
+{
+ remove_proc_entry("test_args", NULL);
+}
+
+module_init(init_mod);
+module_exit(exit_mod);
diff --git a/tools/kcov-dataflow/eight_args_rust/Makefile b/tools/kcov-dataflow/eight_args_rust/Makefile
new file mode 100644
index 000000000000..8881d369e670
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_rust/Makefile
@@ -0,0 +1,2 @@
+obj-m := eight_args_rust.o
+KCOV_DATAFLOW_eight_args_rust.o := y
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.
+#![allow(missing_docs)]
+
+use kernel::prelude::*;
+use kernel::c_str;
+
+module! {
+ type: ArgsModule,
+ name: "eight_args_rust",
+ authors: ["kcov-dataflow"],
+ description: "1-8 arg verification",
+ license: "GPL",
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc1(a1: u64) -> u64 { a1 }
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc2(a1: u64, a2: u64) -> u64 { a1 + a2 }
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc3(a1: u64, a2: u64, a3: u64) -> u64 {
+ a1 + a2 + a3
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc4(a1: u64, a2: u64, a3: u64, a4: u64) -> u64 {
+ a1 + a2 + a3 + a4
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc5(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc6(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc7(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64, a7: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6 + a7
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc8(
+ a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64, a7: u64,
+ a8: u64,
+) -> u64 {
+ a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8
+}
+
+unsafe extern "C" fn write_handler(
+ _file: *mut kernel::bindings::file,
+ _buf: *const core::ffi::c_char,
+ count: usize,
+ _ppos: *mut kernel::bindings::loff_t,
+) -> kernel::ffi::c_long {
+ let r1 = rfunc1(0x11);
+ pr_info!("rfunc1: ret=0x{:x}\n", r1);
+ let r2 = rfunc2(0x11, 0x22);
+ pr_info!("rfunc2: ret=0x{:x}\n", r2);
+ let r3 = rfunc3(0x11, 0x22, 0x33);
+ pr_info!("rfunc3: ret=0x{:x}\n", r3);
+ let r4 = rfunc4(0x11, 0x22, 0x33, 0x44);
+ pr_info!("rfunc4: ret=0x{:x}\n", r4);
+ let r5 = rfunc5(0x11, 0x22, 0x33, 0x44, 0x55);
+ pr_info!("rfunc5: ret=0x{:x}\n", r5);
+ let r6 = rfunc6(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
+ pr_info!("rfunc6: ret=0x{:x}\n", r6);
+ let r7 = rfunc7(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77);
+ pr_info!("rfunc7: ret=0x{:x}\n", r7);
+ let r8 = rfunc8(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88);
+ pr_info!("rfunc8: ret=0x{:x}\n", r8);
+ count as kernel::ffi::c_long
+}
+
+#[repr(transparent)]
+struct SyncFops(kernel::bindings::file_operations);
+unsafe impl Sync for SyncFops {}
+
+static FOPS: SyncFops = SyncFops(kernel::bindings::file_operations {
+ write: Some(unsafe { core::mem::transmute(write_handler as *const ()) }),
+ ..unsafe { core::mem::zeroed() }
+});
+
+struct ArgsModule { d: *mut kernel::bindings::dentry }
+
+impl kernel::Module for ArgsModule {
+ fn init(_module: &'static ThisModule) -> Result<Self> {
+ let d = unsafe {
+ kernel::bindings::debugfs_create_file_unsafe(
+ c_str!("test_args_rust").as_char_ptr(),
+ 0o222, core::ptr::null_mut(), core::ptr::null_mut(), &FOPS.0,
+ )
+ };
+ Ok(Self { d })
+ }
+}
+impl Drop for ArgsModule {
+ fn drop(&mut self) { unsafe { kernel::bindings::debugfs_remove(self.d) }; }
+}
+unsafe impl Send for ArgsModule {}
+unsafe impl Sync for ArgsModule {}
diff --git a/tools/kcov-dataflow/kcov-view.py b/tools/kcov-dataflow/kcov-view.py
new file mode 100755
index 000000000000..70acb5474f5e
--- /dev/null
+++ b/tools/kcov-dataflow/kcov-view.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+kcov-view.py - Merged KCOV + KCOV_DATAFLOW viewer
+
+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)
+KCOV_INIT_TRACE = 0x80086301 # _IOR('c', 1, unsigned long)
+KCOV_ENABLE = 0x6364 # _IO('c', 100)
+KCOV_DISABLE = 0x6365 # _IO('c', 101)
+KCOV_TRACE_PC = 0
+
+KCOV_DF_INIT_TRACE = 0x80086401 # _IOR('d', 1, unsigned long)
+KCOV_DF_ENABLE = 0x6464 # _IO('d', 100)
+KCOV_DF_DISABLE = 0x6465 # _IO('d', 101)
+
+BUF_SIZE = 65536 # 65536 * 8 = 512KB = 128 pages (page-aligned)
+
+# Load kallsyms for symbolization
+def load_kallsyms():
+ syms = {}
+ try:
+ with open("/proc/kallsyms") as f:
+ for line in f:
+ parts = line.split()
+ if len(parts) >= 3:
+ addr = int(parts[0], 16)
+ name = parts[2]
+ mod = parts[3].strip("[]") if len(parts) > 3 else ""
+ syms[addr] = (name, mod)
+ except:
+ pass
+ return syms
+
+def symbolize(pc, syms):
+ """Find nearest symbol <= pc"""
+ best_addr = 0
+ best_name = f"0x{pc:x}"
+ best_mod = ""
+ for addr, (name, mod) in syms.items():
+ if addr <= pc and addr > best_addr:
+ best_addr = addr
+ best_name = name
+ best_mod = mod
+ offset = pc - best_addr
+ if best_mod:
+ return f"{best_name}+0x{offset:x} [{best_mod}]"
+ return f"{best_name}+0x{offset:x}"
+
+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
+
+ if typ not in (0xE0000000, 0xF0000000):
+ i += 1
+ continue
+
+ pc = buf[i + 1]
+ meta = buf[i + 2]
+ i += 3
+
+ # Collect field values
+ fields = []
+ while i <= n and i < BUF_SIZE:
+ v = buf[i]
+ vtype = v & 0xF0000000
+ 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
+ })
+ else:
+ ret_sz = (meta >> 48) & 0xFF
+ ptr = meta & 0xFFFFFFFFFFFF
+ events.append({
+ "type": "ret", "seq": seq, "pc": pc,
+ "ret_size": ret_sz, "ptr": ptr, "fields": fields
+ })
+ return events
+
+def format_value(val):
+ if val == 0xBADADD85:
+ return "FAULT"
+ if val == 0:
+ return "0"
+ return f"0x{val:x}"
+
+def format_entry(ev):
+ """Format an entry event as a function argument."""
+ if len(ev["fields"]) > 1:
+ # Struct: multiple fields
+ flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in enumerate(ev["fields"]))
+ return f"struct{{{flds}}}"
+ elif len(ev["fields"]) == 1:
+ v = ev["fields"][0]
+ if v == 0 and ev["ptr"] == 0:
+ return "NULL"
+ return format_value(v)
+ return format_value(ev["ptr"])
+
+def merge_and_display(pc_trace, df_events, syms):
+ """Display dataflow events with symbolization."""
+ print("\n╔═══════════════════════════════════════════════════════════╗")
+ print("║ Merged KCOV Coverage + Dataflow View ║")
+ print("╚═══════════════════════════════════════════════════════════╝\n")
+
+ if not df_events:
+ print(" (no dataflow events captured)")
+ return
+
+ # Group events into calls: consecutive entries for same PC followed by a ret
+ calls = []
+ current_args = []
+ current_pc = None
+
+ for ev in df_events:
+ if ev["type"] == "entry":
+ if current_pc is not None and ev["pc"] != current_pc:
+ calls.append({"pc": current_pc, "args": current_args, "ret": None})
+ current_args = []
+ current_pc = ev["pc"]
+ current_args.append(ev)
+ elif ev["type"] == "ret":
+ if current_pc == ev["pc"]:
+ calls.append({"pc": current_pc, "args": current_args, "ret": ev})
+ current_args = []
+ current_pc = None
+ else:
+ if current_args:
+ calls.append({"pc": current_pc, "args": current_args, "ret": None})
+ current_args = []
+ calls.append({"pc": ev["pc"], "args": [], "ret": ev})
+ current_pc = None
+
+ if current_args:
+ calls.append({"pc": current_pc, "args": current_args, "ret": None})
+
+ for call in calls:
+ sym = symbolize(call["pc"], syms)
+ args_parts = []
+ for a in call["args"]:
+ idx = a["arg_idx"]
+ if len(a["fields"]) > 1:
+ flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in enumerate(a["fields"]))
+ args_parts.append(f"arg[{idx}]=struct{{{flds}}}")
+ elif len(a["fields"]) == 1:
+ args_parts.append(f"arg[{idx}]={format_value(a['fields'][0])}")
+ else:
+ args_parts.append(f"arg[{idx}]=?")
+
+ print(f" → {sym}({', '.join(args_parts)})")
+
+ if call["ret"]:
+ r = call["ret"]
+ if len(r["fields"]) > 1:
+ flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in enumerate(r["fields"]))
+ print(f" ← ret = struct{{{flds}}}")
+ elif len(r["fields"]) == 1:
+ print(f" ← ret = {format_value(r['fields'][0])}")
+ print()
+
+def main():
+ if len(sys.argv) < 2:
+ print(f"Usage: {sys.argv[0]} <trigger_command>")
+ print(f"Example: {sys.argv[0]} 'echo x > /proc/uaf_trigger'")
+ sys.exit(1)
+
+ trigger_cmd = sys.argv[1]
+ syms = load_kallsyms()
+
+ # Setup ctypes mmap
+ libc = ctypes.CDLL("libc.so.6", use_errno=True)
+ libc.mmap.restype = ctypes.c_void_p
+ libc.mmap.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int,
+ ctypes.c_int, ctypes.c_int, ctypes.c_long]
+ PROT_RW = 0x3 # PROT_READ | PROT_WRITE
+ MAP_SHARED = 0x01
+
+ # Open both devices
+ kcov_fd = -1
+ df_fd = -1
+ kcov_arr = None
+ df_arr = None
+
+ # Legacy kcov (PC trace) - skip for now, use kallsyms for symbolization
+ kcov_arr = None
+
+ # Dataflow device - required
+ df_fd = os.open("/sys/kernel/debug/kcov_dataflow", os.O_RDWR)
+ fcntl.ioctl(df_fd, KCOV_DF_INIT_TRACE, BUF_SIZE)
+ df_ptr = libc.mmap(None, BUF_SIZE * 8, PROT_RW, MAP_SHARED, df_fd, 0)
+ if df_ptr == ctypes.c_void_p(-1).value:
+ print("Error: kcov_dataflow mmap failed")
+ sys.exit(1)
+ df_arr = (ctypes.c_uint64 * BUF_SIZE).from_address(df_ptr)
+
+ # Enable both
+ if kcov_arr:
+ fcntl.ioctl(kcov_fd, KCOV_ENABLE, KCOV_TRACE_PC)
+ kcov_arr[0] = 0
+
+ fcntl.ioctl(df_fd, KCOV_DF_ENABLE, 0)
+ df_arr[0] = 0
+
+ # Trigger - must happen in THIS process (kcov_dataflow is per-task)
+ if ">" in trigger_cmd:
+ target = trigger_cmd.split(">")[-1].strip()
+ else:
+ target = trigger_cmd
+ try:
+ fd_t = os.open(target, os.O_WRONLY)
+ os.write(fd_t, b"x")
+ os.close(fd_t)
+ except Exception as e:
+ print(f"Trigger failed: {e}")
+
+ # Read results
+ pc_trace = []
+ if kcov_arr:
+ n_pcs = kcov_arr[0]
+ for i in range(1, min(int(n_pcs) + 1, BUF_SIZE)):
+ pc_trace.append(kcov_arr[i])
+ fcntl.ioctl(kcov_fd, KCOV_DISABLE, 0)
+
+ n_df = int(df_arr[0])
+ df_raw = [int(df_arr[i]) for i in range(min(n_df + 10, BUF_SIZE))]
+ fcntl.ioctl(df_fd, KCOV_DF_DISABLE, 0)
+
+ # Parse and display
+ df_events = parse_dataflow(df_raw, int(n_df))
+ merge_and_display(pc_trace, df_events, syms)
+
+ # Cleanup
+ if kcov_arr:
+ os.close(kcov_fd)
+ os.close(df_fd)
+
+if __name__ == "__main__":
+ main()
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
+ * function args/ret TLV records. Completely independent from legacy kcov.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+#define KCOV_DF_ENABLE _IO('d', 100)
+#define KCOV_DF_DISABLE _IO('d', 101)
+
+#define 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;
+ uint64_t seq = hdr & 0x00FFFFFFULL;
+ uint64_t pc = cover[i + 1];
+ uint64_t meta = cover[i + 2];
+
+ if (type == 0xE0000000ULL) {
+ uint32_t arg_idx = (meta >> 56) & 0xFF;
+ uint32_t arg_sz = (meta >> 48) & 0xFF;
+ uint64_t ptr = meta & 0xFFFFFFFFFFFFULL;
+
+ printf("[ENTRY] seq=%lu pc=0x%lx arg[%u](%u) ptr=0x%lx\n",
+ seq, pc, arg_idx, arg_sz, ptr);
+ } else if (type == 0xF0000000ULL) {
+ uint32_t ret_sz = (meta >> 48) & 0xFF;
+ uint64_t ptr = meta & 0xFFFFFFFFFFFFULL;
+
+ printf("[RET] seq=%lu pc=0x%lx ret(%u) ptr=0x%lx\n",
+ seq, pc, ret_sz, ptr);
+ } else {
+ i++;
+ continue;
+ }
+
+ /* Print field values */
+ i += 3;
+ while (i <= n && i < COVER_SIZE) {
+ uint64_t next = cover[i];
+ uint64_t next_type = next & 0xF0000000ULL;
+
+ if (next_type == 0xE0000000ULL || next_type == 0xF0000000ULL)
+ break;
+ if (next == 0xBADADD85ULL)
+ printf(" val = FAULT\n");
+ else
+ printf(" val = 0x%lx\n", next);
+ i++;
+ }
+ }
+ printf("=== Done ===\n");
+}
+
+int main(int argc, char **argv)
+{
+ const char *trigger_path = "/proc/uaf_trigger";
+ int fd, tfd;
+ uint64_t *cover;
+ uint64_t n;
+
+ if (argc > 1)
+ trigger_path = argv[1];
+
+ fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ if (fd < 0) {
+ perror("open kcov_dataflow");
+ return 1;
+ }
+
+ if (ioctl(fd, KCOV_DF_INIT_TRACE, COVER_SIZE)) {
+ perror("KCOV_DF_INIT_TRACE");
+ close(fd);
+ return 1;
+ }
+
+ cover = mmap(NULL, COVER_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (cover == MAP_FAILED) {
+ perror("mmap");
+ close(fd);
+ return 1;
+ }
+
+ if (ioctl(fd, KCOV_DF_ENABLE, 0)) {
+ perror("KCOV_DF_ENABLE");
+ munmap(cover, COVER_SIZE * sizeof(uint64_t));
+ close(fd);
+ return 1;
+ }
+
+ /* Reset */
+ __atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);
+
+ /* Trigger */
+ tfd = open(trigger_path, O_WRONLY);
+ if (tfd >= 0) {
+ write(tfd, "x", 1);
+ close(tfd);
+ }
+
+ n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);
+
+ ioctl(fd, KCOV_DF_DISABLE, 0);
+
+ dump_buffer(cover, n);
+
+ munmap(cover, COVER_SIZE * sizeof(uint64_t));
+ close(fd);
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 5/6] kcov: add interrupt context guard to kcov_df_write()
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
` (3 preceding siblings ...)
2026-06-03 17:43 ` [RFC PATCH v2 4/6] tools/kcov-dataflow: add userspace consumer and test modules Yunseong Kim
@ 2026-06-03 17:43 ` Yunseong Kim
2026-06-04 8:48 ` Peter Zijlstra
2026-06-03 17:43 ` [RFC PATCH v2 6/6] kcov: add recursion guard and documentation for kcov-dataflow Yunseong Kim
` (2 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Yunseong Kim @ 2026-06-03 17:43 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: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
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
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH v2 6/6] kcov: add recursion guard and documentation for kcov-dataflow
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
` (4 preceding siblings ...)
2026-06-03 17:43 ` [RFC PATCH v2 5/6] kcov: add interrupt context guard to kcov_df_write() Yunseong Kim
@ 2026-06-03 17:43 ` Yunseong Kim
2026-06-04 8:52 ` Peter Zijlstra
2026-06-04 8:40 ` [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Peter Zijlstra
2026-06-04 9:29 ` Yunseong Kim
7 siblings, 1 reply; 15+ messages in thread
From: Yunseong Kim @ 2026-06-03 17:43 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: Yunseong Kim, linux-kernel, kasan-dev, llvm, linux-kbuild,
rust-for-linux, workflows, linux-doc, Yunseong Kim
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>
---
Documentation/dev-tools/kcov-dataflow.rst | 282 ++++++++++++++++++++++++++++++
kernel/Makefile | 3 +
kernel/kcov.c | 14 +-
3 files changed, 298 insertions(+), 1 deletion(-)
diff --git a/Documentation/dev-tools/kcov-dataflow.rst b/Documentation/dev-tools/kcov-dataflow.rst
new file mode 100644
index 000000000000..5941df9f29e6
--- /dev/null
+++ b/Documentation/dev-tools/kcov-dataflow.rst
@@ -0,0 +1,282 @@
+KCOV-Dataflow: function argument and return value extraction
+=============================================================
+
+KCOV-Dataflow captures function arguments and return values — including
+automatic struct field decomposition — at instrumented kernel function
+boundaries. It provides per-task, lock-free ring buffers accessible via
+``mmap()``, enabling data-flow-aware fuzzing and post-mortem contract
+verification.
+
+Unlike KCOV's ``trace-pc`` which reports *which* code executed,
+KCOV-Dataflow reports *what values* were passed and returned. This is
+a completely separate device from ``/sys/kernel/debug/kcov``.
+
+Prerequisites
+-------------
+
+KCOV-Dataflow requires Clang/LLVM with the ``dataflow-args`` and
+``dataflow-ret`` SanitizerCoverage extensions. Standard (unpatched)
+compilers will not expose these Kconfig options.
+
+To enable KCOV-Dataflow, configure the kernel with::
+
+ CONFIG_KCOV=y
+ CONFIG_KCOV_DATAFLOW_ARGS=y
+ CONFIG_KCOV_DATAFLOW_RET=y
+
+Optional: instrument the entire kernel (significant overhead)::
+
+ CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y
+
+Coverage data becomes accessible once debugfs is mounted::
+
+ mount -t debugfs none /sys/kernel/debug
+
+Per-module instrumentation
+--------------------------
+
+To instrument a specific module, add to its Makefile::
+
+ KCOV_DATAFLOW_my_module.o := y
+
+For example, to instrument the Android binder driver::
+
+ # drivers/android/Makefile
+ KCOV_DATAFLOW_binder.o := y
+ KCOV_DATAFLOW_binder_alloc.o := y
+
+For Rust modules, add to the crate's Makefile::
+
+ # drivers/android/binder/Makefile
+ KCOV_DATAFLOW := y
+
+To instrument an entire directory, set the variable without a filename::
+
+ # fs/Makefile
+ KCOV_DATAFLOW := y
+
+The build system automatically adds the required compiler flags
+(``-fsanitize-coverage=dataflow-args,dataflow-ret -g``).
+
+Data collection
+---------------
+
+The following program demonstrates how to collect function argument and
+return value data for a single syscall:
+
+.. code-block:: c
+
+ #include <stdio.h>
+ #include <stdint.h>
+ #include <stdlib.h>
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
+ #include <sys/mman.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+
+ #define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+ #define KCOV_DF_ENABLE _IO('d', 100)
+ #define KCOV_DF_DISABLE _IO('d', 101)
+ #define BUF_SIZE (64 << 10) /* 64K words = 512KB */
+
+ int main(void)
+ {
+ int fd;
+ uint64_t *buf, n, i;
+
+ fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ if (fd == -1)
+ perror("open"), exit(1);
+
+ /* Allocate buffer (size in u64 words). */
+ if (ioctl(fd, KCOV_DF_INIT_TRACE, BUF_SIZE))
+ perror("ioctl(INIT)"), exit(1);
+
+ /* Map the buffer into user space. */
+ buf = (uint64_t *)mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (buf == MAP_FAILED)
+ perror("mmap"), exit(1);
+
+ /* Enable data-flow collection for this task. */
+ if (ioctl(fd, KCOV_DF_ENABLE, 0))
+ perror("ioctl(ENABLE)"), exit(1);
+
+ /* Reset counter. */
+ __atomic_store_n(&buf[0], 0, __ATOMIC_RELAXED);
+
+ /* === Trigger syscall(s) here === */
+ read(-1, NULL, 0);
+
+ /* Read how many words were written. */
+ n = __atomic_load_n(&buf[0], __ATOMIC_RELAXED);
+
+ /* Parse TLV records. */
+ i = 1;
+ while (i < n) {
+ uint64_t type_seq = buf[i];
+ uint64_t pc = buf[i + 1];
+ uint64_t meta = buf[i + 2];
+ uint32_t type = (type_seq >> 28) & 0xF;
+ uint32_t seq = type_seq & 0x00FFFFFF;
+ uint32_t arg_idx = (meta >> 56) & 0xFF;
+ uint32_t size = (meta >> 48) & 0xFF;
+
+ printf("[%s] seq=%u pc=0x%lx arg_idx=%u size=%u val=0x%lx\n",
+ type == 0xE ? "ENTRY" : "RET",
+ seq, pc, arg_idx, size, buf[i + 3]);
+ i += 4; /* minimum record size: 3 header + 1 value */
+ }
+
+ if (ioctl(fd, KCOV_DF_DISABLE, 0))
+ perror("ioctl(DISABLE)"), exit(1);
+
+ munmap(buf, BUF_SIZE * sizeof(uint64_t));
+ close(fd);
+ return 0;
+ }
+
+Ring buffer format
+------------------
+
+The buffer is an array of ``u64`` words::
+
+ buf[0]: atomic counter — total words written
+
+Each record occupies 3 + N words:
+
++--------+------------------+---------------------------------------------+
+| Offset | Field | Description |
++========+==================+=============================================+
+| 0 | type_and_seq | bits[31:28] = 0xE (entry) or 0xF (return), |
+| | | bits[23:0] = per-task sequence number |
++--------+------------------+---------------------------------------------+
+| 1 | pc | Instrumented function address |
++--------+------------------+---------------------------------------------+
+| 2 | meta | bits[63:56] = arg_idx (0 for return), |
+| | | bits[55:48] = size in bytes, |
+| | | bits[47:0] = raw pointer value |
++--------+------------------+---------------------------------------------+
+| 3..N | field_val[0..N] | Struct field values or single scalar |
++--------+------------------+---------------------------------------------+
+
+Magic values:
+
+- ``0xBADADD85``: field read failed (pointer was invalid/freed/poisoned)
+
+Safety
+------
+
+- Callbacks are ``notrace``, ``__no_sanitize_coverage``, ``noinline``
+ to prevent recursion.
+- All pointer reads use ``copy_from_kernel_nofault()`` — survives
+ freed, poisoned, or unmapped memory.
+- An ``in_task()`` guard rejects calls from hardirq/softirq/NMI context,
+ preventing reentrant buffer corruption.
+- No ``printk`` or allocation in the data path.
+- When not enabled for a task, overhead is a single boolean check.
+
+Ioctl interface
+---------------
+
++---------------------+----------------------------+---------------------------+
+| Command | Value | Description |
++=====================+============================+===========================+
+| KCOV_DF_INIT_TRACE | ``_IOR('d', 1, unsigned | Allocate buffer |
+| | long)`` | (size in u64 words) |
++---------------------+----------------------------+---------------------------+
+| KCOV_DF_ENABLE | ``_IO('d', 100)`` | Start collection for |
+| | | current task |
++---------------------+----------------------------+---------------------------+
+| KCOV_DF_DISABLE | ``_IO('d', 101)`` | Stop collection |
++---------------------+----------------------------+---------------------------+
+
+Compatibility
+-------------
+
+KCOV-Dataflow is completely independent from legacy KCOV:
+
+- Separate device: ``/sys/kernel/debug/kcov_dataflow``
+- Separate ioctl namespace (``'d'`` vs ``'c'``)
+- Separate per-task buffer
+- Both can be used simultaneously without interference
+- syzkaller and other KCOV users are unaffected
+
+Rust module support
+-------------------
+
+Rust kernel modules are supported via a post-compilation pipeline::
+
+ rustc --emit=llvm-ir -g module.rs
+ opt -passes=sancov-module \
+ -sanitizer-coverage-dataflow-args \
+ -sanitizer-coverage-dataflow-ret module.ll -S -o module_inst.ll
+ llc -filetype=obj module_inst.ll -o module.o
+
+This is the good method for capturing Rust function arguments at runtime.
+
+
+Tracing child processes (fork interception)
+-------------------------------------------
+
+KCOV-Dataflow is per-task: after ``fork()``, the child does not inherit
+the enabled state. To trace child processes, re-enable on the inherited
+file descriptor in the child before ``exec()``. The ``mmap``'d buffer is
+shared (``MAP_SHARED``), so both parent and child write to the same ring
+buffer atomically.
+
+.. code-block:: c
+
+ #include <stdio.h>
+ #include <stdint.h>
+ #include <stdlib.h>
+ #include <sys/ioctl.h>
+ #include <sys/mman.h>
+ #include <sys/wait.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+
+ #define KCOV_DF_INIT_TRACE _IOR('d', 1, unsigned long)
+ #define KCOV_DF_ENABLE _IO('d', 100)
+ #define KCOV_DF_DISABLE _IO('d', 101)
+ #define BUF_SIZE (64 << 10)
+
+ int main(int argc, char **argv)
+ {
+ int fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+ ioctl(fd, KCOV_DF_INIT_TRACE, BUF_SIZE);
+ uint64_t *buf = mmap(NULL, BUF_SIZE * 8,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ /* Enable for parent task */
+ ioctl(fd, KCOV_DF_ENABLE, 0);
+ __atomic_store_n(&buf[0], 0, __ATOMIC_RELAXED);
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ /* Child: re-enable on inherited fd.
+ * The shared mmap buffer receives records from both tasks.
+ */
+ ioctl(fd, KCOV_DF_ENABLE, 0);
+ execvp(argv[1], &argv[1]);
+ _exit(1);
+ }
+
+ waitpid(pid, NULL, 0);
+ ioctl(fd, KCOV_DF_DISABLE, 0);
+
+ uint64_t n = __atomic_load_n(&buf[0], __ATOMIC_RELAXED);
+ printf("Captured %lu words from parent + child\n", n);
+
+ munmap(buf, BUF_SIZE * 8);
+ close(fd);
+ return 0;
+ }
+
+Note: the child's ``ioctl(fd, KCOV_DF_ENABLE)`` will fail if the parent
+has not yet called ``KCOV_DF_DISABLE``, because only one task can be
+associated with a descriptor at a time. For true multi-process tracing,
+open a separate ``kcov_dataflow`` fd per child, or disable in the parent
+before the child enables (as shown above — the parent is blocked in
+``waitpid`` so it generates no records during that time anyway).
diff --git a/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
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
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
` (5 preceding siblings ...)
2026-06-03 17:43 ` [RFC PATCH v2 6/6] kcov: add recursion guard and documentation for kcov-dataflow Yunseong Kim
@ 2026-06-04 8:40 ` Peter Zijlstra
2026-06-04 9:29 ` Yunseong Kim
7 siblings, 0 replies; 15+ messages in thread
From: Peter Zijlstra @ 2026-06-04 8:40 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
On Wed, Jun 03, 2026 at 07:43:27PM +0200, Yunseong Kim wrote:
> CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL instruments every function in the
> kernel.
Well, I would hope it would very much not instrument noinstr functions.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values
2026-06-03 17:43 ` [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values Yunseong Kim
@ 2026-06-04 8:41 ` Peter Zijlstra
0 siblings, 0 replies; 15+ messages in thread
From: Peter Zijlstra @ 2026-06-04 8:41 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
On Wed, Jun 03, 2026 at 07:43:28PM +0200, Yunseong Kim wrote:
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index e2f976c3301b..abd1a94589aa 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -2261,6 +2261,28 @@ config KCOV_SELFTEST
> On test failure, causes the kernel to panic. Recommended to be
> enabled, ensuring critical functionality works as intended.
>
> +
^ This line...
> +config KCOV_DATAFLOW_ARGS
> + bool "Enable KCOV dataflow: function argument capture"
> + depends on KCOV
> + depends on $(cc-option,-fsanitize-coverage=dataflow-args)
> + help
> + Captures function arguments at entry via /sys/kernel/debug/kcov_dataflow.
> + Struct pointer arguments are auto-expanded using compiler DebugInfo
> + metadata, recording individual field values at runtime.
> + Enable per-module with: KCOV_DATAFLOW_file.o := y in the Makefile.
> + Requires clang with -fsanitize-coverage=dataflow-args support.
> +
> +config KCOV_DATAFLOW_RET
> + bool "Enable KCOV dataflow: return value capture"
> + depends on KCOV
> + depends on $(cc-option,-fsanitize-coverage=dataflow-ret)
> + help
> + Captures function return values via /sys/kernel/debug/kcov_dataflow.
> + Struct pointer returns are auto-expanded using compiler DebugInfo
> + metadata, recording individual field values at runtime.
> + Enable per-module with: KCOV_DATAFLOW_file.o := y in the Makefile.
> + Requires clang with -fsanitize-coverage=dataflow-ret support.
Probably wants to be here...
> config DEBUG_AID_FOR_SYZBOT
> bool "Additional debug code for syzbot"
> default n
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation
2026-06-03 17:43 ` [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation Yunseong Kim
@ 2026-06-04 8:45 ` Peter Zijlstra
2026-06-04 21:48 ` Nathan Chancellor
0 siblings, 1 reply; 15+ messages in thread
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
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 [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 3/6] kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE
2026-06-03 17:43 ` [RFC PATCH v2 3/6] kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE Yunseong Kim
@ 2026-06-04 8:46 ` Peter Zijlstra
0 siblings, 0 replies; 15+ messages in thread
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
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 [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 5/6] kcov: add interrupt context guard to kcov_df_write()
2026-06-03 17:43 ` [RFC PATCH v2 5/6] kcov: add interrupt context guard to kcov_df_write() Yunseong Kim
@ 2026-06-04 8:48 ` Peter Zijlstra
0 siblings, 0 replies; 15+ messages in thread
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
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 [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 6/6] kcov: add recursion guard and documentation for kcov-dataflow
2026-06-03 17:43 ` [RFC PATCH v2 6/6] kcov: add recursion guard and documentation for kcov-dataflow Yunseong Kim
@ 2026-06-04 8:52 ` Peter Zijlstra
0 siblings, 0 replies; 15+ messages in thread
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
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 [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
` (6 preceding siblings ...)
2026-06-04 8:40 ` [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Peter Zijlstra
@ 2026-06-04 9:29 ` Yunseong Kim
7 siblings, 0 replies; 15+ messages in thread
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
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 [flat|nested] 15+ messages in thread
* Re: [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation
2026-06-04 8:45 ` Peter Zijlstra
@ 2026-06-04 21:48 ` Nathan Chancellor
0 siblings, 0 replies; 15+ messages in thread
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
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 [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-06-04 21:48 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-03 17:43 [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Yunseong Kim
2026-06-03 17:43 ` [RFC PATCH v2 1/6] kcov: add per-task dataflow tracking for function arguments/return values Yunseong Kim
2026-06-04 8:41 ` Peter Zijlstra
2026-06-03 17:43 ` [RFC PATCH v2 2/6] kcov: add build system support for dataflow instrumentation Yunseong Kim
2026-06-04 8:45 ` Peter Zijlstra
2026-06-04 21:48 ` Nathan Chancellor
2026-06-03 17:43 ` [RFC PATCH v2 3/6] kcov: add CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL and NO_INLINE Yunseong Kim
2026-06-04 8:46 ` Peter Zijlstra
2026-06-03 17:43 ` [RFC PATCH v2 4/6] tools/kcov-dataflow: add userspace consumer and test modules Yunseong Kim
2026-06-03 17:43 ` [RFC PATCH v2 5/6] kcov: add interrupt context guard to kcov_df_write() Yunseong Kim
2026-06-04 8:48 ` Peter Zijlstra
2026-06-03 17:43 ` [RFC PATCH v2 6/6] kcov: add recursion guard and documentation for kcov-dataflow Yunseong Kim
2026-06-04 8:52 ` Peter Zijlstra
2026-06-04 8:40 ` [RFC PATCH v2 0/6] kcov: per-task dataflow extraction at kernel function boundaries Peter Zijlstra
2026-06-04 9:29 ` Yunseong Kim
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox