Skip to contentSkip to conversation

Add task latency over budget RV monitor

PATCH Superseded [email protected] 2 participants 19 messages Aug 19

Subsystems (4)

v5 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v5 cover Add task latency over budget RV monitor

From: Wen Yang <[email protected]>

This series introduces tlob (task latency over budget), a per-task hybrid automaton RV monitor that measures elapsed wall-clock time across a user-delimited code section and emits an error when the elapsed time exceeds a configurable budget.

The monitor tracks three states (running, waiting, sleeping) driven by sched_switch and sched_wakeup tracepoints. A single clock invariant, clk_elapsed < BUDGET_NS(), is enforced by a per-task hrtimer (HRTIMER_MODE_REL_HARD). On expiry, error_env_tlob is emitted, followed by detail_env_tlob carrying a per-state time breakdown (running_ns, waiting_ns, sleeping_ns).

Tasks are registered for monitoring by writing to the tracefs monitor file:

  echo "p /path/to/binary:OFFSET_START OFFSET_STOP threshold=NS" \
       > /sys/kernel/tracing/rv/monitors/tlob/monitor

Two uprobes delimit the measured section without modifying the target binary. Multiple uprobe pairs can be active simultaneously. A task may repeat the START/STOP sequence any number of times: the entry parks in the automaton's "stopped" state and later restarts in place, reusing the same pool slot (see Patch 6).

Series structure
----------------
Patch 1: rv: Introduce DA_MON_ALLOCATION_STRATEGY
Compile-time allocation strategy selector for per-object DA storage:
DA_ALLOC_AUTO (default kmalloc), DA_ALLOC_POOL (pre-allocated mempool), DA_ALLOC_MANUAL (caller-managed). DA_MON_POOL_SIZE alone selects pool mode.

Patch 2: rv: Add generic uprobe infrastructure for RV monitors
Thin wrapper over uprobe_consumer for path resolution, registration, and safe synchronous teardown. struct rv_uprobe embeds uprobe_consumer directly and holds the probed binary's path for its lifetime, avoiding a separate heap allocation per probe.

Patch 3: rv: Add tlob model DOT file
Graphviz DOT specification of the tlob hybrid automaton.

Patch 4: rv: Fix ha_invariant_passed_ns silent bypass of invariant check
Without this fix, ha_invariant_passed_ns() returns 0 on first call and leaves env_store at U64_MAX. Every subsequent state transition resets the hrtimer to the full budget instead of the remaining time, so the budget never expires for tasks that transition between states. This causes tlob's detail_sleeping and detail_waiting selftests to hang.

This patch is a minimal fix in the current dual-representation framework. Nam's series [1] refactors ha_monitor.h to a single-representation model; once it lands, tlob will need to be updated to the new API and this patch will be superseded by Gabriele's planned framework-level initialisation improvement. We carry it here to keep the series self-contained and testable.

[1] https://lore.kernel.org/lkml/08188c28f274da63a3f8549add3086a92aef45e5.1780908661.git.namcao@linutronix.de

Patch 5: rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable
#ifndef guards for da_monitor_reset_hook and EVENT_NONE_LBL.

Patch 6: rv: Add tlob hybrid automaton monitor
Main tlob implementation, including the parked-window redesign described below.

Patches 7-9: Tests
KUnit tests for the uprobe-line parser; eight ftrace-style selftests for tlob; the ftracetest walk-up change needed to run them from the verification subdirectory.

Changes since v4
----------------
Series-wide:
- Subject tags normalized to a single "rv:" prefix (was rv/da:, rv/ha:, rv/tlob:) with summary words capitalized, matching the trace subsystem style
- Comments tightened throughout all patches: narrative prose and duplicated explanations removed, concise technical comments kept
- The ftracetest walk-up change (previously folded into the selftests patch) is split out as its own final patch, per Gabriele's suggestion; the series now has 9 patches

Patch 1 (DA storage):
- Pool storage redesigned from the lock-free llist stack to a pre-allocated mempool: mempool_init_kmalloc_pool() pre-allocates all slots at init and mempool_alloc_preallocated() pops one without touching the allocator, bounding start latency and returning -ENOSPC when exhausted; mempool_free() is safe from RCU-callback context
- da_extra_cleanup() hook added so per-object monitors can release pool-backed storage; the pool is torn down only after rcu_barrier()

Patch 2 (uprobe):
- struct rv_uprobe keeps a struct path (not an inode pointer) to the probed binary; the path is held from registration until after the sync barrier and released with path_put(), since uprobe_register() takes no inode reference of its own

Patch 6 (tlob): parked-window redesign
- The automaton gains a "stopped" state (see tlob.dot): ending a window (STOP uprobe, budget-timer expiry) no longer frees the entry; it stays parked in the hash table, and repeated START/STOP of the same region restarts in place, reusing the pool slot, hash entry and task_struct reference (no allocation, no get_task_struct, no hash churn)
- ws->stopping is now per-window (cleared on restart) while final teardown is claimed per-task through ws->destroying; the monitor disable path switches its claim to destroying as well
- Per-binding started_list plus tlob_unbind_reap(): removing a binding destroys tasks currently parked under it and detaches active ones, closing the leak where parked tasks lingered until exit
- Pool slots fenced at TLOB_MAX_MONITORED and backed by the mempool; fresh starts past the cap return -ENOSPC
- __tlob_acc() gates on stopping so scheduler events never reach a parked task (hence no "stopped" self-loops in the model)
- tlob_stop_task() now takes the firing binding and rejects (-EALREADY) a STOP that does not own the active window, so nested monitored regions cannot truncate a window mid-flight; task-exit cleanup passes NULL to skip the check, symmetric with the restart check

Patch 8 (selftests):
- uprobe_restart.tc added: drives the target through START/STOP twice in a loop, checking the second start restarts the parked window, plus an unbind-while-parked scenario leaving no stale state
- Test helpers moved out of test.d/tlob/ into the verification test root as tlob_sym.c / tlob_target.c; the per-directory Makefile is no longer needed
- The verification Makefile now follows the standard lib.mk selftest layout (TEST_GEN_FILES := tlob_sym tlob_target, export VERIFICATIONTEST_BINDIR := $(OUTPUT)); the original empty all: target is kept, so the diff over the base Makefile is exactly those two lines. tlob_sym resolves ELF symbols in C, so the tests run without any binutils dependency.

Testing
-------
Environment: x86_64, CONFIG_PREEMPT_RT=y, kernel built with virtme-ng (vng --build), selftests built on the host and run inside the VM:
- KUnit test suite: tlob uprobe-line parser tests
- 8 ftrace-style selftests: uprobe_bind, uprobe_violation, uprobe_no_event, uprobe_multi, uprobe_detail_{running,sleeping, waiting}, uprobe_restart
Both suites pass. The pre-existing rv_* tests in the same directory keep passing as well.

v4: https://lore.kernel.org/all/[email protected]/
v3: https://lore.kernel.org/all/[email protected]/
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/

Wen Yang (9):
rv: Introduce DA_MON_ALLOCATION_STRATEGY
rv: Add generic uprobe infrastructure for RV monitors
rv: Add tlob model DOT file
rv: Fix ha_invariant_passed_ns silent bypass of invariant check
rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable
rv: Add tlob hybrid automaton monitor
rv: Add KUnit tests for the tlob monitor selftests/verification: Add tlob selftests selftests/ftrace: Walk up to find test.d/functions when a subdirectory
is passed

 Documentation/trace/rv/index.rst              |    1 +
 Documentation/trace/rv/monitor_tlob.rst       |  194 +++
 include/rv/da_monitor.h                       |  140 +-
 include/rv/ha_monitor.h                       |   13 +-
 include/rv/rv_uprobe.h                        |   90 ++
 kernel/trace/rv/Kconfig                       |    5 +
 kernel/trace/rv/Makefile                      |    3 +
 kernel/trace/rv/monitors/tlob/.kunitconfig    |    5 +
 kernel/trace/rv/monitors/tlob/Kconfig         |   22 +
 kernel/trace/rv/monitors/tlob/tlob.c          | 1134 +++++++++++++++++
 kernel/trace/rv/monitors/tlob/tlob.h          |  155 +++
 kernel/trace/rv/monitors/tlob/tlob_kunit.c    |  139 ++
 kernel/trace/rv/monitors/tlob/tlob_trace.h    |   48 +
 kernel/trace/rv/rv_trace.h                    |    1 +
 kernel/trace/rv/rv_uprobe.c                   |   91 ++
 tools/testing/selftests/ftrace/ftracetest     |   17 +-
 .../testing/selftests/verification/.gitignore |    2 +
 tools/testing/selftests/verification/Makefile |    4 +
 .../test.d/tlob/run_tlob_tests.sh             |   20 +
 .../verification/test.d/tlob/uprobe_bind.tc   |   35 +
 .../test.d/tlob/uprobe_detail_running.tc      |   49 +
 .../test.d/tlob/uprobe_detail_sleeping.tc     |   48 +
 .../test.d/tlob/uprobe_detail_waiting.tc      |   68 +
 .../verification/test.d/tlob/uprobe_multi.tc  |   59 +
 .../test.d/tlob/uprobe_no_event.tc            |   17 +
 .../test.d/tlob/uprobe_restart.tc             |   75 ++
 .../test.d/tlob/uprobe_violation.tc           |   65 +
 .../testing/selftests/verification/tlob_sym.c |  209 +++
 .../selftests/verification/tlob_target.c      |  138 ++
 tools/verification/models/tlob.dot            |   25 +
 30 files changed, 2860 insertions(+), 12 deletions(-)
 create mode 100644 Documentation/trace/rv/monitor_tlob.rst
 create mode 100644 include/rv/rv_uprobe.h
 create mode 100644 kernel/trace/rv/monitors/tlob/.kunitconfig
 create mode 100644 kernel/trace/rv/monitors/tlob/Kconfig
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob.c
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob.h
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.c
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_trace.h
 create mode 100644 kernel/trace/rv/rv_uprobe.c
 create mode 100755 tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_running.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_sleeping.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_waiting.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc
 create mode 100644 tools/testing/selftests/verification/tlob_sym.c
 create mode 100644 tools/testing/selftests/verification/tlob_target.c
 create mode 100644 tools/verification/models/tlob.dot

base-commit: 785095112f4198de49760552374f364043c8dbdf

Signature · 1 line
2.25.1
#1 lore raw

PATCH v5 1/9 Introduce DA_MON_ALLOCATION_STRATEGY

From: Wen Yang <[email protected]>

Per-object DA storage allocation is currently limited to kmalloc on demand. Add a compile-time selector so monitors can choose among three strategies:

DA_ALLOC_AUTO (default) - kmalloc per object on the monitor path
DA_ALLOC_POOL - pre-allocated fixed-size mempool;
selected by defining DA_MON_POOL_SIZE
DA_ALLOC_MANUAL - caller pre-inserts storage; framework
only links the target field

---
 include/rv/da_monitor.h | 140 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 133 insertions(+), 7 deletions(-)
include/rv/da_monitor.h +133 -7
index e3cf85c9ce55..48c534324cbb 100644
@@ -14,7 +14,50 @@
 #ifndef _RV_DA_MONITOR_H
 #define _RV_DA_MONITOR_H
 
+/*
+ * Allocation strategies for RV_MON_PER_OBJ monitors, selected by defining
+ * one of the following before including this header (never both):
+ *
+ *   DA_MON_POOL_SIZE N           - pool mode, N pre-allocated slots
+ *   DA_MON_ALLOCATION_STRATEGY   - explicit strategy (see below)
+ *   (neither)                    - auto mode (default)
+ *
+ * DA_ALLOC_AUTO   - kmalloc on demand; unbounded.
+ * DA_ALLOC_POOL   - pre-allocated fixed-size pool.
+ * DA_ALLOC_MANUAL - caller pre-inserts storage; framework links the target.
+ */
+#define DA_ALLOC_AUTO   0
+#define DA_ALLOC_POOL   1
+#define DA_ALLOC_MANUAL 2
+
+#ifdef DA_MON_POOL_SIZE
+#ifdef DA_MON_ALLOCATION_STRATEGY
+#error "Define only one of DA_MON_POOL_SIZE or DA_MON_ALLOCATION_STRATEGY"
+#endif
+#if DA_MON_POOL_SIZE == 0
+#error "DA_MON_POOL_SIZE must be non-zero"
+#endif
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_POOL
+#endif /* DA_MON_POOL_SIZE */
+
+#ifndef DA_MON_ALLOCATION_STRATEGY
+#ifdef DA_SKIP_AUTO_ALLOC
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_MANUAL
+#else
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_AUTO
+#endif
+#endif /* DA_MON_ALLOCATION_STRATEGY */
+
+/* Zero default keeps pool-mode conditionals compile-time constant. */
+#ifndef DA_MON_POOL_SIZE
+#if DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL
+#error "DA_ALLOC_POOL requires DA_MON_POOL_SIZE to be defined and non-zero"
+#endif
+#define DA_MON_POOL_SIZE 0
+#endif /* DA_MON_POOL_SIZE */
+
 #include <rv/automata.h>
+#include <linux/mempool.h>
 #include <linux/rv.h>
 #include <rv/kunit.h>
 #include <linux/stringify.h>
@@ -67,6 +110,16 @@ static struct rv_monitor rv_this;
 #define da_monitor_sync_hook()
 #endif
 
+/*
+ * Per-object teardown hook, called after da_monitor_reset_all() +
+ * da_monitor_sync_hook() and before hash_del_rcu() for each entry.
+ * All HA timer callbacks have completed at this point.
+ * Define before including this header.  Default: no-op.
+ */
+#ifndef da_extra_cleanup
+#define da_extra_cleanup(da_mon)
+#endif
+
 /*
  * Type for the target id, default to int but can be overridden.
  * A long type can work as hash table key (PER_OBJ) but will be downgraded to
@@ -543,6 +596,59 @@ static inline monitor_target da_get_target_by_id(da_id_type id)
 	return mon_storage->target;
 }
 
+/*
+ * Pre-allocated mempool for DA_ALLOC_POOL monitors: DA_MON_POOL_SIZE
+ * slots, eager-allocated at init.  mempool_alloc_preallocated() pops a
+ * slot without touching the allocator (bounded start latency; NULL when
+ * exhausted).  mempool_free() is safe from RCU-callback context.
+ * Non-pool monitors get a zero-initialised mempool_t; pool paths compile
+ * away.
+ */
+static mempool_t da_monitor_pool;
+
+static void da_pool_return_cb(struct rcu_head *head)
+{
+	struct da_monitor_storage *ms =
+		container_of(head, struct da_monitor_storage, rcu);
+
+	mempool_free(ms, &da_monitor_pool);
+}
+
+/*
+ * da_create_pool_storage - pop a free pool slot and insert it into the hash.
+ *
+ * Returns the new da_monitor, or NULL if the pool is exhausted.  Finding
+ * an existing entry for the same id fires WARN_ON_ONCE (double-start bug).
+ *
+ * Caller must hold an RCU read-side CS and the monitor's serialisation lock.
+ */
+static inline struct da_monitor *
+da_create_pool_storage(da_id_type id, monitor_target target,
+		       struct da_monitor *da_mon)
+{
+	struct da_monitor_storage *mon_storage, *existing;
+
+	if (da_mon)
+		return da_mon;
+
+	mon_storage = mempool_alloc_preallocated(&da_monitor_pool);
+	if (!mon_storage)
+		return NULL;
+	memset(mon_storage, 0, sizeof(*mon_storage));
+
+	mon_storage->id = id;
+	mon_storage->target = target;
+
+	/* Single consumer under the caller's lock; duplicate is a double-start bug. */
+	existing = __da_get_mon_storage(id);
+	if (WARN_ON_ONCE(existing)) {
+		mempool_free(mon_storage, &da_monitor_pool);
+		return NULL;
+	}
+	hash_add_rcu(da_monitor_ht, &mon_storage->node, id);
+	return &mon_storage->rv.da_mon;
+}
+
 /*
  * da_destroy_storage - destroy the per-object storage
  *
@@ -564,7 +670,10 @@ static inline void da_destroy_storage(da_id_type id)
 		return;
 	da_monitor_reset_hook(&mon_storage->rv.da_mon);
 	hash_del_rcu(&mon_storage->node);
-	kfree_rcu(mon_storage, rcu);
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+		call_rcu(&mon_storage->rcu, da_pool_return_cb);
+	else
+		kfree_rcu(mon_storage, rcu);
 }
 
 static void __da_monitor_reset_all(void (*reset)(struct da_monitor *))
@@ -590,6 +699,9 @@ static inline void da_monitor_reset_state_all(void)
 static inline int da_monitor_init(void)
 {
 	hash_init(da_monitor_ht);
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+		return mempool_init_kmalloc_pool(&da_monitor_pool, DA_MON_POOL_SIZE,
+						 sizeof(struct da_monitor_storage));
 	return 0;
 }
 
@@ -607,8 +719,17 @@ static inline void da_monitor_destroy(void)
 	 * pending, we can safely assume no concurrent user.
 	 */
 	hash_for_each_safe(da_monitor_ht, bkt, tmp, mon_storage, node) {
+		da_extra_cleanup(&mon_storage->rv.da_mon);
 		hash_del_rcu(&mon_storage->node);
-		kfree(mon_storage);
+		if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+			mempool_free(mon_storage, &da_monitor_pool);
+		else
+			kfree(mon_storage);
+	}
+
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL) {
+		rcu_barrier();
+		mempool_exit(&da_monitor_pool);
 	}
 }
 
@@ -617,11 +738,16 @@ static inline void da_monitor_destroy(void)
  * start condition is in a context problematic for allocation (e.g. scheduling).
  * In such case, if the storage was pre-allocated without a target, set it now.
  */
-#ifdef DA_SKIP_AUTO_ALLOC
-#define da_prepare_storage da_fill_empty_storage
-#else
-#define da_prepare_storage da_create_storage
-#endif /* DA_SKIP_AUTO_ALLOC */
+static inline struct da_monitor *
+da_prepare_storage(da_id_type id, monitor_target target,
+		   struct da_monitor *da_mon)
+{
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+		return da_create_pool_storage(id, target, da_mon);
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_MANUAL)
+		return da_fill_empty_storage(id, target, da_mon);
+	return da_create_storage(id, target, da_mon);
+}
 
 #endif /* RV_MON_TYPE */
 
Signature · 1 line
2.25.1
#3 lore raw

PATCH v5 2/9 Add generic uprobe infrastructure for RV monitors

In reply to [email protected]

From: Wen Yang <[email protected]>

Monitors that instrument user-space function boundaries need to resolve paths, register uprobes, and deregister them safely. Provide a thin wrapper so monitors share a single implementation of this boilerplate.

struct rv_uprobe embeds struct uprobe_consumer directly, avoiding a separate heap allocation per probe. The struct holds a struct path for the probed binary so that the inode and its mount remain referenced for the full uprobe lifetime; uprobe_register() does not take its own reference to the inode. The path is released in rv_uprobe_unregister_nosync() after the consumer has been removed.

rv_uprobe_sync() calls uprobe_unregister_sync() which performs synchronize_rcu_tasks_trace(), waiting for all rcu_read_lock_trace() readers (handler_chain()) to complete on all CPUs before returning; the caller may then free the containing struct.

The API provides register, synchronous and nosync unregister, a global handler barrier (rv_uprobe_sync), and an active-state predicate.

---
 include/rv/rv_uprobe.h      | 90 ++++++++++++++++++++++++++++++++++++
 kernel/trace/rv/rv_uprobe.c | 91 +++++++++++++++++++++++++++++++++++++
 2 files changed, 181 insertions(+)
 create mode 100644 include/rv/rv_uprobe.h
 create mode 100644 kernel/trace/rv/rv_uprobe.c
include/rv/rv_uprobe.h new file +90 -0
new file mode 100644
index 000000000000..d0a9079ac5be
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Wen Yang <[email protected]> */
+/*
+ * Generic uprobe infrastructure for RV monitors.
+ *
+ */
+
+#ifndef _RV_UPROBE_H
+#define _RV_UPROBE_H
+
+#include <linux/path.h>
+#include <linux/types.h>
+#include <linux/uprobes.h>
+
+struct pt_regs;
+
+/**
+ * struct rv_uprobe - embeddable uprobe handle for RV monitors
+ *
+ * Embed via DECLARE_RV_UPROBE() and pass &name to rv_uprobe_register().
+ * The caller may free the containing struct after rv_uprobe_unregister()
+ * (or rv_uprobe_unregister_nosync() + rv_uprobe_sync()) returns.
+ *
+ * @uc:     embedded uprobe_consumer; set handler/ret_handler before registering
+ * @uprobe: registered uprobe pointer (NULL when not registered)
+ * @path:   path of the probed binary, held until unregistration
+ */
+struct rv_uprobe {
+	struct uprobe_consumer	uc;
+	struct uprobe		*uprobe;
+	struct path		path;
+};
+
+/* Embed a named rv_uprobe inside a caller struct */
+#define DECLARE_RV_UPROBE(name)		struct rv_uprobe name
+
+/**
+ * rv_uprobe_is_registered - test whether an uprobe is currently active
+ * @p: probe to test; may be NULL
+ */
+bool rv_uprobe_is_registered(const struct rv_uprobe *p);
+
+/**
+ * rv_uprobe_register - initialise and register an uprobe
+ * @binpath: absolute path to the target binary
+ * @offset:  byte offset within the binary
+ * @p:       caller-provided rv_uprobe (embedded via DECLARE_RV_UPROBE);
+ *           p->uc.handler and/or p->uc.ret_handler must be set before this call
+ *
+ * Resolves the path and registers p->uc with the uprobe subsystem.
+ * No heap allocation is performed.
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+int rv_uprobe_register(const char *binpath, loff_t offset, struct rv_uprobe *p);
+
+/**
+ * rv_uprobe_unregister - synchronously unregister a uprobe
+ * @p: probe to unregister; may be NULL (no-op)
+ *
+ * Removes the consumer from the uprobe subsystem and waits for all in-flight
+ * handlers to complete (via synchronize_rcu_tasks_trace()).  After this
+ * returns, the containing struct may be safely freed by the caller.
+ * Use rv_uprobe_unregister_nosync() + rv_uprobe_sync() to batch multiple
+ * deregistrations before a single synchronisation.
+ */
+void rv_uprobe_unregister(struct rv_uprobe *p);
+
+/**
+ * rv_uprobe_unregister_nosync - dequeue an uprobe without waiting
+ * @p: probe to dequeue; may be NULL (no-op)
+ *
+ * Removes the consumer without waiting for in-flight handlers.  The path
+ * (p->path) is NOT released here; the caller must call rv_uprobe_sync()
+ * followed by path_put(&p->path) before freeing the containing struct.
+ * Use rv_uprobe_unregister() to handle both in one step.
+ */
+void rv_uprobe_unregister_nosync(struct rv_uprobe *p);
+
+/**
+ * rv_uprobe_sync - wait for all in-flight uprobe handlers to complete
+ *
+ * Global barrier: calls uprobe_unregister_sync(), which runs
+ * synchronize_rcu_tasks_trace() and synchronize_srcu(&uretprobes_srcu).
+ * After this returns, no handler_chain() iteration referencing any
+ * previously deregistered consumer is still in progress.
+ */
+void rv_uprobe_sync(void);
+
+#endif /* _RV_UPROBE_H */
kernel/trace/rv/rv_uprobe.c new file +91 -0

91 lines not on this page · raw message

Signature · 1 line
2.25.1
#5 lore raw

PATCH v5 3/9 Add tlob model DOT file

In reply to [email protected]

From: Wen Yang <[email protected]>

Add the Graphviz DOT specification of the tlob hybrid automaton to tools/verification/models/. The model has three states (running, waiting, sleeping), five transitions (switch_in, preempt, wakeup, sleep), and a single clock invariant clk_elapsed < BUDGET_NS() active in all states.

---
 tools/verification/models/tlob.dot | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)
 create mode 100644 tools/verification/models/tlob.dot
tools/verification/models/tlob.dot new file +25 -0
new file mode 100644
index 000000000000..7f6f09c3d7cf
@@ -0,0 +1,25 @@
+digraph state_automaton {
+	center = true;
+	size = "7,11";
+	{node [shape = plaintext, style=invis, label=""] "__init_stopped"};
+	{node [shape = ellipse] "running"};
+	{node [shape = plaintext] "running"};
+	{node [shape = plaintext] "waiting"};
+	{node [shape = plaintext] "sleeping"};
+	{node [shape = plaintext] "stopped"};
+	"__init_stopped" -> "stopped";
+	"running"  [label = "running\nclk_elapsed < BUDGET_NS()",  color = green3];
+	"waiting"  [label = "waiting\nclk_elapsed < BUDGET_NS()"];
+	"sleeping" [label = "sleeping\nclk_elapsed < BUDGET_NS()"];
+	"stopped"  [label = "stopped"];
+	"running"  -> "sleeping" [ label = "sleep" ];
+	"running"  -> "waiting"  [ label = "preempt" ];
+	"waiting"  -> "running"  [ label = "switch_in" ];
+	"sleeping" -> "waiting"  [ label = "wakeup" ];
+	"running"  -> "stopped"  [ label = "stop" ];
+	"stopped"  -> "running"  [ label = "start;reset(clk_elapsed)" ];
+	{ rank = min ;
+		"__init_stopped";
+		"stopped";
+	}
+}
Signature · 1 line
2.25.1
#7 lore raw

PATCH v5 4/9 Fix ha_invariant_passed_ns silent bypass of invariant check

In reply to [email protected]

From: Wen Yang <[email protected]>

When env_store is U64_MAX (its initial sentinel value), ha_invariant_passed_ns() returns 0 immediately without initializing env_store to the current clock. Subsequent calls to ha_check_invariant_ns() then find env_store still at U64_MAX, causing the elapsed comparison to wrap and always report the invariant as satisfied, silently masking any violations.

Fix by calling ha_reset_clk_ns() to establish the guard on the first invocation instead of returning early. Apply the same fix to ha_invariant_passed_jiffy().

This is a stopgap: once the RV framework reworks the per-env clock guard, this first-invocation reset should be subsumed.

---
 include/rv/ha_monitor.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
include/rv/ha_monitor.h +3 -2
index 6e1c7fe5449a..e1738d199b28 100644
@@ -355,7 +355,7 @@ static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs en
 	if (env < 0 || env >= ENV_MAX_STORED)
 		return 0;
 	if (ha_monitor_env_invalid(ha_mon, env))
-		return 0;
+		ha_reset_clk_ns(ha_mon, env, time_ns);
 	return ha_get_env(ha_mon, env, time_ns);
 }
 
@@ -375,6 +375,7 @@ static inline bool ha_check_invariant_jiffy(struct ha_monitor *ha_mon, enum envs
 {
 	return time_after64(READ_ONCE(ha_mon->env_store[env]), get_jiffies_64() - expire_jiffy);
 }
+
 /*
  * ha_invariant_passed_jiffy - prepare the invariant and return the time since reset
  */
@@ -383,7 +384,7 @@ static inline u64 ha_invariant_passed_jiffy(struct ha_monitor *ha_mon, enum envs
 	if (env < 0 || env >= ENV_MAX_STORED)
 		return 0;
 	if (ha_monitor_env_invalid(ha_mon, env))
-		return 0;
+		ha_reset_clk_jiffy(ha_mon, env);
 	return ha_get_env(ha_mon, env, time_ns);
 }
 
Signature · 1 line
2.25.1
#9 lore raw

PATCH v5 5/9 Make da_monitor_reset_hook and EVENT_NONE_LBL overridable

In reply to [email protected]

From: Wen Yang <[email protected]>

Wrap both definitions with #ifndef guards so HA-based monitors can substitute their own implementations before including this header. tlob uses this to define a reset hook that cancels per-task hrtimers on monitor teardown.

Overrides must still call ha_monitor_reset_env() or cancel outstanding timers to avoid timer UAF.

No behaviour change for monitors that do not override either macro.

---
 include/rv/ha_monitor.h | 8 ++++++++
 1 file changed, 8 insertions(+)
include/rv/ha_monitor.h +8 -0
index e1738d199b28..807b981eb548 100644
@@ -36,8 +36,14 @@ static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
 					 da_id_type id);
 #define da_monitor_event_hook ha_monitor_handle_constraint
 #define da_monitor_init_hook ha_monitor_init_env
+
+/* Overrides must still call ha_monitor_reset_env() or cancel the timer. */
+#ifndef da_monitor_reset_hook
 #define da_monitor_reset_hook ha_monitor_reset_env
+#endif
+#ifndef da_monitor_sync_hook
 #define da_monitor_sync_hook() synchronize_rcu()
+#endif
 
 #if !defined(HA_SKIP_AUTO_CLEANUP) && RV_MON_TYPE == RV_MON_PER_TASK
 /*
@@ -75,7 +81,9 @@ _Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
 #define ENV_INVALID_VALUE U64_MAX
 /* Error with no event occurs only on timeouts */
 #define EVENT_NONE EVENT_MAX
+#ifndef EVENT_NONE_LBL
 #define EVENT_NONE_LBL "none"
+#endif
 #define ENV_BUFFER_SIZE 64
 
 #ifdef CONFIG_RV_REACTORS
Signature · 1 line
2.25.1
#11 lore raw

PATCH v5 6/9 Add tlob hybrid automaton monitor

In reply to [email protected]

From: Wen Yang <[email protected]>

tlob (task latency over budget) is a per-task hybrid automaton RV monitor that tracks wall-clock time across a user-delimited code section and emits an error when elapsed time exceeds a configurable budget.

Four-state automaton (running, waiting, sleeping, stopped) driven by sched_switch/sched_wakeup tracepoints and a user-visible start/stop
pair: "stop" only fires from running and parks the window in stopped, where a later "start" restarts it in place (same pool slot, no re-registration); both callers of stop run while the task is on CPU. A single clk_elapsed < BUDGET_NS() invariant is enforced by a per-task HRTIMER_MODE_REL_HARD timer; on expiry the monitor records a per-state breakdown (running_ns, waiting_ns, sleeping_ns) before emitting error_env_tlob.

Uprobe pairs are registered through a tracefs monitor file as "p PATH:OFFSET_START OFFSET_STOP threshold=NS". A pre-allocated mempool hard-caps concurrently monitored tasks at TLOB_MAX_MONITORED (past it, fresh starts return -ENOSPC) with allocation-free start/stop on the uprobe hot path.

---
 Documentation/trace/rv/index.rst           |    1 +
 Documentation/trace/rv/monitor_tlob.rst    |  194 ++++
 kernel/trace/rv/Kconfig                    |    5 +
 kernel/trace/rv/Makefile                   |    2 +
 kernel/trace/rv/monitors/tlob/Kconfig      |   12 +
 kernel/trace/rv/monitors/tlob/tlob.c       | 1132 ++++++++++++++++++++
 kernel/trace/rv/monitors/tlob/tlob.h       |  149 +++
 kernel/trace/rv/monitors/tlob/tlob_trace.h |   48 +
 kernel/trace/rv/rv_trace.h                 |    1 +
 9 files changed, 1544 insertions(+)
 create mode 100644 Documentation/trace/rv/monitor_tlob.rst
 create mode 100644 kernel/trace/rv/monitors/tlob/Kconfig
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob.c
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob.h
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_trace.h
Documentation/trace/rv/index.rst +1 -0
index 29769f06bb0f..1501545b5f08 100644
@@ -16,5 +16,6 @@ Runtime Verification
    monitor_wwnr.rst
    monitor_sched.rst
    monitor_rtapp.rst
+   monitor_tlob.rst
    monitor_stall.rst
    monitor_deadline.rst
Documentation/trace/rv/monitor_tlob.rst new file +194 -0

194 lines not on this page · raw message

kernel/trace/rv/Kconfig +5 -0
index efa930f94ea4..222b3bea8079 100644
@@ -84,8 +84,13 @@ source "kernel/trace/rv/monitors/deadline/Kconfig"
 source "kernel/trace/rv/monitors/nomiss/Kconfig"
 # Add new deadline monitors here
 
+source "kernel/trace/rv/monitors/tlob/Kconfig"
 # Add new monitors here
 
+config RV_UPROBE
+	bool
+	depends on RV && UPROBES
+
 config RV_REACTORS
 	bool "Runtime verification reactors"
 	default y
kernel/trace/rv/Makefile +2 -0
index cdbf68c84f5a..cd0ec11f0e05 100644
@@ -21,7 +21,9 @@ obj-$(CONFIG_RV_MON_STALL) += monitors/stall/stall.o
 obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o
 obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
 obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o
+obj-$(CONFIG_RV_MON_TLOB) += monitors/tlob/tlob.o
 # Add new monitors here
+obj-$(CONFIG_RV_UPROBE) += rv_uprobe.o
 obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
 obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
 obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
kernel/trace/rv/monitors/tlob/Kconfig new file +12 -0
new file mode 100644
index 000000000000..aa43382073d2
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TLOB
+	bool "tlob monitor"
+	depends on RV && UPROBES && HIGH_RES_TIMERS
+	select HA_MON_EVENTS_ID
+	select RV_UPROBE
+	help
+	  Enable the tlob (task latency over budget) hybrid-automaton RV
+	  monitor.  tlob tracks per-task elapsed wall-clock time across a
+	  user-delimited code section and emits error_env_tlob when the
+	  elapsed time exceeds a configurable per-invocation budget.
kernel/trace/rv/monitors/tlob/tlob.c new file +1132 -0

1,132 lines not on this page · raw message

kernel/trace/rv/monitors/tlob/tlob.h new file +149 -0

149 lines not on this page · raw message

kernel/trace/rv/monitors/tlob/tlob_trace.h new file +48 -0

48 lines not on this page · raw message

kernel/trace/rv/rv_trace.h +1 -0
index 2f8a932432c9..4bfa39717cef 100644
@@ -189,6 +189,7 @@ DECLARE_EVENT_CLASS(error_env_da_monitor_id,
 
 #include <monitors/stall/stall_trace.h>
 #include <monitors/nomiss/nomiss_trace.h>
+#include <monitors/tlob/tlob_trace.h>
 // Add new monitors based on CONFIG_HA_MON_EVENTS_ID here
 
 #endif
Signature · 1 line
2.25.1
#13 lore raw

PATCH v5 7/9 Add KUnit tests for the tlob monitor

In reply to [email protected]

From: Wen Yang <[email protected]>

Add CONFIG_TLOB_KUNIT_TEST (tristate, depends on RV_MON_TLOB && KUNIT, default KUNIT_ALL_TESTS) with a test suite covering the uprobe-line parser.

Tests call tlob_parse_uprobe_line() and tlob_parse_remove_line() directly rather than going through the top-level write handler. Mark both functions VISIBLE_IF_KUNIT and export with EXPORT_SYMBOL_IF_KUNIT. Update the IS_ENABLED guard in tlob.h to use CONFIG_TLOB_KUNIT_TEST instead of CONFIG_KUNIT.

---
 kernel/trace/rv/Makefile                   |   1 +
 kernel/trace/rv/monitors/tlob/.kunitconfig |   5 +
 kernel/trace/rv/monitors/tlob/Kconfig      |  10 ++
 kernel/trace/rv/monitors/tlob/tlob.c       |   6 +-
 kernel/trace/rv/monitors/tlob/tlob.h       |   6 +
 kernel/trace/rv/monitors/tlob/tlob_kunit.c | 139 +++++++++++++++++++++
 6 files changed, 165 insertions(+), 2 deletions(-)
 create mode 100644 kernel/trace/rv/monitors/tlob/.kunitconfig
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.c
kernel/trace/rv/Makefile +1 -0
index cd0ec11f0e05..92a04367ce93 100644
@@ -22,6 +22,7 @@ obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o
 obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
 obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o
 obj-$(CONFIG_RV_MON_TLOB) += monitors/tlob/tlob.o
+obj-$(CONFIG_TLOB_KUNIT_TEST) += monitors/tlob/tlob_kunit.o
 # Add new monitors here
 obj-$(CONFIG_RV_UPROBE) += rv_uprobe.o
 obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
kernel/trace/rv/monitors/tlob/.kunitconfig new file +5 -0
new file mode 100644
index 000000000000..82a6f121016e
@@ -0,0 +1,5 @@
+CONFIG_KUNIT=y
+CONFIG_UPROBES=y
+CONFIG_RV=y
+CONFIG_RV_MON_TLOB=y
+CONFIG_TLOB_KUNIT_TEST=y
kernel/trace/rv/monitors/tlob/Kconfig +10 -0
index aa43382073d2..f4fce35d38fb 100644
@@ -10,3 +10,13 @@ config RV_MON_TLOB
 	  monitor.  tlob tracks per-task elapsed wall-clock time across a
 	  user-delimited code section and emits error_env_tlob when the
 	  elapsed time exceeds a configurable per-invocation budget.
+
+config TLOB_KUNIT_TEST
+	tristate "KUnit tests for tlob monitor" if !KUNIT_ALL_TESTS
+	depends on RV_MON_TLOB && KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  Enable KUnit unit tests for the tlob RV monitor.  The tests
+	  cover the uprobe-line parser (tlob_parse_uprobe_line) and the
+	  remove-line parser (tlob_parse_remove_line), verifying valid,
+	  invalid, and out-of-range inputs without requiring a running kernel.
kernel/trace/rv/monitors/tlob/tlob.c +4 -2
index 99acd34726f1..e109390ba3ad 100644
@@ -874,7 +874,7 @@ static ssize_t tlob_monitor_read(struct file *file,
  * PATH may contain ':'; the last ':' separates path from offset.
  * Returns 0, -EINVAL, or -ERANGE.
  */
-static int tlob_parse_uprobe_line(char *buf, u64 *thr_out,
+VISIBLE_IF_KUNIT int tlob_parse_uprobe_line(char *buf, u64 *thr_out,
 					    char **path_out,
 					    loff_t *start_out, loff_t *stop_out)
 {
@@ -948,11 +948,12 @@ static int tlob_parse_uprobe_line(char *buf, u64 *thr_out,
 	*stop_out  = (loff_t)stop_val;
 	return 0;
 }
+EXPORT_SYMBOL_IF_KUNIT(tlob_parse_uprobe_line);
 
 /*
  * Parse "-PATH:OFFSET_START" (ftrace uprobe_events removal convention).
  */
-static int tlob_parse_remove_line(char *buf, char **path_out,
+VISIBLE_IF_KUNIT int tlob_parse_remove_line(char *buf, char **path_out,
 					    loff_t *start_out)
 {
 	char *binpath, *colon;
@@ -976,6 +977,7 @@ static int tlob_parse_remove_line(char *buf, char **path_out,
 	*start_out = (loff_t)off;
 	return 0;
 }
+EXPORT_SYMBOL_IF_KUNIT(tlob_parse_remove_line);
 
 static int tlob_create_or_delete_uprobe(char *buf)
 {
kernel/trace/rv/monitors/tlob/tlob.h +6 -0
index 94e7382c2130..6ad9d5179ab6 100644
@@ -146,4 +146,10 @@ static const struct automaton_tlob automaton_tlob = {
 /* Upper budget bound (1 hour): keeps the u64 ns accumulators far from overflow. */
 #define TLOB_MAX_THRESHOLD_NS	3600000000000ULL
 
+#if IS_ENABLED(CONFIG_TLOB_KUNIT_TEST)
+int tlob_parse_uprobe_line(char *buf, u64 *thr_out, char **path_out,
+			   loff_t *start_out, loff_t *stop_out);
+int tlob_parse_remove_line(char *buf, char **path_out, loff_t *start_out);
+#endif /* CONFIG_TLOB_KUNIT_TEST */
+
 #endif /* _RV_TLOB_H */
kernel/trace/rv/monitors/tlob/tlob_kunit.c new file +139 -0

139 lines not on this page · raw message

Signature · 1 line
2.25.1
#15 lore raw

PATCH v5 8/9 selftests/verification: Add tlob selftests

In reply to [email protected]

From: Wen Yang <[email protected]>

Add seven ftrace-style test scripts for the tlob RV monitor under tools/testing/selftests/verification/test.d/tlob/. The tests cover uprobe binding management, budget violation detection, and per-state time accounting.

Helper binaries tlob_sym and tlob_target are placed in tools/testing/selftests/verification/ and built via TEST_GEN_FILES in the top-level Makefile, following the standard kselftests convention. VERIFICATIONTEST_BINDIR is exported after include ../lib.mk so OUTPUT is already populated by lib.mk when the variable is assigned.

run_tlob_tests.sh is a thin wrapper: it builds the helpers and delegates all argument handling to ftracetest via exec "$FTRACETEST" -K --rv "$@".

All .tc files that start background processes set up a trap teardown EXIT immediately after launch so background tasks are cleaned up even when a subsequent assertion fails under set -e.

---
 .../testing/selftests/verification/.gitignore |   2 +
 tools/testing/selftests/verification/Makefile |   4 +
 .../test.d/tlob/run_tlob_tests.sh             |  20 ++
 .../verification/test.d/tlob/uprobe_bind.tc   |  35 +++
 .../test.d/tlob/uprobe_detail_running.tc      |  49 ++++
 .../test.d/tlob/uprobe_detail_sleeping.tc     |  48 ++++
 .../test.d/tlob/uprobe_detail_waiting.tc      |  68 ++++++
 .../verification/test.d/tlob/uprobe_multi.tc  |  59 +++++
 .../test.d/tlob/uprobe_no_event.tc            |  17 ++
 .../test.d/tlob/uprobe_restart.tc             |  75 +++++++
 .../test.d/tlob/uprobe_violation.tc           |  65 ++++++
 .../testing/selftests/verification/tlob_sym.c | 209 ++++++++++++++++++
 .../selftests/verification/tlob_target.c      | 138 ++++++++++++
 13 files changed, 789 insertions(+)
 create mode 100755 tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_running.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_sleeping.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_waiting.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc
 create mode 100644 tools/testing/selftests/verification/tlob_sym.c
 create mode 100644 tools/testing/selftests/verification/tlob_target.c
tools/testing/selftests/verification/.gitignore +2 -0
index 2659417cb2c7..d2f231f1bacb 100644
@@ -1,2 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 logs
+tlob_sym
+tlob_target
tools/testing/selftests/verification/Makefile +4 -0
index aa8790c22a71..41445d15b86a 100644
@@ -5,4 +5,8 @@ TEST_PROGS := verificationtest-ktap
 TEST_FILES := test.d settings
 EXTRA_CLEAN := $(OUTPUT)/logs/*
 
+TEST_GEN_FILES := tlob_sym tlob_target
+
 include ../lib.mk
+
+export VERIFICATIONTEST_BINDIR := $(OUTPUT)
tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh new file +20 -0
new file mode 100755
index 000000000000..13adf1eec9a2
@@ -0,0 +1,20 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Standalone runner for tlob selftests
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+FTRACETEST="$SCRIPT_DIR/../../../ftrace/ftracetest"
+
+# Build test helpers
+echo "Building tlob test helpers..."
+make -C "$SCRIPT_DIR/../.." all
+
+# Export VERIFICATIONTEST_BINDIR so test scripts can find tlob_target and
+# tlob_sym (built in the verification directory)
+export VERIFICATIONTEST_BINDIR="$(realpath "$SCRIPT_DIR/../..")"
+
+# Forward all options to ftracetest
+exec "$FTRACETEST" -K --rv "$SCRIPT_DIR" "$@"
tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc new file +35 -0
new file mode 100644
index 000000000000..baa6c5fa0ff2
@@ -0,0 +1,35 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor uprobe binding (visible in monitor file, removable, duplicate rejected)
+# requires: tlob:monitor
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"
+TLOB_SYM="${VERIFICATIONTEST_BINDIR}/tlob_sym"
+TLOB_MONITOR=monitors/tlob/monitor
+
+busy_offset=$("$TLOB_SYM" sym_offset "$UPROBE_TARGET" tlob_busy_work 2>/dev/null)
+stop_offset=$("$TLOB_SYM" sym_offset "$UPROBE_TARGET" tlob_busy_work_done 2>/dev/null)
+
+"$UPROBE_TARGET" 30000 &
+busy_pid=$!
+teardown() {
+    kill "$busy_pid" 2>/dev/null || true; wait "$busy_pid" 2>/dev/null || true
+}
+trap teardown EXIT
+sleep 0.05
+
+echo 1 > monitors/tlob/enable
+echo "p ${UPROBE_TARGET}:${busy_offset} ${stop_offset} threshold=5000000000" > "$TLOB_MONITOR"
+
+# Binding must appear in monitor file with canonical hex-offset format.
+grep -qE "^p ${UPROBE_TARGET}:0x[0-9a-f]+ 0x[0-9a-f]+ threshold=[0-9]+$" "$TLOB_MONITOR"
+grep -q "threshold=5000000000" "$TLOB_MONITOR"
+
+# Duplicate offset_start must be rejected.
+! echo "p ${UPROBE_TARGET}:${busy_offset} ${stop_offset} threshold=9999000" > "$TLOB_MONITOR" 2>/dev/null || false
+
+# Remove the binding; it must no longer appear.
+echo "-${UPROBE_TARGET}:${busy_offset}" > "$TLOB_MONITOR"
+! grep -q "^p .*:0x${busy_offset#0x} " "$TLOB_MONITOR" || false
+
+echo 0 > monitors/tlob/enable
tools/testing/selftests/verification/test.d/tlob/uprobe_detail_running.tc new file +49 -0

49 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_detail_sleeping.tc new file +48 -0

48 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_detail_waiting.tc new file +68 -0

68 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc new file +59 -0

59 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc new file +17 -0
new file mode 100644
index 000000000000..30e78f2b1475
@@ -0,0 +1,17 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor no spurious events without active uprobe binding
+# requires: tlob:monitor
+
+echo 1 > /sys/kernel/tracing/events/rv/error_env_tlob/enable
+echo 1 > /sys/kernel/tracing/tracing_on
+echo 1 > monitors/tlob/enable
+echo > /sys/kernel/tracing/trace
+
+sleep 0.5
+
+! grep -q "error_env_tlob" /sys/kernel/tracing/trace || false
+
+echo 0 > monitors/tlob/enable
+echo 0 > /sys/kernel/tracing/events/rv/error_env_tlob/enable
+echo > /sys/kernel/tracing/trace
tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc new file +75 -0

75 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc new file +65 -0

65 lines not on this page · raw message

tools/testing/selftests/verification/tlob_sym.c new file +209 -0

209 lines not on this page · raw message

tools/testing/selftests/verification/tlob_target.c new file +138 -0

138 lines not on this page · raw message

Signature · 1 line
2.25.1
#17 lore raw

PATCH v5 9/9 selftests/ftrace: Walk up to find test.d/functions when a subdirectory is passed

In reply to [email protected]

From: Wen Yang <[email protected]>

When a test directory that does not itself contain test.d/functions is passed to ftracetest (e.g. verification/test.d/tlob/), ftracetest fell back to its own functions file and lost the rv-specific check_requires handling for ':monitor' and ':reactor' requirements.

Walk up the directory tree from OPT_TEST_DIR until a directory containing test.d/functions is found. This allows monitor subdirectories to be passed directly as the test root without placing a functions shim in each one.

The RV verification suite uses this so that

tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh

can pass test.d/tlob/ to ftracetest and have it source verification/test.d/functions (which understands ':monitor'/':reactor').

---
 tools/testing/selftests/ftrace/ftracetest | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)
tools/testing/selftests/ftrace/ftracetest +14 -3
index 0a56bf209f6c..3ba929820dd2 100755
@@ -159,9 +159,20 @@ parse_opts() { # opts
   if [ -n "$OPT_TEST_CASES" ]; then
     TEST_CASES=$OPT_TEST_CASES
   fi
-  if [ -n "$OPT_TEST_DIR" -a -f "$OPT_TEST_DIR"/test.d/functions ]; then
-    TOP_DIR=$OPT_TEST_DIR
-    TEST_DIR=$TOP_DIR/test.d
+  if [ -n "$OPT_TEST_DIR" ]; then
+    # Walk up from OPT_TEST_DIR to find the nearest ancestor that contains
+    # test.d/functions.  This allows a monitor subdirectory (e.g.
+    # verification/test.d/tlob/) to be passed directly without placing a
+    # dummy functions shim in each new subdirectory.
+    dir=$OPT_TEST_DIR
+    while [ "$dir" != "/" ]; do
+      if [ -f "$dir/test.d/functions" ]; then
+        TOP_DIR=$dir
+        TEST_DIR=$TOP_DIR/test.d
+        break
+      fi
+      dir=$(dirname "$dir")
+    done
   fi
 }
 
Signature · 1 line
2.25.1
#14 lore raw

Re: [PATCH v5 7/9] rv: Add KUnit tests for the tlob monitor

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Missing prototype declaration warning (-Wmissing-prototypes) when CONFIG_KUNIT is enabled but…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing prototype declaration warning (-Wmissing-prototypes) when CONFIG_KUNIT is enabled but CONFIG_TLOB_KUNIT_TEST is disabled. --

--- Patch [7]: [PATCH v5 7/9] rv: Add KUnit tests for the tlob monitor --- commit c187567361217a017982494c7c6e1a3021a25452
Author: Wen Yang <[email protected]>

rv: Add KUnit tests for the tlob monitor

This commit adds a KUnit test suite for the tlob monitor's uprobe-line parsers. It modifies tlob_parse_uprobe_line and tlob_parse_remove_line to be visible and exported when testing is enabled.

Quoted 12 lines
kernel/trace/rv/monitors/tlob/tlob.c +1 -1
index 99acd34726f10..e109390ba3ada 100644
@@ -874,7 +874,7 @@ static ssize_t tlob_monitor_read(struct file *file,
  * PATH may contain ':'; the last ':' separates path from offset.
  * Returns 0, -EINVAL, or -ERANGE.
  */
-static int tlob_parse_uprobe_line(char *buf, u64 *thr_out,
+VISIBLE_IF_KUNIT int tlob_parse_uprobe_line(char *buf, u64 *thr_out,
 					    char **path_out,
 					    loff_t *start_out, loff_t *stop_out)

[ ... ]

kernel/trace/rv/monitors/tlob/tlob.h static const struct automaton_tlob automaton_tlob = { · line 153

Quoted 13 lines
kernel/trace/rv/monitors/tlob/tlob.h +5 -0
index 94e7382c2130b..6ad9d5179ab60 100644
@@ -146,4 +146,10 @@ static const struct automaton_tlob automaton_tlob = {
 /* Upper budget bound (1 hour): keeps the u64 ns accumulators far from overflow. */
 #define TLOB_MAX_THRESHOLD_NS	3600000000000ULL
 
+#if IS_ENABLED(CONFIG_TLOB_KUNIT_TEST)
+int tlob_parse_uprobe_line(char *buf, u64 *thr_out, char **path_out,
+			   loff_t *start_out, loff_t *stop_out);
+int tlob_parse_remove_line(char *buf, char **path_out, loff_t *start_out);
+#endif /* CONFIG_TLOB_KUNIT_TEST */

[Severity: Low]
Will this cause a missing-prototypes build warning when CONFIG_KUNIT is enabled but CONFIG_TLOB_KUNIT_TEST is disabled?

In tlob.c, VISIBLE_IF_KUNIT drops the static keyword whenever CONFIG_KUNIT is enabled, making the functions global.

However, the prototype declarations here in tlob.h are strictly gated behind CONFIG_TLOB_KUNIT_TEST.

If a kernel is built with CONFIG_KUNIT=y but CONFIG_TLOB_KUNIT_TEST=n, the functions become global without a visible prototype, which will trigger -Wmissing-prototypes build errors. Should this IS_ENABLED() check use CONFIG_KUNIT instead to match the visibility macro?

Quoted 2 lines
kernel/trace/rv/monitors/tlob/tlob.h +1 -0
+
 #endif /* _RV_TLOB_H */
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
#6 lore raw

Re: [PATCH v5 3/9] rv: Add tlob model DOT file

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] The 'running' node is defined twice with conflicting shapes, incorrectly designating it as the…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The 'running' node is defined twice with conflicting shapes, incorrectly designating it as the final state instead of 'stopped'.
- [Medium] The commit message significantly contradicts the implemented DOT model's states, transitions, and invariants. --

--- Patch [3]: [PATCH v5 3/9] rv: Add tlob model DOT file --- commit a0f17b48f9f5df47c444bf6d511a4f8c03b0a847
Author: Wen Yang <[email protected]>

rv: Add tlob model DOT file

Add the Graphviz DOT specification of the tlob hybrid automaton to tools/verification/models/.

Quoted 4 lines

The model has three states (running,
waiting, sleeping), five transitions (switch_in, preempt, wakeup, sleep), and a single clock invariant clk_elapsed < BUDGET_NS() active in all states.

[Severity: Medium]
This isn't a bug, but does this description accurately reflect the implemented model?

The DOT file defines four states (running, waiting, sleeping, stopped), six transitions, and the clock invariant is intentionally absent from the "stopped" state.

tools/verification/models/tlob.dot line 6

Quoted 12 lines
tools/verification/models/tlob.dot new file +6 -0
new file mode 100644
index 0000000000000..7f6f09c3d7cf0
@@ -0,0 +1,25 @@
+digraph state_automaton {
+	center = true;
+	size = "7,11";
+	{node [shape = plaintext, style=invis, label=""] "__init_stopped"};
+	{node [shape = ellipse] "running"};
+	{node [shape = plaintext] "running"};

[Severity: High]
Is it intentional to define the "running" node twice with conflicting shapes?

Because tools/verification/rvgen/rvgen/automata.py prioritizes the first definition ("ellipse") over the second ("plaintext") when merging attributes, this improperly designates "running" as the final accepting state instead of "stopped".

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
#4 lore raw

Re: [PATCH v5 2/9] rv: Add generic uprobe infrastructure for RV monitors

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] The `rv_uprobe_unregister_nosync` API destroys state needed for safe batch cleanup, leading to…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The rv_uprobe_unregister_nosync API destroys state needed for safe batch cleanup, leading to resource leaks or double-frees.
- [High] The DECLARE_RV_UPROBE macro fails to zero-initialize the structure, risking execution of uninitialized function pointers.
- [Medium] The commit message explicitly contradicts the code regarding path release in rv_uprobe_unregister_nosync. --

--- Patch [2]: [PATCH v5 2/9] rv: Add generic uprobe infrastructure for RV monitors --- commit 51a0e9ee4d8b7c0315a129cc3c664429f117eeb4
Author: Wen Yang <[email protected]>

rv: Add generic uprobe infrastructure for RV monitors

Monitors that instrument user-space function boundaries need to resolve paths, register uprobes, and deregister them safely. Provide a thin wrapper so monitors share a single implementation of this boilerplate.

Quoted 2 lines

The path is released in
rv_uprobe_unregister_nosync() after the consumer has been removed.

[Severity: Medium]
The commit message states that the path is released in rv_uprobe_unregister_nosync() after the consumer has been removed. Is this description accurate?

Looking at the code for rv_uprobe_unregister_nosync(), it explicitly defers releasing the path to the caller. Could the commit message be updated to match the actual implementation?

Quoted 4 lines
include/rv/rv_uprobe.h new file +0 -0
index 0000000000000..d0a9079ac5be4

[ ... ]

include/rv/rv_uprobe.h line 35

Quoted 2 lines
include/rv/rv_uprobe.h new file +2 -0
+/* Embed a named rv_uprobe inside a caller struct */
+#define DECLARE_RV_UPROBE(name)		struct rv_uprobe name

[Severity: High]
Does this macro zero-initialize the structure?

Since it simply expands to a struct declaration, local variables using this macro might contain stack garbage. If a caller only sets p->uc.handler, could this leave other uprobe_consumer fields like filter or ret_handler uninitialized, causing the uprobe subsystem to execute random memory when triggered?

Quoted 4 lines
kernel/trace/rv/rv_uprobe.c new file +0 -0
index 0000000000000..b412a8e28a6ec

[ ... ]

Quoted 4 lines
kernel/trace/rv/rv_uprobe.c new file +4 -0
+int rv_uprobe_register(const char *binpath, loff_t offset, struct rv_uprobe *p)
+{
+	struct inode *inode;
+	int ret;

[ ... ]

kernel/trace/rv/rv_uprobe.c line 43

Quoted 10 lines
kernel/trace/rv/rv_uprobe.c new file +10 -0
+	p->uprobe = uprobe_register(inode, offset, 0, &p->uc);
+	if (IS_ERR(p->uprobe)) {
+		ret = PTR_ERR(p->uprobe);
+		p->uprobe = NULL;
+		path_put(&p->path);
+		return ret;
+	}
+
+	return 0;
+}

[Severity: High]
When uprobe_register() fails, path_put() is called, but p->path is not cleared. If a caller blindly loops over an array of probes to clean up paths later, could this leave dangling pointers that result in a double-free?

[ ... ]

Quoted 10 lines
kernel/trace/rv/rv_uprobe.c new file +10 -0
+void rv_uprobe_unregister(struct rv_uprobe *p)
+{
+	if (!p || !p->uprobe)
+		return;
+
+	uprobe_unregister_nosync(p->uprobe, &p->uc);
+	p->uprobe = NULL;
+	rv_uprobe_sync();
+	path_put(&p->path);
+}

[ ... ]

kernel/trace/rv/rv_uprobe.c line 81

Quoted 9 lines
kernel/trace/rv/rv_uprobe.c new file +9 -0
+void rv_uprobe_unregister_nosync(struct rv_uprobe *p)
+{
+	if (!p || !p->uprobe)
+		return;
+
+	uprobe_unregister_nosync(p->uprobe, &p->uc);
+	p->uprobe = NULL;
+	/* path held; caller must call rv_uprobe_sync() then path_put(&p->path) */
+}

[Severity: High]
By setting p->uprobe to NULL here, does rv_uprobe_unregister_nosync() destroy the state needed to safely perform the path cleanup?

If a caller attempts to use rv_uprobe_unregister() to clean up paths after a batch of nosync operations, it will return early because p->uprobe is NULL, silently leaking the VFS path reference:

rv_uprobe_unregister() {
	if (!p || !p->uprobe)
		return;
    ...
}

Alternatively, without external shadow state, how can a caller distinguish between a successfully unregistered probe that needs a path_put() and a probe that failed registration initially?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#16 lore raw

Re: [PATCH v5 8/9] selftests/verification: Add tlob selftests

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider: - [High] The test `uprobe_detail_waiting.tc` spawns an unbounded SCHED_FIFO priority-99 hog, causing a…

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] The test uprobe_detail_waiting.tc spawns an unbounded SCHED_FIFO priority-99 hog, causing a system-wide hard deadlock on single-CPU machines.
- [Medium] Empty noinline uprobe target functions will be optimized away by the compiler, breaking the stop probe and causing tests to fail.
- [Medium] The test suite fails when run from an installed kselftest environment due to relying on an unexported VERIFICATIONTEST_BINDIR environment variable.
- [Medium] The test scripts hardcode absolute paths to /sys/kernel/tracing/, breaking portability on systems where tracefs is mounted at /sys/kernel/debug/tracing.
- [Low] The tlob_sym ELF parser lacks bounds checking for the program headers offset (e_phoff), allowing out-of-bounds memory reads.
- [Low] The run_tlob_tests.sh standalone script relies on relative source paths and make, making it non-functional in installed kselftest environments. --

--- Patch [8]: [PATCH v5 8/9] selftests/verification: Add tlob selftests --- commit 71ad2abd1a52a7f2eb279e47c07bd2822ab59ec5
Author: Wen Yang <[email protected]>

selftests/verification: Add tlob selftests

Add seven ftrace-style test scripts for the tlob RV monitor under tools/testing/selftests/verification/test.d/tlob/. The tests cover uprobe binding management, budget violation detection, and per-state time accounting.

Quoted 4 lines
tools/testing/selftests/verification/Makefile +0 -0
index aa8790c22a71b..41445d15b86a0 100644

[ ... ]

tools/testing/selftests/verification/Makefile TEST_PROGS := verificationtest-ktap · line 12

Quoted 9 lines

TEST_PROGS := verificationtest-ktap
TEST_FILES := test.d settings
EXTRA_CLEAN := $(OUTPUT)/logs/*

tools/testing/selftests/verification/Makefile +4 -0
+TEST_GEN_FILES := tlob_sym tlob_target
+
 include ../lib.mk
+
+export VERIFICATIONTEST_BINDIR := $(OUTPUT)

[Severity: Medium]
Will this environment variable be available when the tests are run from an installed kselftest environment? Since the underlying runner doesn't invoke make during installed test execution, this might leave the variable undefined.

Quoted 5 lines
tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh new file +0 -0
new file mode 100755
index 0000000000000..13adf1eec9a26

[ ... ]

tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh line 13

Quoted 5 lines
tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh new file +5 -0
+FTRACETEST="$SCRIPT_DIR/../../../ftrace/ftracetest"
+
+# Build test helpers
+echo "Building tlob test helpers..."
+make -C "$SCRIPT_DIR/../.." all

[Severity: Low]
Does this script work in an installed kselftest environment? The source Makefile and .c files won't be present there, which could cause this make command to fail.

tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc line 6

Quoted 12 lines
tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc new file +6 -0
new file mode 100644
index 0000000000000..baa6c5fa0ff2a
@@ -0,0 +1,35 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor uprobe binding (visible in monitor file, removable, duplicate rejected)
+# requires: tlob:monitor
+
+UPROBE_TARGET="${VERIFICATIONTEST_BINDIR}/tlob_target"

[Severity: Medium]
Could this lead to path resolution errors? If VERIFICATIONTEST_BINDIR is empty during installed test execution, it tries to execute /tlob_target from the root directory.

Quoted 5 lines
tools/testing/selftests/verification/test.d/tlob/uprobe_detail_running.tc new file +0 -0
new file mode 100644
index 0000000000000..f9a412930cda4

[ ... ]

tools/testing/selftests/verification/test.d/tlob/uprobe_detail_running.tc line 24

Quoted 4 lines
tools/testing/selftests/verification/test.d/tlob/uprobe_detail_running.tc new file +4 -0
+echo 1 > /sys/kernel/tracing/events/rv/detail_env_tlob/enable
+echo 1 > /sys/kernel/tracing/tracing_on
+echo 1 > monitors/tlob/enable
+echo > /sys/kernel/tracing/trace

[Severity: Medium]
Could hardcoding absolute paths to /sys/kernel/tracing/ cause issues on systems where tracefs is mounted at /sys/kernel/debug/tracing? ftracetest handles dynamically locating the mount point, so relative paths might be more portable.

Quoted 5 lines
tools/testing/selftests/verification/test.d/tlob/uprobe_detail_waiting.tc new file +0 -0
new file mode 100644
index 0000000000000..68aeb1fa2172f

[ ... ]

Quoted 2 lines
tools/testing/selftests/verification/test.d/tlob/uprobe_detail_waiting.tc new file +2 -0
+# Pick the last CPU to avoid cpu0 which is used by vng infrastructure.
+cpu=$(($(nproc) - 1))

[ ... ]

tools/testing/selftests/verification/test.d/tlob/uprobe_detail_waiting.tc line 32

Quoted 4 lines
tools/testing/selftests/verification/test.d/tlob/uprobe_detail_waiting.tc new file +4 -0
+# Start the RT hog BEFORE the target so the target is immediately preempted
+# when it calls tlob_preempt_work() (start probe fires), minimising running_ns.
+chrt -f 99 taskset -c "$cpu" sh -c 'while true; do :; done' 2>/dev/null &
+hog_pid=$!

[Severity: High]
Can this script cause a system-wide hard deadlock on single-CPU machines?

On a single core, cpu evaluates to 0, and spawning an unbounded SCHED_FIFO priority-99 loop will permanently monopolize the CPU. With ftracetest disabling RT throttling, the test script is starved and cannot proceed to teardown the process.

Quoted 5 lines
tools/testing/selftests/verification/tlob_sym.c new file +0 -0
new file mode 100644
index 0000000000000..a92fc49d13043

[ ... ]

tools/testing/selftests/verification/tlob_sym.c line 116

Quoted 7 lines
tools/testing/selftests/verification/tlob_sym.c new file +7 -0
+		Elf64_Phdr *phdrs = (Elf64_Phdr *)((char *)map + ehdr->e_phoff);
+		int pi;
+
+		for (pi = 0; pi < ehdr->e_phnum; pi++) {
+			Elf64_Phdr *ph = &phdrs[pi];
+
+			if (ph->p_type != PT_LOAD)

[Severity: Low]
Is it possible for this parser to perform an out-of-bounds memory read?

The parser verifies the section header offsets against the file size, but it looks like the program header offset e_phoff and e_phnum are not similarly validated before casting and reading ph->p_type.

Quoted 5 lines
tools/testing/selftests/verification/tlob_target.c new file +0 -0
new file mode 100644
index 0000000000000..adf4c2397fb37

[ ... ]

Quoted 9 lines
tools/testing/selftests/verification/tlob_target.c new file +9 -0
+/* stop probe; noinline keeps the entry point visible to uprobes */
+noinline void tlob_busy_work_done(void)
+{
+	/* empty: uprobe fires on entry */
+}
+
+/* start probe; busy-spin so running_ns dominates */
+noinline void tlob_busy_work(unsigned long duration_ms)
+{

[ ... ]

tools/testing/selftests/verification/tlob_target.c line 68

Quoted 2 lines
tools/testing/selftests/verification/tlob_target.c new file +2 -0
+	tlob_busy_work_done();
+}

[Severity: Medium]
Will the compiler optimize away the call to tlob_busy_work_done()?

Since the function is empty and returns void, interprocedural analysis might determine it has no side effects and remove the call from tlob_busy_work(), which would prevent the uprobe stop event from firing. Could an empty volatile asm statement prevent it from being optimized out?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
#2 lore raw

Re: [PATCH v5 1/9] rv: Introduce DA_MON_ALLOCATION_STRATEGY

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] DA_ALLOC_POOL strategy causes lockdep 'Invalid wait context' warnings and crashes on PREEMPT_RT…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] DA_ALLOC_POOL strategy causes lockdep 'Invalid wait context' warnings and crashes on PREEMPT_RT when used in tracepoints that hold raw spinlocks. --

--- Patch [1]: [PATCH v5 1/9] rv: Introduce DA_MON_ALLOCATION_STRATEGY --- commit e8961cb4205551f74e01ec6b7818734bafc846f3
Author: Wen Yang <[email protected]>

rv: Introduce DA_MON_ALLOCATION_STRATEGY

This patch introduces a compile-time selection for per-object deterministic automata (DA) monitor allocation strategies. It adds DA_ALLOC_AUTO, DA_ALLOC_POOL, and DA_ALLOC_MANUAL to handle contexts where dynamic memory allocation is problematic.

Quoted 4 lines
include/rv/da_monitor.h +0 -0
index e3cf85c9ce556..48c534324cbb2 100644

[ ... ]

Quoted 13 lines
include/rv/da_monitor.h +9 -0
@@ -543,6 +596,59 @@ static inline monitor_target da_get_target_by_id(da_id_type id)
 	return mon_storage->target;
 }
 
+/*
+ * Pre-allocated mempool for DA_ALLOC_POOL monitors: DA_MON_POOL_SIZE
+ * slots, eager-allocated at init.  mempool_alloc_preallocated() pops a
+ * slot without touching the allocator (bounded start latency; NULL when
+ * exhausted).  mempool_free() is safe from RCU-callback context.
+ * Non-pool monitors get a zero-initialised mempool_t; pool paths compile
+ * away.
+ */
+static mempool_t da_monitor_pool;

[ ... ]

Quoted 8 lines
include/rv/da_monitor.h +8 -0
+/*
+ * da_create_pool_storage - pop a free pool slot and insert it into the hash.
+ *
+ * Returns the new da_monitor, or NULL if the pool is exhausted.  Finding
+ * an existing entry for the same id fires WARN_ON_ONCE (double-start bug).
+ *
+ * Caller must hold an RCU read-side CS and the monitor's serialisation lock.
+ */

include/rv/da_monitor.h static inline monitor_target da_get_target_by_id(da_id_type id) · line 634

Quoted 10 lines
include/rv/da_monitor.h +10 -0
+static inline struct da_monitor *
+da_create_pool_storage(da_id_type id, monitor_target target,
+		       struct da_monitor *da_mon)
+{
+	struct da_monitor_storage *mon_storage, *existing;
+
+	if (da_mon)
+		return da_mon;
+
+	mon_storage = mempool_alloc_preallocated(&da_monitor_pool);

[Severity: High]
Will this cause an "Invalid wait context" lockdep warning or a kernel panic on PREEMPT_RT systems when executed from tracepoints holding raw spinlocks?

The patch describes DA_ALLOC_POOL as being useful for contexts problematic for allocation, such as scheduling. If a monitor uses this pool strategy in a tracepoint like sched_switch, it will be called while holding a raw_spinlock_t (like rq->lock).

Looking at mempool_alloc_preallocated(), it unconditionally acquires the pool lock, which is a spinlock_t. On PREEMPT_RT, spinlock_t maps to a sleepable rt_mutex. Acquiring a sleeping lock while holding a raw_spinlock_t is a strict wait-context locking violation.

Is there a way to manage the pre-allocated pool in da_create_pool_storage() without relying on locks that sleep on PREEMPT_RT?

Quoted 6 lines
include/rv/da_monitor.h +6 -0
+	if (!mon_storage)
+		return NULL;
+	memset(mon_storage, 0, sizeof(*mon_storage));
+
+	mon_storage->id = id;
+	mon_storage->target = target;

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#10 lore raw

Re: [PATCH v5 5/9] rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Overriding da_monitor_reset_hook to synchronously cancel timers deadlocks the system
- [Medium] Undocumented API change allowing da_monitor_sync_hook to be overridden

Pre-existing issues:
- [High] Monitor teardown sequence causes a Use-After-Free due to improper timer cancellation
- [High] Lockless update of discrete state and hybrid environment variables allows concurrent events to evaluate inconsistent state --

--- Patch [5]: [PATCH v5 5/9] rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable --- commit 367db4a7d614e2458fbdde565ff9b9fc8d92d1b1
Author: Wen Yang <[email protected]>

rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable

This commit wraps da_monitor_reset_hook and EVENT_NONE_LBL with #ifndef guards to allow HA-based monitors to substitute their own implementations. It is intended to help tools define custom reset hooks for per-task hrtimer cleanup.

Quoted 4 lines

rv: Make da_monitor_reset_hook and EVENT_NONE_LBL overridable

Wrap both definitions with #ifndef guards so HA-based monitors can substitute their own implementations before including this header.

[Severity: Medium]
The commit message mentions wrapping both definitions (da_monitor_reset_hook and EVENT_NONE_LBL), but the patch also silently wraps a third definition:
da_monitor_sync_hook(). Could the commit message be updated to reflect this additional API change, or was this change unintentional?

include/rv/ha_monitor.h static bool ha_monitor_handle_constraint(struct da_monitor *da_mon, · line 37

Quoted 7 lines
include/rv/ha_monitor.h +0 -0
index e1738d199b289..807b981eb548a 100644
@@ -36,8 +36,14 @@ static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
 					 da_id_type id);
 #define da_monitor_event_hook ha_monitor_handle_constraint

[Severity: High]
This isn't a bug introduced by this patch, but can concurrent events evaluate inconsistent states here?

Looking at the event processing sequence, da_event() in include/rv/da_monitor.h updates the monitor's discrete state using a lockless try_cmpxchg() loop. Only after the state is successfully updated does it call da_monitor_event_hook(), which resolves to ha_monitor_handle_constraint(), to update the environment variables.

Could a concurrent event on another CPU observe the newly updated discrete state but still see the stale environment variables before the first CPU finishes updating them?

include/rv/ha_monitor.h static bool ha_monitor_handle_constraint(struct da_monitor *da_mon, · line 43

Quoted 6 lines
include/rv/ha_monitor.h +4 -0
 #define da_monitor_init_hook ha_monitor_init_env
+
+/* Overrides must still call ha_monitor_reset_env() or cancel the timer. */
+#ifndef da_monitor_reset_hook
 #define da_monitor_reset_hook ha_monitor_reset_env
+#endif

[Severity: High]
Could following the documentation to cancel the timer here cause a deadlock?

If a developer overrides this hook to synchronously cancel their timers (such as using hrtimer_cancel() or timer_delete_sync()), it appears this will deadlock when called from the timer callback itself.

The timer callback __ha_monitor_timer_callback() calls da_monitor_reset(), which unconditionally invokes da_monitor_reset_hook(). Would this result in the timer callback attempting to synchronously cancel itself?

[Severity: High]
This isn't a bug introduced by this patch, but is there a use-after-free risk during monitor teardown due to improper timer cancellation?

When a per-object monitor is destroyed, da_destroy_storage() cancels timers without waiting (using hrtimer_try_to_cancel() or timer_delete() via ha_cancel_timer()) and then defers freeing the storage using call_rcu() or kfree_rcu().

The timer callback __ha_monitor_timer_callback() relies on guard(rcu)() for synchronization. However, if the timer callback thread is preempted before it enters the RCU read-side critical section, the grace period could complete and the monitor storage could be freed.

When the timer callback resumes, would it then dereference the freed monitor pointer?

Quoted 3 lines
include/rv/ha_monitor.h +2 -0
+#ifndef da_monitor_sync_hook
 #define da_monitor_sync_hook() synchronize_rcu()
+#endif

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
#18 lore raw

Re: [PATCH v5 9/9] selftests/ftrace: Walk up to find test.d/functions when a subdirectory is passed

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Local Privilege Escalation via untrusted directory traversal when implicitly sourcing…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Local Privilege Escalation via untrusted directory traversal when implicitly sourcing test.d/functions.
- [Medium] Infinite loop in directory traversal when testing top-level directories due to POSIX 'dirname' behavior with double slashes. --

--- Patch [9]: [PATCH v5 9/9] selftests/ftrace: Walk up to find test.d/functions when a subdirectory is passed --- commit 982c65a7f93c6d254f86b734c29fd401b0e3707a
Author: Wen Yang <[email protected]>

selftests/ftrace: Walk up to find test.d/functions when a subdirectory is passed

When a test directory that does not itself contain test.d/functions is passed to ftracetest (e.g. verification/test.d/tlob/), ftracetest fell back to its own functions file and lost the rv-specific check_requires handling for ':monitor' and ':reactor' requirements.

Quoted 8 lines
tools/testing/selftests/ftrace/ftracetest +0 -0
index 0a56bf209f6c0..3ba929820dd2c 100755
@@ -159,9 +159,20 @@ parse_opts() { # opts
   if [ -n "$OPT_TEST_CASES" ]; then
     TEST_CASES=$OPT_TEST_CASES
   fi

tools/testing/selftests/ftrace/ftracetest parse_opts() { # opts · line 168

Quoted 10 lines
tools/testing/selftests/ftrace/ftracetest +7 -3
-  if [ -n "$OPT_TEST_DIR" -a -f "$OPT_TEST_DIR"/test.d/functions ]; then
-    TOP_DIR=$OPT_TEST_DIR
-    TEST_DIR=$TOP_DIR/test.d
+  if [ -n "$OPT_TEST_DIR" ]; then
+    # Walk up from OPT_TEST_DIR to find the nearest ancestor that contains
+    # test.d/functions.  This allows a monitor subdirectory (e.g.
+    # verification/test.d/tlob/) to be passed directly without placing a
+    # dummy functions shim in each new subdirectory.
+    dir=$OPT_TEST_DIR
+    while [ "$dir" != "/" ]; do

[Severity: Medium]
Can this loop run indefinitely if a user passes a directory directly under the root, like /tmp?

If the path is formatted as //tmp, GNU dirname preserves double leading slashes because it treats them as POSIX UNC paths. If dir starts as //tmp, dirname "$dir" evaluates to //, and the loop condition [ "$dir" != "/" ] will evaluate to true infinitely, causing the test script to hang.

tools/testing/selftests/ftrace/ftracetest parse_opts() { # opts · line 173

Quoted 5 lines
tools/testing/selftests/ftrace/ftracetest +5 -0
+      if [ -f "$dir/test.d/functions" ]; then
+        TOP_DIR=$dir
+        TEST_DIR=$TOP_DIR/test.d
+        break
+      fi

[Severity: High]
Could this directory traversal lead to arbitrary code execution if the path walks back through a world-writable directory?

If ftracetest is run as root on a trusted directory inside a path like /tmp/legitimate_tests/, the loop will traverse up to /tmp and search for /tmp/test.d/functions.

An unprivileged attacker could preemptively plant a malicious script at that location. The script would then be found, and since the ftracetest script later sources this file, it would execute the attacker's code with root privileges.

Quoted 4 lines
tools/testing/selftests/ftrace/ftracetest +3 -0
+      dir=$(dirname "$dir")
+    done
+  fi
 }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
#8 lore raw

Re: [PATCH v5 4/9] rv: Fix ha_invariant_passed_ns silent bypass of invariant check

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:

Pre-existing issues:
- [High] The patch unmasks a preexisting integer underflow bug in ha_check_invariant_ns that will falsely trigger invariant violations during early boot when time_ns < expire_ns.
- [High] Per-object monitor destruction frees storage via kfree_rcu while background timers can still be executing, leading to Use-After-Free.
- [High] New monitor storage is published to the RCU hash table before its fields are fully initialized, exposing readers to NULL dereferences.
- [High] Lockless updates to the monitor's state and environment variables allow concurrent events to evaluate constraints against stale environment data.
- [High] An integer underflow in ha_start_timer_ns() and ha_start_timer_jiffy() silently masks invariant violations if the elapsed time exceeds the expiration limit upon state entry. --

--- Patch [4]: [PATCH v5 4/9] rv: Fix ha_invariant_passed_ns silent bypass of invariant check --- commit d0917ae23deee9db8a74a56834fa74dbc75f6b60
Author: Wen Yang <[email protected]>

rv: Fix ha_invariant_passed_ns silent bypass of invariant check

When env_store is U64_MAX, ha_invariant_passed_ns() returns 0 immediately without initializing env_store to the current clock. Subsequent calls find env_store still at U64_MAX, causing the elapsed comparison to wrap and always report the invariant as satisfied, silently masking any violations. This commit fixes it by calling ha_reset_clk_ns() to establish the guard on the first invocation.

include/rv/ha_monitor.h static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs en · line 358

Quoted 10 lines
include/rv/ha_monitor.h +1 -1
index 6e1c7fe5449a3..e1738d199b289 100644
@@ -355,7 +355,7 @@ static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs en
 	if (env < 0 || env >= ENV_MAX_STORED)
 		return 0;
 	if (ha_monitor_env_invalid(ha_mon, env))
-		return 0;
+		ha_reset_clk_ns(ha_mon, env, time_ns);

[Severity: High]
This is a pre-existing issue, but does this subtraction underflow during early boot, which is now exposed by this initialization?

Looking at ha_check_invariant_ns():

include/rv/ha_monitor.h:ha_check_invariant_ns() {
    return READ_ONCE(ha_mon->env_store[env]) >= time_ns - expire_ns;
}

If an ns-based monitor is started during early boot, time_ns can be smaller than expire_ns, causing time_ns - expire_ns to underflow to a huge unsigned value.

Since this patch now correctly initializes env_store to time_ns, the check env_store >= huge_value will evaluate to false, spuriously failing the invariant immediately.

include/rv/ha_monitor.h static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs en · line 361

Quoted 3 lines
include/rv/ha_monitor.h +0 -0
 	return ha_get_env(ha_mon, env, time_ns);
 }
 

[ ... ]

[Severity: High]
This is a pre-existing issue, but does this calculation underflow if the timeout has already been violated?

Looking at ha_start_timer_ns() in include/rv/ha_monitor.h:

include/rv/ha_monitor.h:ha_start_timer_ns() {
    ...
	u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns);

	ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED,
			     nsecs_to_jiffies(expire - passed + TICK_NSEC - 1), time_ns);
    ...
}

If passed > expire, the subtraction expire - passed underflows to a massive value, which gets converted to a huge positive delay. Could this schedule the verification timer billions of years in the future instead of failing immediately, silently masking the violation?

This same pattern appears in ha_start_timer_jiffy():

include/rv/ha_monitor.h:ha_start_timer_jiffy() {
    ...
	u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns);

	ha_start_timer_ns(ha_mon, ENV_MAX_STORED,
			  jiffies_to_nsecs(expire - passed), time_ns);
    ...
}

[Severity: High]
This is a pre-existing issue, but can this sequence lead to a use-after-free if a background timer is still executing?

Looking at da_destroy_storage() in include/rv/da_monitor.h:

include/rv/da_monitor.h:da_destroy_storage() {
    ...
	da_monitor_reset_hook(&mon_storage->rv.da_mon);
	hash_del_rcu(&mon_storage->node);
	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
		call_rcu(&mon_storage->rcu, da_pool_return_cb);
	else
		kfree_rcu(mon_storage, rcu);
    ...
}

da_monitor_reset_hook() cancels the timer via non-synchronous timer_delete() or hrtimer_try_to_cancel(), then the storage is immediately queued for freeing via kfree_rcu().

If the timer callback (__ha_monitor_timer_callback) is preempted before it calls guard(rcu)(), the RCU grace period could complete and free the memory. When the timer thread resumes, it reads the ha_mon_destroying flag from freed memory.

[Severity: High]
This is a pre-existing issue, but are new monitor objects published to the RCU hash table before they are fully initialized?

Looking at da_create_empty_storage() and da_create_storage() in include/rv/da_monitor.h:

include/rv/da_monitor.h:da_create_empty_storage() {
    ...
	hash_add_rcu(da_monitor_ht, &mon_storage->node, id);
	mon_storage->id = id;
	return mon_storage;
}
include/rv/da_monitor.h:da_create_storage() {
    ...
	mon_storage = da_create_empty_storage(id);
	if (!mon_storage)
		return NULL;

	mon_storage->target = target;
    ...
}

hash_add_rcu() makes the node visible to concurrent RCU readers before mon_storage->id is assigned, and before mon_storage->target is populated in da_create_storage(). Can a concurrent lookup find the node with a NULL target, leading to a NULL pointer dereference in the caller?

[Severity: High]
This is a pre-existing issue, but can the state machine and environment guards fall out of sync during concurrent events?

Looking at da_event() in include/rv/da_monitor.h:

include/rv/da_monitor.h:da_event() {
    ...
		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
			if (!da_monitor_event_hook(da_mon, curr_state, event, next_state, id))
				return false;
    ...
}

The new state is published locklessly via try_cmpxchg() before da_monitor_event_hook() executes to update the environment variables.

For global monitors, if multiple CPUs execute da_event() concurrently, could CPU 2 observe the new state and process a subsequent event before CPU 1 finishes updating the environment variables, causing CPU 2 to evaluate constraints using stale clock values?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
#12 lore raw

Re: [PATCH v5 6/9] rv: Add tlob hybrid automaton monitor

In reply to [email protected]
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider: - [Critical] Relying on the empty stub `ha_cancel_timer_sync()` leaves the budget timer active when windows…

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] Relying on the empty stub ha_cancel_timer_sync() leaves the budget timer active when windows close, causing Use-After-Free panics on timer expiry.
- [High] Concurrent task exit during tlob_unbind_reap() causes Use-After-Free and list corruption.
- [High] tlob_destroy_task() dereferences ha_mon outside of the RCU read-side critical section, causing a Use-After-Free.
- [High] The clock anchor ha_mon->env_store[clk_elapsed_tlob] is never initialized, causing immediate hrtimer firing due to integer underflow.
- [High] tlob_destroy_task() uses a PID lookup to find the monitor, risking UAF and corruption of an unrelated task's monitor due to PID recycling.
- [High] Dangling pointer in the RV framework hash table leading to UAF if da_handle_start_run_event fails.
- [Medium] Unbinding uprobes while a task is actively monitored permanently leaks its mempool slot.
- [Medium] Missing initialization of the stopping flag on fresh allocations allows sched tracepoints to trigger invalid monitor transitions. --

--- Patch [6]: [PATCH v5 6/9] rv: Add tlob hybrid automaton monitor --- commit 2dbaa3c57b50d6013814617edf9ea93aed73291d
Author: Wen Yang <[email protected]>
Subject: rv: Add tlob hybrid automaton monitor

This commit adds tlob (task latency over budget), a per-task hybrid automaton RV monitor. It tracks wall-clock time across a user-delimited code section and emits an error when elapsed time exceeds a configurable budget.

Quoted 3 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +0 -0

[ ... ]

Quoted 4 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +0 -0
@@ -218,6 +218,22 @@ static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
 				       enum states curr_state, enum events event,
 				       enum states next_state, u64 time_ns)
 {

[ ... ]

Quoted 5 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +5 -0
+	if (atomic_read_acquire(&ha_get_target(ha_mon)->stopping)) {
+		/*
+		 * Restart (stopped -> running): arm the timer, then clear
+		 * stopping so __tlob_acc() admits sched events only once the
+		 * state is already running_tlob.  _release pairs with the

kernel/trace/rv/monitors/tlob/tlob.c line 247

Quoted 10 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +10 -0
+		 * acquires in __tlob_acc/tlob_reset_notify.
+		 */
+		if (next_state < state_max_tlob)
+			ha_start_timer_ns(ha_mon, clk_elapsed_tlob, BUDGET_NS(ha_mon), time_ns);
+		atomic_set_release(&ha_get_target(ha_mon)->stopping, 0);
+		return;
+	}
+
+	if (next_state < state_max_tlob)
+		ha_start_timer_ns(ha_mon, clk_elapsed_tlob, BUDGET_NS(ha_mon), time_ns);

[Severity: High]
Does this path forget to initialize the clock anchor?

ha_setup_invariants() does not seem to assign ha_mon->env_store[clk_elapsed_tlob] = time_ns here. Because it remains at the uninitialized sentinel value, won't the elapsed time calculation underflow and cause the relative hrtimer to fire immediately upon window start?

Quoted 3 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +3 -0
+	else
+		ha_cancel_timer(ha_mon);
+}

[ ... ]

Quoted 2 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +0 -0
@@ -480,6 +480,41 @@ static int tlob_start_task(struct task_struct *task, u64 threshold_ns, void *bin
 {

[ ... ]

kernel/trace/rv/monitors/tlob/tlob.c line 540

Quoted 5 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +5 -0
+	ws = tlob_ws_alloc();
+	if (!ws)
+		return -ENOSPC;
+
+	ws->task = task;

[Severity: Medium]
Should the stopping flag be initialized to 1 here?

For a fresh allocation, tlob_ws_alloc() zeroes the memory, leaving ws->stopping = 0. If a scheduler event happens during start before ha_setup_invariants() is reached, won't __tlob_acc() allow it through while in stopped_tlob, causing an invalid transition and monitor reset?

Quoted 7 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +7 -0
+	get_task_struct(task);
+	ws->threshold_ns = threshold_ns;
+	ws->last_ts = ktime_get();
+	raw_spin_lock_init(&ws->entry_lock);
+	ws->binding = binding;
+	if (binding)
+		list_add_tail(&ws->started_node,

kernel/trace/rv/monitors/tlob/tlob.c line 557

Quoted 10 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +10 -0
+			      &((struct tlob_uprobe_binding *)binding)->started_list);
+
+	/* Dispatch failed (pool exhausted or monitor disabled): unwind the slot. */
+	if (!da_handle_start_run_event(task->pid, ws, start_tlob)) {
+		if (binding)
+			list_del_init(&ws->started_node);
+		put_task_struct(task);
+		tlob_ws_direct_return(ws);
+		return -ENOSPC;
+	}

[Severity: High]
By directly freeing ws via tlob_ws_direct_return(ws) without calling da_destroy_storage(task->pid), does this leave a dangling pointer in the framework hash table?

If da_handle_start_run_event() fails, it appears it has already allocated storage in the RCU-protected da_monitor_ht hash table. Subsequent tracepoints might dereference this freed memory.

Quoted 3 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +3 -0
+
+	return 0;
+}

[ ... ]

kernel/trace/rv/monitors/tlob/tlob.c static void tlob_destroy_task(struct task_struct *task) · line 628

Quoted 7 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +5 -0
@@ -623,20 +623,32 @@ static void tlob_destroy_task(struct task_struct *task)
 {
+	struct ha_monitor *ha_mon;
+	struct tlob_task_state *ws;
+
+	scoped_guard(rcu) {
+		ha_mon = ha_get_monitor(task->pid, NULL);

[Severity: High]
Is it safe to look up the monitor by task->pid here?

If tlob_unbind_reap() delays calling tlob_destroy_task() for a task that has already exited, the original PID might be recycled and assigned to a new process. If this new process is also monitored by tlob, could this blindly claim and destroy the new process's monitor while leaking the original task state?

Quoted 4 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +4 -0
+		if (!ha_mon)
+			return;
+		ws = ha_get_target(ha_mon);
+		if (WARN_ON_ONCE(!ws))

kernel/trace/rv/monitors/tlob/tlob.c line 644

Quoted 10 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +10 -0
+			return;
+		if (atomic_cmpxchg_release(&ws->destroying, 0, 1) != 0)
+			return;
+	}
+
+	tlob_detach_from_binding(ws);
+
+	/* Force the window ended: @task may never have reached STOP or a timer. */
+	atomic_set(&ws->stopping, 1);
+	ha_cancel_timer_sync(ha_mon);

[Severity: High]
Is ha_mon still safe to dereference after the RCU read-side critical section ends?

The scoped_guard(rcu) block closes just above, dropping the RCU lock. If the monitor is concurrently disabled, ha_monitor_destroy() issues synchronize_rcu() and frees the monitor storage. Could the storage be freed before the teardown calls on ha_mon execute here?

[Severity: Critical]
Is this stub definition intentional?

If ha_cancel_timer_sync() is defined as an empty inline stub in include/rv/ha_monitor.h, it seems the hrtimer is never actually dequeued. When tlob_destroy_task() subsequently frees the monitor storage, could the still-active timer fire and execute its callback on freed memory?

Quoted 5 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +5 -0
+
+	scoped_guard(rcu) {
+		da_monitor_reset(&ha_mon->da_mon);
+	}
+	da_destroy_storage(task->pid);

[ ... ]

kernel/trace/rv/monitors/tlob/tlob.c static void tlob_unbind_reap(struct tlob_uprobe_binding *b) · line 759

Quoted 13 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +11 -0
@@ -748,16 +748,22 @@ static void tlob_unbind_reap(struct tlob_uprobe_binding *b)
 {
+	struct tlob_task_state *ws, *tmp;
+	LIST_HEAD(to_destroy);
+
+	scoped_guard(spinlock, &tlob_ws_lock) {
+		list_for_each_entry_safe(ws, tmp, &b->started_list, started_node) {
+			list_del_init(&ws->started_node);
+			ws->binding = NULL;
+			if (atomic_read(&ws->stopping))
+				list_add_tail(&ws->started_node, &to_destroy);
+		}
+	}

[Severity: Medium]
Does this sequence permanently leak the mempool slot for active tasks?

If an active task is unbound, its ws->binding is set to NULL but it is not added to to_destroy. Since the uprobe is unregistered, the stop probe never fires. Even if it could, tlob_stop_task() returns -EALREADY because ws->binding is NULL. Will the task remain in running_tlob forever, leaking its pool slot until the task exits?

kernel/trace/rv/monitors/tlob/tlob.c line 763

Quoted 2 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +2 -0
+
+	list_for_each_entry_safe(ws, tmp, &to_destroy, started_node) {

[Severity: High]
Can concurrent task exits cause list corruption and use-after-free here?

tlob_unbind_reap() populates to_destroy and clears ws->binding under lock, then drops the lock and iterates to_destroy locklessly.

If a task exits concurrently:
handle_sched_process_exit()
tlob_destroy_task()
tlob_detach_from_binding()

Because ws->binding is already NULL, tlob_detach_from_binding() returns early without acquiring the lock or calling list_del_init(). tlob_destroy_task() then frees ws, and when tlob_unbind_reap() resumes iterating, it accesses the freed ws object.

Quoted 4 lines
kernel/trace/rv/monitors/tlob/tlob.c new file +4 -0
+		list_del_init(&ws->started_node);
+		tlob_destroy_task(ws->task);
+	}
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6