tracing: wprobe: x86: Add wprobe for watchpoint
Subsystems (5)
Masami Hiramatsu (Google)
maintainerv7 cover tracing: wprobe: x86: Add wprobe for watchpoint
Hi,
Here is the 7th version of the series for adding new wprobe (watch probe) which provides memory access tracing event. Moreover, this can be used via event trigger. Thus it can trace memory access on a dynamically allocated objects too.
The previous version is here:
https://lore.kernel.org/all/176960933881.182525.11984731584313026309.stgit@devnote2/
This version is rebased on top of probes/for-next branch, fold IS_ERR_PCPU fix and fix some checkpatch.pl related issue (not all, some of them seems wrong warnings).
Other updates:
- Fix a race on wprobe handler using bp->attr.bp_addr instead of tw->addr.
- Fix modify_wide_hw_breakpoint_local() to update bp->attr.bp_addr.
- Return -EOPNOTSUPP instead of -ENOSYS. (that is not a syscall function)
- Update test script to use dentry_kill instead of __dentry_kill.
To support arm64, we need to avoid major pagefault on single-stepping issue. I think we can solve it with checking whether the address is the kernel (which must not cause a major page fault), or not.
Usage
-----
The basic usage of this wprobe is similar to other probes;
w:[GRP/][EVENT] [r|w|rw]@<ADDRESS|SYMBOL[+OFFS]> [FETCHARGS]
This defines a new wprobe event. For example, to trace jiffies update, you can do;
echo 'w:my_jiffies w@jiffies:8 value=+0($addr)' >> dynamic_events echo 1 > events/wprobes/my_jiffies/enable
Moreover, this can be combined with event trigger to trace the memory accecss on slab objects. The trigger syntax is;
set_wprobe:WPROBE_EVENT:FIELD[+OFFSET] [if FILTER] clear_wprobe:WPROBE_EVENT[:FIELD[+OFFSET]] [if FILTER]
set_wprobe sets WPROBE_EVENT's watch address on FIELD[+OFFSET]. clear_wprobe clears WPROBE_EVENT's watch address if it is set to FIELD[+OFFSET]. If FIELD is omitted, forcibly clear the watch address when trigger event is hit.
For example, trace the first 8 byte of the dentry data structure passed to do_truncate() until it is deleted by __dentry_kill(). (Note: all tracefs setup uses '>>' so that it does not kick do_truncate())
# echo 'w:watch rw@0:8 address=$addr value=+0($addr)' > dynamic_events
# echo 'f:truncate do_truncate dentry=$arg2' >> dynamic_events # echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
# echo 'f:dentry_kill __dentry_kill dentry=$arg1' >> dynamic_events # echo 'clear_wprobe:watch:dentry' >> events/fprobes/dentry_kill/trigger
# echo 1 >> events/fprobes/truncate/enable # echo 1 >> events/fprobes/dentry_kill/enable
# echo aaa > /tmp/hoge # echo bbb > /tmp/hoge # echo ccc > /tmp/hoge # rm /tmp/hoge
Then, the trace data will show;
# tracer: nop
#
# entries-in-buffer/entries-written: 16/16 #P:8
#
# _-----=> irqs-off/BH-disabled
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / _-=> migrate-disable
# |||| / delay
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
# | | | ||||| | |
sh-113 [004] ..... 6.467444: truncate: (do_truncate+0x4/0x120) dentry=0xffff8880044f0fd8
sh-113 [004] ..Zff 6.468534: watch: (lookup_fast+0xaa/0x150) address=0xffff8880044f0fd8 value=0x200080
sh-113 [004] ..Zff 6.468542: watch: (step_into+0x82/0x360) address=0xffff8880044f0fd8 value=0x200080
sh-113 [004] ..Zff 6.468547: watch: (step_into+0x9f/0x360) address=0xffff8880044f0fd8 value=0x200080
sh-113 [004] ..Zff 6.468553: watch: (path_openat+0xb3a/0xe70) address=0xffff8880044f0fd8 value=0x200080
sh-113 [004] ..Zff 6.468557: watch: (path_openat+0xb9a/0xe70) address=0xffff8880044f0fd8 value=0x200080
sh-113 [004] ..... 6.468563: truncate: (do_truncate+0x4/0x120) dentry=0xffff8880044f0fd8
sh-113 [004] ...1. 6.469826: dentry_kill: (__dentry_kill+0x0/0x220) dentry=0xffff8880044f0ea0
sh-113 [004] ...1. 6.469859: dentry_kill: (__dentry_kill+0x0/0x220) dentry=0xffff8880044f0d68
rm-118 [001] ..Zff 6.472360: watch: (lookup_fast+0xaa/0x150) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472366: watch: (step_into+0x82/0x360) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472370: watch: (step_into+0x9f/0x360) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472386: watch: (lookup_fast+0xaa/0x150) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472390: watch: (step_into+0x82/0x360) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472394: watch: (step_into+0x9f/0x360) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472415: watch: (lookup_one_qstr_excl+0x2c/0x150) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472419: watch: (lookup_one_qstr_excl+0xd5/0x150) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472424: watch: (may_delete+0x18/0x200) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472428: watch: (may_delete+0x194/0x200) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] ..Zff 6.472446: watch: (vfs_unlink+0x63/0x1c0) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] d.Z.. 6.472528: watch: (dont_mount+0x19/0x30) address=0xffff8880044f0fd8 value=0x200180
rm-118 [001] ..Zff 6.472533: watch: (vfs_unlink+0x11a/0x1c0) address=0xffff8880044f0fd8 value=0x200180
rm-118 [001] ..Zff 6.472538: watch: (vfs_unlink+0x12e/0x1c0) address=0xffff8880044f0fd8 value=0x200180
rm-118 [001] d.Z1. 6.472543: watch: (d_delete+0x61/0xa0) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] d.Z1. 6.472547: watch: (dentry_unlink_inode+0x14/0x110) address=0xffff8880044f0fd8 value=0x200080
rm-118 [001] d.Z1. 6.472551: watch: (dentry_unlink_inode+0x1e/0x110) address=0xffff8880044f0fd8 value=0x80
rm-118 [001] d.Z.. 6.472563: watch: (fast_dput+0x8d/0x120) address=0xffff8880044f0fd8 value=0x80
rm-118 [001] ...1. 6.472567: dentry_kill: (__dentry_kill+0x0/0x220) dentry=0xffff8880044f0fd8
sh-113 [004] ...2. 6.473049: dentry_kill: (__dentry_kill+0x0/0x220) dentry=0xffff888006e383a8Thank you,
---
Jinchao Wang (2):
x86/hw_breakpoint: Unify breakpoint install/uninstall x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint
Masami Hiramatsu (Google) (8):
tracing: wprobe: Add watchpoint probe event based on hardware breakpoint
x86: hw_breakpoint: Add a kconfig to clarify when a breakpoint fires
selftests: tracing: Add a basic testcase for wprobe
selftests: tracing: Add syntax testcase for wprobe
tracing: wprobe: Use a new seq_print_ip_sym_offset() wrapper
HWBP: Add modify_wide_hw_breakpoint_local() API
tracing: wprobe: Add wprobe event trigger
selftests: ftrace: Add wprobe trigger testcase
Documentation/trace/index.rst | 1 Documentation/trace/wprobetrace.rst | 158 +++ arch/Kconfig | 20 arch/x86/Kconfig | 2 arch/x86/include/asm/hw_breakpoint.h | 8 arch/x86/kernel/hw_breakpoint.c | 148 ++- include/linux/hw_breakpoint.h | 6 include/linux/trace_events.h | 3 kernel/events/hw_breakpoint.c | 39 + kernel/trace/Kconfig | 24 kernel/trace/Makefile | 1 kernel/trace/trace.c | 9 kernel/trace/trace.h | 5 kernel/trace/trace_probe.c | 22 kernel/trace/trace_probe.h | 8 kernel/trace/trace_wprobe.c | 1108 ++++++++++++++++++++ tools/testing/selftests/ftrace/config | 2 .../ftrace/test.d/dynevent/add_remove_wprobe.tc | 68 + .../test.d/dynevent/wprobes_syntax_errors.tc | 20 .../ftrace/test.d/trigger/trigger-wprobe.tc | 48 + 20 files changed, 1635 insertions(+), 65 deletions(-) create mode 100644 Documentation/trace/wprobetrace.rst create mode 100644 kernel/trace/trace_wprobe.c create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.tc create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc
--
Masami Hiramatsu (Google) <[email protected]>
Masami Hiramatsu (Google)
maintainerPATCH v7 1/10 tracing: wprobe: Add watchpoint probe event based on hardware breakpoint
From: Masami Hiramatsu (Google) <[email protected]>
Add a new probe event for the hardware breakpoint called wprobe-event. This wprobe allows user to trace (watch) the memory access at the specified memory address.
The new syntax is;
w[:[GROUP/]EVENT] [r|w|rw]@[ADDR|SYM][:SIZE] [FETCH_ARGs]
User also can use $addr to fetch the accessed address. But no other variables are supported. To record updated value, use '+0($addr)'.
For example, tracing updates of the jiffies;
/sys/kernel/tracing # echo 'w:my_jiffies w@jiffies' >> dynamic_events /sys/kernel/tracing # cat dynamic_events w:wprobes/my_jiffies w@jiffies:4
/sys/kernel/tracing # echo 1 > events/wprobes/my_jiffies/enable /sys/kernel/tracing # head -n 20 trace | tail -n 5
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
# | | | ||||| | |
<idle>-0 [000] d.Z1. 206.547317: my_jiffies: (tick_do_update_jiffies64+0xbe/0x130)
<idle>-0 [000] d.Z1. 206.548341: my_jiffies: (tick_do_update_jiffies64+0xbe/0x130)
<idle>-0 [000] d.Z1. 206.549346: my_jiffies: (tick_do_update_jiffies64+0xbe/0x130)---
Changes in v7:
- Include IS_ERR_PCPU fix.
- use seq_print_ip_sym_offset().
- fix checkpatch warning on DEFINE_FREE()
- Use bp->attr.bp_addr instead of tw->addr because it can be updated from another CPU.
--- Documentation/trace/index.rst | 1 Documentation/trace/wprobetrace.rst | 69 +++ include/linux/trace_events.h | 2 kernel/trace/Kconfig | 13 + kernel/trace/Makefile | 1 kernel/trace/trace.c | 9 kernel/trace/trace.h | 5 kernel/trace/trace_probe.c | 22 + kernel/trace/trace_probe.h | 8 kernel/trace/trace_wprobe.c | 692 +++++++++++++++++++++++++++++++++++ 10 files changed, 819 insertions(+), 3 deletions(-) create mode 100644 Documentation/trace/wprobetrace.rst create mode 100644 kernel/trace/trace_wprobe.c
Documentation/trace/index.rst +1 -0
@@ -36,6 +36,7 @@ the Linux kernel. kprobes kprobetrace fprobetrace + wprobetrace eprobetrace fprobe ring-buffer-design
Documentation/trace/wprobetrace.rst new file +69 -0
69 lines not on this page · raw message
include/linux/trace_events.h +2 -0
@@ -328,6 +328,7 @@ enum {TRACE_EVENT_FL_UPROBE_BIT ,TRACE_EVENT_FL_EPROBE_BIT ,TRACE_EVENT_FL_FPROBE_BIT , +TRACE_EVENT_FL_WPROBE_BIT ,TRACE_EVENT_FL_CUSTOM_BIT , TRACE_EVENT_FL_TEST_STR_BIT, };
@@ -358,6 +359,7 @@ enum {TRACE_EVENT_FL_UPROBE = (1 <<TRACE_EVENT_FL_UPROBE_BIT ),TRACE_EVENT_FL_EPROBE = (1 <<TRACE_EVENT_FL_EPROBE_BIT ),TRACE_EVENT_FL_FPROBE = (1 <<TRACE_EVENT_FL_FPROBE_BIT ), +TRACE_EVENT_FL_WPROBE = (1 <<TRACE_EVENT_FL_WPROBE_BIT ),TRACE_EVENT_FL_CUSTOM = (1 <<TRACE_EVENT_FL_CUSTOM_BIT ), TRACE_EVENT_FL_TEST_STR = (1 << TRACE_EVENT_FL_TEST_STR_BIT), };
kernel/trace/Kconfig +13 -0
@@ -862,6 +862,19 @@ config EPROBE_EVENTS convert the type of an event field. For example, turn an address into a string. +config WPROBE_EVENTS + bool "Enable wprobe-based dynamic events" + depends on TRACING + depends on HAVE_HW_BREAKPOINT + select PROBE_EVENTS + select DYNAMIC_EVENTS + help + This allows the user to add watchpoint tracing events based on + hardware breakpoints on the fly via the ftrace interface. + + Those events can be inserted wherever hardware breakpoints can be + set, and record accessed memory address and values. + config BPF_EVENTS depends on BPF_SYSCALL depends on (KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS
kernel/trace/Makefile +1 -0
@@ -126,6 +126,7 @@ obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o obj-$(CONFIG_FPROBE) += fprobe.o obj-$(CONFIG_RETHOOK) += rethook.o obj-$(CONFIG_FPROBE_EVENTS) += trace_fprobe.o +obj-$(CONFIG_WPROBE_EVENTS) += trace_wprobe.o obj-$(CONFIG_TRACEPOINT_BENCHMARK) += trace_benchmark.o obj-$(CONFIG_RV) += rv/
kernel/trace/trace.c +8 -1
@@ -4294,8 +4294,12 @@ static const char readme_msg[] =" uprobe_events\t\t- Create/append/remove/show the userspace dynamic events\n" "\t\t\t Write into this file to define/undefine new trace events.\n"#endif +#ifdef CONFIG_WPROBE_EVENTS +" wprobe_events\t\t- Create/append/remove/show the hardware breakpoint dynamic events\n" + "\t\t\t Write into this file to define/undefine new trace events.\n" +#endif #if defined(CONFIG_KPROBE_EVENTS ) || defined(CONFIG_UPROBE_EVENTS ) || \ - defined(CONFIG_FPROBE_EVENTS ) + defined(CONFIG_FPROBE_EVENTS ) || defined(CONFIG_WPROBE_EVENTS ) "\t accepts: event-definitions (one definition per line)\n"#if defined(CONFIG_KPROBE_EVENTS ) || defined(CONFIG_UPROBE_EVENTS )"\t Format: p[:[<group>/][<event>]] <place> [<args>]\n"
@@ -4305,6 +4309,9 @@ static const char readme_msg[] ="\t f[:[<group>/][<event>]] <func-name>[%return] [<args>]\n" "\t t[:[<group>/][<event>]] <tracepoint> [<args>]\n"#endif +#ifdef CONFIG_WPROBE_EVENTS + "\t w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]\n" +#endif #ifdef CONFIG_HIST_TRIGGERS "\t s:[synthetic/]<event> <field> [<field>]\n"#endif
kernel/trace/trace.h +5 -0
@@ -179,6 +179,11 @@ struct fexit_trace_entry_head {unsigned long ret_ip; }; +struct wprobe_trace_entry_head { +struct trace_entry ent; +unsigned long ip; +}; +#define TRACE_BUF_SIZE 1024struct trace_array ;
kernel/trace/trace_probe.c +21 -1
@@ -1344,6 +1344,24 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,return 0 ; } +/* wprobe only support "$addr" and "$value" variable */ +if (ctx->flags &TPARG_FL_WPROBE ) { +if (!strcmp (arg,"addr" )) { + code->op =FETCH_OP_BADDR ; +return 0 ; + } +if (!strcmp (arg,"value" )) { + code->op =FETCH_OP_BADDR ; + code++; + code->op =FETCH_OP_DEREF ; + code->offset =0 ; + *pcode = code; +return 0 ; + } + err =TP_ERR_BAD_VAR ; + goto inval; + } +if (str_has_prefix (arg,"retval" )) {if (!(ctx->flags &TPARG_FL_RETURN )) { err =TP_ERR_RETVAL_ON_PROBE ;
@@ -1491,8 +1509,9 @@ parse_probe_arg(char *arg, const struct fetch_type *type, ret =parse_probe_vars (arg, type, pcode, end, ctx);break ; +#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API case '%' :/* named register */ -if (ctx->flags & (TPARG_FL_TEVENT |TPARG_FL_FPROBE )) { +if (ctx->flags & (TPARG_FL_TEVENT |TPARG_FL_FPROBE |TPARG_FL_WPROBE )) {/* eprobe and fprobe do not handle registers */ trace_probe_log_err (ctx->offset,BAD_VAR );break ;
@@ -1505,6 +1524,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type, } elsetrace_probe_log_err (ctx->offset,BAD_REG_NAME );break ; +#endif case '@' :/* memory, file-offset or symbol */ if (isdigit (arg[1 ])) {
kernel/trace/trace_probe.h +7 -1
@@ -90,6 +90,7 @@ typedef int (*print_type_func_t)(struct trace_seq *, void *, void *);FETCH_OP (STACK , param),/* Stack: .param = index */ \FETCH_OP (STACKP , none),/* Stack pointer */ \FETCH_OP (RETVAL , none),/* Return value */ \ +FETCH_OP (BADDR , none),/* Break address */ \FETCH_OP (IMM , imm),/* Immediate: .immediate */ \FETCH_OP (COMM , none),/* Current comm */ \ FETCH_OP(CURRENT, none), /* Current task_struct address */\
@@ -419,6 +420,7 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)#define TPARG_FL_USER BIT(4)#define TPARG_FL_FPROBE BIT(5)#define TPARG_FL_TPOINT BIT(6) +#define TPARG_FL_WPROBE BIT(7)#define TPARG_FL_LOC_MASK GENMASK(4, 0)static inline bool tparg_is_function_entry (unsigned int flags)
@@ -600,7 +602,11 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,C (TYPECAST_SYM_OFFSET ,"@SYM+/-OFFSET with typecast needs parentheses" ), \C (TYPECAST_NOT_ALIGNED ,"Typecast field option is not byte-aligned" ), \C (TYPECAST_BAD_ARROW ,"Typecast field option does not support -> operator" ), \ -C (NOSUP_PERCPU ,"Per-cpu variable access is only for kernel probes" ), +C (NOSUP_PERCPU ,"Per-cpu variable access is only for kernel probes" ), \ +C (BAD_ACCESS_FMT ,"Access memory address requires @" ), \ +C (BAD_ACCESS_TYPE ,"Bad memory access type" ), \ +C (BAD_ACCESS_LEN ,"This memory access length is not supported" ), \ +C (BAD_ACCESS_ADDR ,"Invalid access memory address" ), #undef C #define C(a, b) TP_ERR_##a
kernel/trace/trace_wprobe.c new file +692 -0
692 lines not on this page · raw message
[email protected]
AI reviewRe: [PATCH v7 01/10] tracing: wprobe: Add watchpoint probe event based on hardware breakpoint
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider: - [High] Missing error rollback in enable_trace_wprobe() leaves dangling event file links and active…
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Missing error rollback in enable_trace_wprobe() leaves dangling event file links and active hardware breakpoints, leading to Use-After-Free and resource leaks.
- [High] Format string bugs in trace_wprobe_show break the dynamic_events save and restore functionality.
- [Medium] wprobes silently drop all perf events because the hardware breakpoint handler lacks perf ring buffer delivery logic.
- [Medium] wprobe_trace_handler silently drops events if the hardware breakpoint address was updated, contradicting the stated design.
- [Medium] Unconditional memory leak of the dynamically allocated symbol string in __trace_wprobe_create().
- [Medium] Undocumented $value variable is implemented, contradicting the commit message claims.
--
--- Patch [1]: [PATCH v7 01/10] tracing: wprobe: Add watchpoint probe event based on hardware breakpoint --- commit f8a7aeb231575f15f9227ab411134bde7408e480
Author: Masami Hiramatsu (Google) <[email protected]>
tracing: wprobe: Add watchpoint probe event based on hardware breakpoint
This patch adds a new dynamic event for hardware breakpoints, called a wprobe-event. It allows users to trace memory access at specified addresses or symbols, supporting both ftrace and perf interfaces.
Quoted 2 lines
User also can use $addr to fetch the accessed address. But no other variables are supported. To record updated value, use '+0($addr)'.
[Severity: Medium]
Is this documentation accurate?
Looking at parse_probe_vars(), it explicitly parses and handles a $value variable:
parse_probe_vars() {
...
if (!strcmp(arg, "value")) {
code->op = FETCH_OP_BADDR;
code++;
code->op = FETCH_OP_DEREF;...
Should the commit message be updated to reflect that $value is actually supported?
Quoted 5 lines
kernel/trace/trace_wprobe.c new file +0
-0
[ ... ]
Quoted 5 lines
kernel/trace/trace_wprobe.c new file +5
-0+static void wprobe_trace_handler (struct trace_wprobe *tw, +unsigned long addr, +struct pt_regs *regs, +struct trace_event_file *trace_file) +{
[ ... ]
kernel/trace/trace_wprobe.c line 139
Quoted 5 lines
kernel/trace/trace_wprobe.c new file +5
-0+if (trace_trigger_soft_disabled (trace_file)) +return ; + +if (tw->addr != addr) +return ;
[Severity: Medium]
Does this strict address comparison inadvertently drop valid events if the hardware breakpoint address was updated dynamically?
The v7 changelog mentions using the dynamic address because the original address can be updated from another CPU, but this check causes the handler to exit immediately if the updated address differs from the statically recorded tw->addr.
[ ... ]
kernel/trace/trace_wprobe.c line 165
Quoted 11 lines
kernel/trace/trace_wprobe.c new file +11
-0+static void wprobe_perf_handler (struct perf_event *bp, +struct perf_sample_data *data, +struct pt_regs *regs) +{ +struct trace_wprobe *tw = bp->overflow_handler_context; +struct event_file_link *link; +unsigned long addr = bp->attr.bp_addr; + +trace_probe_for_each_link_rcu (link, &tw->tp) +wprobe_trace_handler (tw, addr, regs, link->file); +}
[Severity: Medium]
Does this handler omit the necessary logic to generate and submit perf samples?
Since wprobe_register() accepts TRACE_REG_PERF_REGISTER, users can open these events via the perf syscalls.
However, this handler only iterates over ftrace ring buffer links and calls wprobe_trace_handler().
Without perf ring buffer delivery logic, will perf recordings of wprobe events contain zero samples?
[ ... ]
Quoted 3 lines
kernel/trace/trace_wprobe.c new file +3
-0+static int enable_trace_wprobe (struct trace_event_call *call, +struct trace_event_file *file) +{
[ ... ]
Quoted 10 lines
kernel/trace/trace_wprobe.c new file +10
-0+if (file) { + ret =trace_probe_add_file (tp, file); +if (ret) +return ret; + }else { +trace_probe_set_flag (tp,TP_FLAG_PROFILE ); + } + +if (!enabled) { +list_for_each_entry (tw,trace_probe_probe_list (tp), tp.list) {
kernel/trace/trace_wprobe.c line 406
Quoted 10 lines
kernel/trace/trace_wprobe.c new file +10
-0+ ret =__register_trace_wprobe (tw); +if (ret <0 ) { +/* TODO: rollback */ +return ret; + } + } + } + +return 0 ; +}
[Severity: High]
Could this missing rollback logic lead to a use-after-free or memory corruption if a later hardware breakpoint registration fails?
When __register_trace_wprobe() fails partway through the loop, the function returns immediately without unlinking the trace_event_file (via trace_probe_remove_file) or unregistering any successfully enabled sibling breakpoints.
If the user then deletes the trace instance, the trace_event_file is freed. Should one of the still-active breakpoints fire later, wprobe_perf_handler() would iterate over the corrupted file list and dereference the freed memory:
wprobe_perf_handler() {
...
trace_probe_for_each_link_rcu(link, &tw->tp)
wprobe_trace_handler(tw, addr, regs, link->file);
}[ ... ]
Quoted 3 lines
kernel/trace/trace_wprobe.c new file +3
-0+static int parse_address_spec (const char *spec,unsigned long *addr,int *type, +int *len,char **symbol) +{
[ ... ]
Quoted 11 lines
kernel/trace/trace_wprobe.c new file +11
-0+if (kstrtoul (at +1 ,0 , &_addr) !=0 ) { +char *off_str =strpbrk (at +1 ,"+-" ); +int offset =0 ; + +if (off_str) { +if (kstrtoint (off_str,0 , &offset) !=0 ) { +trace_probe_log_err (off_str - _spec,BAD_PROBE_ADDR ); +return -EINVAL ; + } + *off_str ='\0' ; + }
kernel/trace/trace_wprobe.c line 549
Quoted 10 lines
kernel/trace/trace_wprobe.c new file +10
-0+ _addr =kallsyms_lookup_name (at +1 ); +if (!_addr) { +trace_probe_log_err (at +1 - _spec,BAD_ACCESS_ADDR ); +return -ENOENT ; + } + _addr += offset; + *symbol =kstrdup (at +1 ,GFP_KERNEL ); +if (!*symbol) +return -ENOMEM ; + }
[Severity: High]
Could the null terminator injection here break dynamic_events save and restore functionality for events with symbol offsets?
When the event is created with a symbol offset (like symbol+8), *off_str is overwritten with a null byte. This truncates the string before it gets duplicated into *symbol.
Later, trace_wprobe_show() outputs the truncated base symbol without the offset, meaning the restored event will monitor the wrong memory address.
[ ... ]
Quoted 2 lines
kernel/trace/trace_wprobe.c new file +2
-0+static int __trace_wprobe_create (int argc,const char *argv[]) +{
[ ... ]
Quoted 5 lines
kernel/trace/trace_wprobe.c new file +5
-0+char *symbol =NULL ; +unsigned long addr; +int len, type, i; +int ret =0 ; +
[ ... ]
kernel/trace/trace_wprobe.c line 604
Quoted 12 lines
kernel/trace/trace_wprobe.c new file +12
-0+trace_probe_log_set_index (1 ); + ret =parse_address_spec (argv[1 ], &addr, &type, &len, &symbol); +if (ret <0 ) +return ret; + +if (!event) + event = symbol ? symbol :"wprobe" ; + + argc -=2 ; argv +=2 ; + tw =alloc_trace_wprobe (group, event, symbol, addr, len, type, argc); +if (IS_ERR (tw)) +return PTR_ERR (tw);
[Severity: Medium]
Does this leak the dynamically allocated symbol string?
The symbol variable is populated with an allocated string from parse_address_spec(). When passed to alloc_trace_wprobe(), that function duplicates the string again:
alloc_trace_wprobe() {
...
if (symbol) {
tw->symbol = kstrdup(symbol, GFP_KERNEL);
...
}However, __trace_wprobe_create() never frees the original symbol string on either the success or error paths.
[ ... ]
Quoted 6 lines
kernel/trace/trace_wprobe.c new file +6
-0+static int trace_wprobe_show (struct seq_file *m,struct dyn_event *ev) +{ +struct trace_wprobe *tw =to_trace_wprobe (ev); +int i; + + seq_printf(m, "w:%s/%s", trace_probe_group_name(&tw->tp),
kernel/trace/trace_wprobe.c line 662
Quoted 10 lines
kernel/trace/trace_wprobe.c new file +10
-0+ trace_probe_name(&tw->tp)); + +char type_char; + +if (tw->type ==HW_BREAKPOINT_R ) + type_char ='r' ; +else if (tw->type ==HW_BREAKPOINT_W ) + type_char ='w' ; +else + type_char ='x' ;/* Should be rw */
[Severity: High]
Will outputting 'x' for HW_BREAKPOINT_RW cause parse_address_spec() to reject the configuration upon restore?
If type_char is set to 'x', the formatted string saved to dynamic_events will be invalid. When the system attempts to restore the event across reboots, parse_address_spec() checks strictly for 'r', 'w', or 'rw', and will fail with -EINVAL for 'x'.
Should this be adjusted to output a string like "rw" instead?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=1
Masami Hiramatsu (Google)
maintainerRe: [PATCH v7 01/10] tracing: wprobe: Add watchpoint probe event based on hardware breakpoint
Quoted 7 lines · On Wed, 15 Jul 2026 02:01:39 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Missing error rollback inenable_trace_wprobe()leaves dangling event file links and active hardware breakpoints, leading to Use-After-Free and resource leaks.
- [High] Format string bugs intrace_wprobe_showbreak thedynamic_eventssave and restore functionality.
- [Medium] wprobes silently drop all perf events because the hardware breakpoint handler lacks perf ring buffer delivery logic.
- [Medium]wprobe_trace_handlersilently drops events if the hardware breakpoint address was updated, contradicting the stated design.
- [Medium] Unconditional memory leak of the dynamically allocatedsymbolstring in__trace_wprobe_create().
- [Medium] Undocumented$valuevariable is implemented, contradicting the commit message claims.
Thanks for the comments.
Quoted 29 lines
--
--- Patch [1]: [PATCH v7 01/10] tracing: wprobe: Add watchpoint probe event based on hardware breakpoint --- commit
f8a7aeb231575f15f9227ab411134bde7408e480
Author: Masami Hiramatsu (Google) <[email protected]>tracing: wprobe: Add watchpoint probe event based on hardware breakpoint
This patch adds a new dynamic event for hardware breakpoints, called a wprobe-event. It allows users to trace memory access at specified addresses or symbols, supporting both ftrace and perf interfaces.
Quoted 2 lines
User also can use $addr to fetch the accessed address. But no other variables are supported. To record updated value, use '+0($addr)'.
[Severity: Medium]
Is this documentation accurate?Looking at
parse_probe_vars(), it explicitly parses and handles a $value variable:parse_probe_vars() { ... if (!strcmp(arg, "value")) { code->op = FETCH_OP_BADDR; code++; code->op = FETCH_OP_DEREF;
Quoted 4 lines
...
Should the commit message be updated to reflect that $value is actually supported?
Good catch! Yeah $value was added, so we don't need to use +0($addr). The $addr is used for ensuring watching address. That will be useful when wprobe trigger is introduced.
Quoted 17 lines
Quoted 6 lines
kernel/trace/trace_wprobe.c new file +0
-0[ ... ]
Quoted 5 lines
kernel/trace/trace_wprobe.c new file +5
-0+static void wprobe_trace_handler (struct trace_wprobe *tw, +unsigned long addr, +struct pt_regs *regs, +struct trace_event_file *trace_file) +{[ ... ]
Quoted 9 lines
kernel/trace/trace_wprobe.c line 139
Quoted 5 lines
kernel/trace/trace_wprobe.c new file +5
-0+if (trace_trigger_soft_disabled (trace_file)) +return ; + +if (tw->addr != addr) +return ;[Severity: Medium]
Does this strict address comparison inadvertently drop valid events if the hardware breakpoint address was updated dynamically?
Yes, to avoid confusion, if the watchpoint is changed on another CPU, it is better to be skipped.
Quoted 5 lines
The v7 changelog mentions using the dynamic address because the original address can be updated from another CPU, but this check causes the handler to exit immediately if the updated address differs from the statically recorded
tw->addr.
Yeah, this is something for wprobe trigger, which will dynamically update the tw->addr. So maybe we can move this in wprobe event trigger patch.
Quoted 18 lines
[ ... ]
kernel/trace/trace_wprobe.c line 165
Quoted 11 lines
kernel/trace/trace_wprobe.c new file +11
-0+static void wprobe_perf_handler (struct perf_event *bp, +struct perf_sample_data *data, +struct pt_regs *regs) +{ +struct trace_wprobe *tw = bp->overflow_handler_context; +struct event_file_link *link; +unsigned long addr = bp->attr.bp_addr; + +trace_probe_for_each_link_rcu (link, &tw->tp) +wprobe_trace_handler (tw, addr, regs, link->file); +}[Severity: Medium]
Does this handler omit the necessary logic to generate and submit perf samples?
Ah, OK. This should not be used from perf. I think this should be explicitly prohibited at this moment.
Quoted 25 lines
[ ... ]
Quoted 3 lines
kernel/trace/trace_wprobe.c new file +3
-0+static int enable_trace_wprobe (struct trace_event_call *call, +struct trace_event_file *file) +{[ ... ]
Quoted 16 lines
kernel/trace/trace_wprobe.c new file +16
-0+if (file) { + ret =trace_probe_add_file (tp, file); +if (ret) +return ret; + }else { +trace_probe_set_flag (tp,TP_FLAG_PROFILE ); + } + +if (!enabled) { +list_for_each_entry (tw,trace_probe_probe_list (tp), tp.list) { + ret =__register_trace_wprobe (tw); +if (ret <0 ) { +/* TODO: rollback */ +return ret; + } + }
Quoted 8 lines
kernel/trace/trace_wprobe.c line 188
Quoted 4 lines
kernel/trace/trace_wprobe.c new file +4
-0+ } + +return 0 ; +}[Severity: High]
Could this missing rollback logic lead to a use-after-free or memory corruption if a later hardware breakpoint registration fails?
OK, to avoid memory corruption add a rollback logic here.
Quoted 34 lines
[ ... ]
Quoted 3 lines
kernel/trace/trace_wprobe.c new file +3
-0+static int parse_address_spec (const char *spec,unsigned long *addr,int *type, +int *len,char **symbol) +{[ ... ]
Quoted 11 lines
kernel/trace/trace_wprobe.c new file +11
-0+if (kstrtoul (at +1 ,0 , &_addr) !=0 ) { +char *off_str =strpbrk (at +1 ,"+-" ); +int offset =0 ; + +if (off_str) { +if (kstrtoint (off_str,0 , &offset) !=0 ) { +trace_probe_log_err (off_str - _spec,BAD_PROBE_ADDR ); +return -EINVAL ; + } + *off_str ='\0' ; + }kernel/trace/trace_wprobe.c line 549
Quoted 10 lines
kernel/trace/trace_wprobe.c new file +10
-0+ _addr =kallsyms_lookup_name (at +1 ); +if (!_addr) { +trace_probe_log_err (at +1 - _spec,BAD_ACCESS_ADDR ); +return -ENOENT ; + } + _addr += offset; + *symbol =kstrdup (at +1 ,GFP_KERNEL ); +if (!*symbol) +return -ENOMEM ; + }[Severity: High]
Could the null terminator injection here breakdynamic_eventssave and restore functionality for events with symbol offsets?
Quoted 6 lines
When the event is created with a symbol offset (like symbol+8),
*off_stris overwritten with a null byte. This truncates the string before it gets duplicated into *symbol.Later,
trace_wprobe_show()outputs the truncated base symbol without the offset, meaning the restored event will monitor the wrong memory address.
Since the symbol is used for making the event name, it should not have offset. However, in trace_wprobe_show(), the symbol is directly used to show the target address. Thus, instead of changing parse_address_spec(), we need to update trace_wprobe_show() to use kallsyms_lookup_name() to find the symbol address and calculate the offset from tw->addr and symbol address, and print out the "symbol+offset".
Quoted 43 lines
[ ... ]
Quoted 2 lines
kernel/trace/trace_wprobe.c new file +2
-0+static int __trace_wprobe_create (int argc,const char *argv[]) +{[ ... ]
Quoted 5 lines
kernel/trace/trace_wprobe.c new file +5
-0+char *symbol =NULL ; +unsigned long addr; +int len, type, i; +int ret =0 ; +[ ... ]
kernel/trace/trace_wprobe.c line 604
Quoted 12 lines
kernel/trace/trace_wprobe.c new file +12
-0+trace_probe_log_set_index (1 ); + ret =parse_address_spec (argv[1 ], &addr, &type, &len, &symbol); +if (ret <0 ) +return ret; + +if (!event) + event = symbol ? symbol :"wprobe" ; + + argc -=2 ; argv +=2 ; + tw =alloc_trace_wprobe (group, event, symbol, addr, len, type, argc); +if (IS_ERR (tw)) +return PTR_ERR (tw);[Severity: Medium]
Does this leak the dynamically allocated symbol string?The symbol variable is populated with an allocated string from
parse_address_spec(). When passed toalloc_trace_wprobe(), that function duplicates the string again:alloc_trace_wprobe() { ... if (symbol) { tw->symbol = kstrdup(symbol, GFP_KERNEL); ... }
Quoted 2 lines
However,
__trace_wprobe_create()never frees the original symbol string on either the success or error paths.
Yes, let me use __free() macro to free it.
Quoted 24 lines
[ ... ]
Quoted 6 lines
kernel/trace/trace_wprobe.c new file +6
-0+static int trace_wprobe_show (struct seq_file *m,struct dyn_event *ev) +{ +struct trace_wprobe *tw =to_trace_wprobe (ev); +int i; + + seq_printf(m, "w:%s/%s", trace_probe_group_name(&tw->tp),kernel/trace/trace_wprobe.c line 662
Quoted 10 lines
kernel/trace/trace_wprobe.c new file +10
-0+ trace_probe_name(&tw->tp)); + +char type_char; + +if (tw->type ==HW_BREAKPOINT_R ) + type_char ='r' ; +else if (tw->type ==HW_BREAKPOINT_W ) + type_char ='w' ; +else + type_char ='x' ;/* Should be rw */ [Severity: High]
Will outputting 'x' forHW_BREAKPOINT_RWcauseparse_address_spec()to reject the configuration upon restore?
Quoted 6 lines
If
type_charis set to 'x', the formatted string saved todynamic_eventswill be invalid. When the system attempts to restore the event across reboots,parse_address_spec()checks strictly for 'r', 'w', or 'rw', and will fail with-EINVALfor 'x'.Should this be adjusted to output a string like "rw" instead?
Ah, good catch. Let me use rw instead.
Thank you,
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>
Masami Hiramatsu (Google)
maintainerPATCH v7 2/10 x86: hw_breakpoint: Add a kconfig to clarify when a breakpoint fires
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add CONFIG_HAVE_POST_BREAKPOINT_HOOK which indicates the hw_breakpoint on that architecture fires after the target memory has been modified. This is currently x86 only behavior.
--- arch/Kconfig | 10 ++++++++++ arch/x86/Kconfig | 1 + kernel/trace/Kconfig | 1 + 3 files changed, 12 insertions(+)
arch/Kconfig +10 -0
@@ -457,6 +457,16 @@ config HAVE_MIXED_BREAKPOINTS_REGS Select this option if your arch implements breakpoints under the latter fashion. +config HAVE_POST_BREAKPOINT_HOOK + bool + depends on HAVE_HW_BREAKPOINT + help + Depending on the arch implementation of hardware breakpoints, + some of them provide breakpoint hook after the target memory + is modified. + Select this option if your arch implements breakpoints overflow + handler hooks after the target memory is modified. + config HAVE_USER_RETURN_NOTIFIER bool
arch/x86/Kconfig +1 -0
@@ -246,6 +246,7 @@ config X86 select HAVE_FUNCTION_TRACER select HAVE_GCC_PLUGINS select HAVE_HW_BREAKPOINT + select HAVE_POST_BREAKPOINT_HOOK select HAVE_IOREMAP_PROT select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64 select HAVE_IRQ_TIME_ACCOUNTING
kernel/trace/Kconfig +1 -0
@@ -866,6 +866,7 @@ config WPROBE_EVENTS bool "Enable wprobe-based dynamic events" depends on TRACING depends on HAVE_HW_BREAKPOINT + depends on HAVE_POST_BREAKPOINT_HOOK select PROBE_EVENTS select DYNAMIC_EVENTS help
[email protected]
AI reviewRe: [PATCH v7 02/10] x86: hw_breakpoint: Add a kconfig to clarify when a breakpoint fires
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] Memory leak of the `symbol` string in `__trace_wprobe_create()`. - [Medium]…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Memory leak of the symbol string in __trace_wprobe_create().
- [Medium] enable_trace_wprobe() fails to rollback trace probe state on hardware breakpoint registration error, leading to state inconsistency and silent tracing failures. --
--- Patch [2]: [PATCH v7 02/10] x86: hw_breakpoint: Add a kconfig to clarify when a breakpoint fires --- commit f8a7aeb231575f15f9227ab411134bde7408e480
Author: Masami Hiramatsu (Google) <[email protected]>
tracing: wprobe: Add watchpoint probe event based on hardware breakpoint
Add a new probe event for the hardware breakpoint called wprobe-event. This wprobe allows user to trace (watch) the memory access at the specified memory address. The events can be defined via the dynamic_events interface file.
Quoted 6 lines
kernel/trace/trace_wprobe.c new file +0
-0@@ -0,0 +1,692 @@
[ ... ]
Quoted 17 lines
kernel/trace/trace_wprobe.c new file +17
-0+static int enable_trace_wprobe (struct trace_event_call *call, +struct trace_event_file *file) +{ +struct trace_probe *tp; +struct trace_wprobe *tw; +bool enabled; +int ret =0 ; + + tp =trace_probe_primary_from_call (call); +if (WARN_ON_ONCE (!tp)) +return -ENODEV ; + enabled =trace_probe_is_enabled (tp); + +if (file) { + ret =trace_probe_add_file (tp, file); +if (ret) +return ret;
kernel/trace/trace_wprobe.c
Quoted 10 lines
kernel/trace/trace_wprobe.c new file +10
-0+ }else { +trace_probe_set_flag (tp,TP_FLAG_PROFILE ); + } + +if (!enabled) { +list_for_each_entry (tw,trace_probe_probe_list (tp), tp.list) { + ret =__register_trace_wprobe (tw); +if (ret <0 ) { +/* TODO: rollback */ +return ret;
[Severity: Medium]
Will this leave the trace probe state inconsistent if hardware breakpoint registration fails?
Earlier in enable_trace_wprobe(), trace_probe_add_file() or trace_probe_set_flag() modify the probe state. If __register_trace_wprobe() returns an error, the function returns immediately without rolling back those changes.
Could this cause subsequent enable attempts to skip registration entirely and silently fail to trace?
Quoted 6 lines
kernel/trace/trace_wprobe.c new file +6
-0+ } + } + } + +return 0 ; +}
[ ... ]
Quoted 38 lines
kernel/trace/trace_wprobe.c new file +38
-0+static int __trace_wprobe_create (int argc,const char *argv[]) +{ +/* +* Argument syntax: +* b[:[GRP/][EVENT]] SPEC +* +* SPEC: +* [r|w|rw]@[ADDR|SYMBOL[+OFFS]][:LEN] +*/ + struct traceprobe_parse_context *ctx __free(traceprobe_parse_context) = NULL; + struct trace_wprobe *tw __free(free_trace_wprobe) = NULL; +const char *event =NULL , *group =WPROBE_EVENT_SYSTEM ; + const char *tplog __free(trace_probe_log_clear) = NULL; +char *symbol =NULL ; +unsigned long addr; +int len, type, i; +int ret =0 ; + +if (argv[0 ][0 ] !='w' ) +return -ECANCELED ; + +if (argc <2 ) +return -EINVAL ; + + tplog =trace_probe_log_init ("wprobe" , argc, argv); + +if (argv[0 ][1 ] !='\0' ) { +if (argv[0 ][1 ] !=':' ) { +trace_probe_log_set_index (0 ); +trace_probe_log_err (1 ,BAD_MAXACT_TYPE ); +/* Invalid format */ +return -EINVAL ; + } + event = &argv[0 ][2 ]; + } + +trace_probe_log_set_index (1 ); + ret =parse_address_spec (argv[1 ], &addr, &type, &len, &symbol);
kernel/trace/trace_wprobe.c
Quoted 10 lines
kernel/trace/trace_wprobe.c new file +10
-0+if (ret <0 ) +return ret; + +if (!event) + event = symbol ? symbol :"wprobe" ; + + argc -=2 ; argv +=2 ; + tw =alloc_trace_wprobe (group, event, symbol, addr, len, type, argc); +if (IS_ERR (tw)) +return PTR_ERR (tw);
[Severity: Medium]
Does this code leak the symbol string?
When a wprobe event is created using a symbol name, parse_address_spec() allocates a string for symbol. This string is then passed to alloc_trace_wprobe() which duplicates it again using kstrdup().
However, the original symbol pointer does not appear to be freed before __trace_wprobe_create() returns. Should symbol be annotated with __free(kfree) or explicitly freed?
Quoted 4 lines
kernel/trace/trace_wprobe.c new file +4
-0+ + ctx =kzalloc_obj (*ctx); +if (!ctx) +return -ENOMEM ;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=2
Masami Hiramatsu (Google)
maintainerPATCH v7 3/10 selftests: tracing: Add a basic testcase for wprobe
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add 'add_remove_wprobe.tc' testcase for testing wprobe event that tests adding and removing operations of the wprobe event.
--- tools/testing/selftests/ftrace/config | 1 .../ftrace/test.d/dynevent/add_remove_wprobe.tc | 68 ++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc
tools/testing/selftests/ftrace/config +1 -0
@@ -27,3 +27,4 @@ CONFIG_STACK_TRACER=y CONFIG_TRACER_SNAPSHOT=y CONFIG_UPROBES=y CONFIG_UPROBE_EVENTS=y +CONFIG_WPROBE_EVENTS=y
tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc new file +68 -0
68 lines not on this page · raw message
[email protected]
AI reviewRe: [PATCH v7 03/10] selftests: tracing: Add a basic testcase for wprobe
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The test script uses '!grep' without a space, causing a shell error that silently bypasses trace…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The test script uses '!grep' without a space, causing a shell error that silently bypasses trace validation. Additionally, the check lacks an 'exit_fail' call. --
--- Patch [3]: [PATCH v7 03/10] selftests: tracing: Add a basic testcase for wprobe --- commit 430a459576b6f13049e560d4a90b2c6a987f1ad3
Author: Masami Hiramatsu (Google) <[email protected]>
selftests: tracing: Add a basic testcase for wprobe
This commit adds a basic selftest testcase to verify adding and removing wprobe dynamic events.
Quoted 4 lines
tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc new file +0
-0
[ ... ]
tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc line 42
Quoted 8 lines
tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc new file +8
-0+# Let some time pass to trigger the breakpoint +sleep 1 + +# Check if we got any trace output +if !grep -q my_wprobe trace; then + echo "wprobe event was not triggered" +fi +
[Severity: Medium]
Will this check silently bypass trace validation? When the shell evaluates "!grep", it treats it as a single command name rather than negating the grep command, which typically results in a 'command not found' error.
Because the shell error results in a non-zero exit code, the if condition sees this as false and entirely skips the block. Furthermore, even if written correctly as "! grep", the block misses an exit_fail call, so the test would still pass if the event failed to trigger.
Could this be updated to add a space after the exclamation mark and an exit_fail call?
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=3
Masami Hiramatsu (Google)
maintainerPATCH v7 4/10 selftests: tracing: Add syntax testcase for wprobe
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add "wprobe_syntax_errors.tc" testcase for testing syntax errors of the watch probe events.
--- .../test.d/dynevent/wprobes_syntax_errors.tc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.tc
tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.tc new file +20 -0
@@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Watch probe event parser error log check +# requires: dynamic_events "w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]":README + +check_error() { # command-with-error-pos-by-^ + ftrace_errlog_check 'wprobe' "$1" 'dynamic_events' +} + +check_error 'w ^symbol' # BAD_ACCESS_FMT +check_error 'w ^a@symbol' # BAD_ACCESS_TYPE +check_error 'w w@^symbol' # BAD_ACCESS_ADDR +check_error 'w w@jiffies^+offset' # BAD_ACCESS_ADDR +check_error 'w w@jiffies:^100' # BAD_ACCESS_LEN +check_error 'w w@jiffies ^$arg1' # BAD_VAR +check_error 'w w@jiffies ^$retval' # BAD_VAR +check_error 'w w@jiffies ^$stack' # BAD_VAR +check_error 'w w@jiffies ^%ax' # BAD_VAR + +exit 0
Masami Hiramatsu (Google)
maintainerPATCH v7 5/10 tracing: wprobe: Use a new seq_print_ip_sym_offset() wrapper
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Use a new seq_print_ip_sym_offset() wrapper function instead of using TRACE_ITER(SYM_OFFSET) mask directly.
--- kernel/trace/trace_wprobe.c | 1 + 1 file changed, 1 insertion(+)
kernel/trace/trace_wprobe.c +1 -0
@@ -20,6 +20,7 @@#include <asm/ptrace.h> #include "trace_dynevent.h" +#include "trace_output.h" #include "trace_probe.h" #include "trace_probe_kernel.h" #include "trace_probe_tmpl.h"
[email protected]
AI reviewRe: [PATCH v7 05/10] tracing: wprobe: Use a new seq_print_ip_sym_offset() wrapper
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] Symbol offsets are discarded in wprobe definitions when displayed via tracefs, preventing…
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Symbol offsets are discarded in wprobe definitions when displayed via tracefs, preventing round-tripping of event specifications.
- [High] Failure to register hardware breakpoints leaves the trace_probe in a corrupted, pseudo-enabled state, causing future enablement attempts to silently fail.
- [High] The dynamically allocated symbol string is permanently leaked every time a wprobe with a symbol is created.
- [Medium] Meaningful code and commit message mismatch regarding the use of a new wrapper function.
--
--- Patch [5]: [PATCH v7 05/10] tracing: wprobe: Use a new seq_print_ip_sym_offset() wrapper --- commit f3a4507e611a20fcf1578dc82ce81258e7452d39
Author: Masami Hiramatsu (Google) <[email protected]>
tracing: wprobe: Use a new seq_print_ip_sym_offset() wrapper
The commit message states it refactors code to use a new seq_print_ip_sym_offset() wrapper. However, the commit only adds an include statement, missing the expected implementation changes.
Quoted 4 lines
tracing: wprobe: Use a new
seq_print_ip_sym_offset()wrapperUse a new
seq_print_ip_sym_offset()wrapper function instead of usingTRACE_ITER(SYM_OFFSET)mask directly.
[Severity: Medium]
Is this commit missing the actual implementation changes? The commit message claims to refactor code to use a new wrapper function, but the diff only adds an include directive.
kernel/trace/trace_wprobe.c line 26
Quoted 12 lines
kernel/trace/trace_wprobe.c +1
-0@@ -20,6 +20,7 @@#include <asm/ptrace.h> #include "trace_dynevent.h" +#include "trace_output.h" #include "trace_probe.h" #include "trace_probe_kernel.h" #include "trace_probe_tmpl.h"
I also noticed a few other concerns in this file:
[Severity: High]
Does parse_address_spec() drop the offset from the base symbol?
kernel/trace/trace_wprobe.c:parse_address_spec() {
...
if (off_str) {
if (kstrtoint(off_str, 0, &offset) != 0) {
trace_probe_log_err(off_str - _spec, BAD_PROBE_ADDR);
return -EINVAL;
}
*off_str = '\0';
...
}If the offset string is stripped and discarded here, it causes the offset to be dropped from the probe's symbol representation.
When trace_wprobe_show() emits the symbol, it is truncated:
kernel/trace/trace_wprobe.c:trace_wprobe_show() {
...
if (tw->symbol)
seq_printf(m, " %c@%s:%d", type_char, tw->symbol, len);
...
}If a user reads the event definition via tracefs, it will display the probe as simply w@jiffies instead of w@jiffies+10. If this definition is saved and loaded back, it attaches to the base symbol instead of the offset address, leading to incorrect behavior.
[Severity: High]
Does enable_trace_wprobe() fail to handle rollback properly on error?
kernel/trace/trace_wprobe.c:enable_trace_wprobe() {
...
if (!enabled) {
list_for_each_entry(tw, trace_probe_probe_list(tp), tp.list) {
ret = __register_trace_wprobe(tw);
if (ret < 0) {
/* TODO: rollback */
return ret;
}
}
}
...
}If __register_trace_wprobe() fails, it returns an error immediately without clearing the flags set earlier (TP_FLAG_PROFILE or file links).
Could this missing rollback leave the trace_probe in an incorrectly enabled state, causing future enablement attempts to silently fail because trace_probe_is_enabled() will return true?
[Severity: High]
Does this code leak the symbol string?
kernel/trace/trace_wprobe.c:parse_address_spec() {
...
_addr += offset;
*symbol = kstrdup(at + 1, GFP_KERNEL);
if (!*symbol)
return -ENOMEM;
...
}The string pointed to by symbol is allocated here. Then __trace_wprobe_create() passes it to alloc_trace_wprobe():
kernel/trace/trace_wprobe.c:__trace_wprobe_create() {
...
ret = parse_address_spec(argv[1], &addr, &type, &len, &symbol);
if (ret < 0)
return ret;
...
tw = alloc_trace_wprobe(group, event, symbol, addr, len, type, argc);
...
}But alloc_trace_wprobe() duplicates the string again:
kernel/trace/trace_wprobe.c:alloc_trace_wprobe() {
...
if (symbol) {
tw->symbol = kstrdup(symbol, GFP_KERNEL);
if (!tw->symbol)
return ERR_PTR(-ENOMEM);
}
...
}The original symbol pointer in __trace_wprobe_create() is never freed before the function returns on either success or error paths, which might leak the string memory on every wprobe creation.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=5
Masami Hiramatsu (Google)
maintainerRe: [PATCH v7 05/10] tracing: wprobe: Use a new seq_print_ip_sym_offset() wrapper
In reply to Masami Hiramatsu (Google)Oops, I missed to fold this in the 1st patch... (the seq_print_ip_sym_offset() part is already folded)
Thanks,
Quoted 25 lines · On Wed, 15 Jul 2026 10:44:57 +0900 "Masami Hiramatsu (Google)"…
From: Masami Hiramatsu (Google) <[email protected]>
Use a new
seq_print_ip_sym_offset()wrapper function instead of usingTRACE_ITER(SYM_OFFSET)mask directly.Signed-off-by Masami Hiramatsu (Google) <[email protected]>--- kernel/trace/trace_wprobe.c | 1 + 1 file changed, 1 insertion(+)kernel/trace/trace_wprobe.c +1
-0@@ -20,6 +20,7 @@#include <asm/ptrace.h> #include "trace_dynevent.h" +#include "trace_output.h" #include "trace_probe.h" #include "trace_probe_kernel.h" #include "trace_probe_tmpl.h"
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>
Masami Hiramatsu (Google)
maintainerPATCH v7 6/10 x86/hw_breakpoint: Unify breakpoint install/uninstall
In reply to Masami Hiramatsu (Google)From: Jinchao Wang <[email protected]>
Consolidate breakpoint management to reduce code duplication. The diffstat was misleading, so the stripped code size is compared instead. After refactoring, it is reduced from 11976 bytes to 11448 bytes on my x86_64 system built with clang.
This also makes it easier to introduce arch_reinstall_hw_breakpoint().
In addition, including linux/types.h to fix a missing build dependency.
--- arch/x86/include/asm/hw_breakpoint.h | 6 + arch/x86/kernel/hw_breakpoint.c | 141 +++++++++++++++++++--------------- 2 files changed, 84 insertions(+), 63 deletions(-)
arch/x86/include/asm/hw_breakpoint.h +6 -0
@@ -5,6 +5,7 @@#include <uapi/asm/hw_breakpoint.h> #define __ARCH_HW_BREAKPOINT_H +#include <linux/types.h> /* * The name should probably be something dealt in
@@ -18,6 +19,11 @@ struct arch_hw_breakpoint {u8 type; }; +enum bp_slot_action { +BP_SLOT_ACTION_INSTALL , +BP_SLOT_ACTION_UNINSTALL , +}; +#include <linux/kdebug.h> #include <linux/percpu.h> #include <linux/list.h>
arch/x86/kernel/hw_breakpoint.c +78 -63
181 lines not on this page · raw message
[email protected]
AI reviewRe: [PATCH v7 06/10] x86/hw_breakpoint: Unify breakpoint install/uninstall
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Missing compiler memory barrier during hardware breakpoint disable allows reordering that breaks…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing compiler memory barrier during hardware breakpoint disable allows reordering that breaks NMI safety. --
--- Patch [6]: [PATCH v7 06/10] x86/hw_breakpoint: Unify breakpoint install/uninstall ---
Note: The format of this review has been altered to a free-form summary due to restrictions on quoting the original patch text.
commit 16356ea39e4e8d100873d56e9298c633a58cb692
Author: Jinchao Wang <[email protected]> x86/hw_breakpoint: Unify breakpoint install/uninstall
This patch consolidates breakpoint management to reduce code duplication and binary size. It introduces manage_bp_slot() and setup_hwbp() to handle the logic previously duplicated in install and uninstall functions.
File: arch/x86/kernel/hw_breakpoint.c
Function: setup_hwbp()
[Severity: High]
Could the repositioning of the barrier() in the disable path introduce a race condition with NMI handlers?
In setup_hwbp(), when enable is false, the code writes to the per-CPU cpu_dr7 cache after updating the hardware DR7 register via set_debugreg(). However, the barrier() call was placed before set_debugreg().
Since neither the native set_debugreg() nor this_cpu_write() provides a compiler memory clobber by default, is the compiler permitted to reorder this_cpu_write(cpu_dr7, dr7) to occur before the hardware set_debugreg() update?
If an NMI interrupts exactly between the reordered memory write to cpu_dr7 and the hardware DR7 register update, the NMI entry code (local_db_save) would observe cpu_dr7 as zero and skip clearing the hardware DR7 register. If the NMI handler then touches the watched address, would this trigger an infinite #DB exception recursion and crash the system?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=6
Masami Hiramatsu (Google)
maintainerPATCH v7 7/10 x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint
In reply to Masami Hiramatsu (Google)From: Jinchao Wang <[email protected]>
The new arch_reinstall_hw_breakpoint() function can be used in an atomic context, unlike the more expensive free and re-allocation path. This allows callers to efficiently re-establish an existing breakpoint.
--- arch/x86/include/asm/hw_breakpoint.h | 2 ++ arch/x86/kernel/hw_breakpoint.c | 9 +++++++++ 2 files changed, 11 insertions(+)
arch/x86/include/asm/hw_breakpoint.h +2 -0
@@ -21,6 +21,7 @@ struct arch_hw_breakpoint {enum bp_slot_action {BP_SLOT_ACTION_INSTALL , +BP_SLOT_ACTION_REINSTALL ,BP_SLOT_ACTION_UNINSTALL , };
@@ -65,6 +66,7 @@ extern int hw_breakpoint_exceptions_notify(struct notifier_block *unused,int arch_install_hw_breakpoint (struct perf_event *bp); +int arch_reinstall_hw_breakpoint (struct perf_event *bp);void arch_uninstall_hw_breakpoint (struct perf_event *bp);void hw_breakpoint_pmu_read (struct perf_event *bp);void hw_breakpoint_pmu_unthrottle (struct perf_event *bp);
arch/x86/kernel/hw_breakpoint.c +9 -0
@@ -100,6 +100,10 @@ static int manage_bp_slot(struct perf_event *bp, enum bp_slot_action action) old_bp =NULL ; new_bp = bp;break ; +case BP_SLOT_ACTION_REINSTALL : + old_bp = bp; + new_bp = bp; +break ;case BP_SLOT_ACTION_UNINSTALL : old_bp = bp; new_bp =NULL ;
@@ -188,6 +192,11 @@ int arch_install_hw_breakpoint(struct perf_event *bp)return arch_manage_bp (bp,BP_SLOT_ACTION_INSTALL ); } +int arch_reinstall_hw_breakpoint (struct perf_event *bp) +{ +return arch_manage_bp (bp,BP_SLOT_ACTION_REINSTALL ); +} +void arch_uninstall_hw_breakpoint (struct perf_event *bp) {arch_manage_bp (bp,BP_SLOT_ACTION_UNINSTALL );
[email protected]
AI reviewRe: [PATCH v7 07/10] x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] arch_reinstall_hw_breakpoint() fails to clear old DR7 length and type bits before applying new…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] arch_reinstall_hw_breakpoint() fails to clear old DR7 length and type bits before applying new attributes, causing hardware state corruption if breakpoint parameters are modified. --
--- Patch [7]: [PATCH v7 07/10] x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint --- commit 6bcf7b8e474f5683afc4564b0a92754b65b43406
Author: Jinchao Wang <[email protected]>
x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint
This commit introduces arch_reinstall_hw_breakpoint() to allow callers to efficiently re-establish existing hardware breakpoints in an atomic context without needing the full free and re-allocation path.
Quoted 4 lines
arch/x86/kernel/hw_breakpoint.c +0
-0
[ ... ]
arch/x86/kernel/hw_breakpoint.c int arch_install_hw_breakpoint(struct perf_event *bp) · line 198
Quoted 8 lines
arch/x86/kernel/hw_breakpoint.c +4
-0@@ -188,6 +192,11 @@ int arch_install_hw_breakpoint(struct perf_event *bp)return arch_manage_bp (bp,BP_SLOT_ACTION_INSTALL ); } +int arch_reinstall_hw_breakpoint (struct perf_event *bp) +{ +return arch_manage_bp (bp,BP_SLOT_ACTION_REINSTALL ); +}
[Severity: Medium]
When callers use this new function to update an existing breakpoint's length or type attributes, will this cause hardware state corruption in the DR7 control register?
The execution path goes through arch_manage_bp() and calls setup_hwbp() with enable = true. Looking at setup_hwbp(), it appears the new control bits are bitwise OR'ed with the existing mask:
arch/x86/kernel/hw_breakpoint.c:setup_hwbp() {
...
if (enable)
dr7 |= encode_dr7(slot, info->len, info->type);
...
}Because the old DR7 configuration bits for this slot are never cleared first, could the combination of old and new bits create an invalid breakpoint configuration mask?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=7
Masami Hiramatsu (Google)
maintainerPATCH v7 8/10 HWBP: Add modify_wide_hw_breakpoint_local() API
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add modify_wide_hw_breakpoint_local() arch-wide interface which allows hwbp users to update watch address on-line. This is available if the arch supports CONFIG_HAVE_REINSTALL_HW_BREAKPOINT.
Note that this allows to change the type only for compatible types, because it does not release and reserve the hwbp slot based on type. For instance, you can not change HW_BREAKPOINT_W to HW_BREAKPOINT_X.
---
Changes in v7:
- Update bp->attr.bp_attr so that we can correctly check the address on it.
- Use -EOPNOTSUPP instead of -ENOSYS.
Changes in v4:
- Update kerneldoc comment about modify_wide_hw_breakpoint_local according to Randy's comment.
Changes in v2:
- Check type compatibility by checking slot. (Thanks Jinchao!)
--- arch/Kconfig | 10 ++++++++++ arch/x86/Kconfig | 1 + include/linux/hw_breakpoint.h | 6 ++++++ kernel/events/hw_breakpoint.c | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+)
arch/Kconfig +10 -0
@@ -467,6 +467,16 @@ config HAVE_POST_BREAKPOINT_HOOK Select this option if your arch implements breakpoints overflow handler hooks after the target memory is modified. +config HAVE_REINSTALL_HW_BREAKPOINT + bool + depends on HAVE_HW_BREAKPOINT + help + Depending on the arch implementation of hardware breakpoints, + some of them are able to update the breakpoint configuration + without release and reserve the hardware breakpoint register. + What configuration is able to update depends on hardware and + software implementation. + config HAVE_USER_RETURN_NOTIFIER bool
arch/x86/Kconfig +1 -0
@@ -247,6 +247,7 @@ config X86 select HAVE_GCC_PLUGINS select HAVE_HW_BREAKPOINT select HAVE_POST_BREAKPOINT_HOOK + select HAVE_REINSTALL_HW_BREAKPOINT select HAVE_IOREMAP_PROT select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64 select HAVE_IRQ_TIME_ACCOUNTING
include/linux/hw_breakpoint.h +6 -0
@@ -81,6 +81,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr,perf_overflow_handler_t triggered, void *context); +extern int modify_wide_hw_breakpoint_local (struct perf_event *bp, +struct perf_event_attr *attr); +extern int register_perf_hw_breakpoint (struct perf_event *bp);extern void unregister_hw_breakpoint (struct perf_event *bp);extern void unregister_wide_hw_breakpoint (struct perf_event *__percpu *cpu_events);
@@ -124,6 +127,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr,perf_overflow_handler_t triggered, void *context) { return NULL; }static inline int +modify_wide_hw_breakpoint_local (struct perf_event *bp, +struct perf_event_attr *attr) {return -EOPNOTSUPP ; } +static inline int register_perf_hw_breakpoint (struct perf_event *bp) {return -ENOSYS ; }static inline void unregister_hw_breakpoint (struct perf_event *bp) { } static inline void
kernel/events/hw_breakpoint.c +39 -0
@@ -888,6 +888,45 @@ void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events) }EXPORT_SYMBOL_GPL (unregister_wide_hw_breakpoint); +/** +* modify_wide_hw_breakpoint_local - update breakpoint config for local CPU +* @bp: the hwbp perf event for this CPU +* @attr: the new attribute for @bp +* +* This does not release and reserve the slot of a HWBP; it just reuses the +* current slot on local CPU. So the users must update the other CPUs by +* themselves. +* Also, since this does not release/reserve the slot, this can not change the +* type to incompatible type of the HWBP. +* Return err if attr is invalid or the CPU fails to update debug register +* for new @attr. +*/ +#ifdef CONFIG_HAVE_REINSTALL_HW_BREAKPOINT +int modify_wide_hw_breakpoint_local (struct perf_event *bp, +struct perf_event_attr *attr) +{ +int ret; + +if (find_slot_idx (bp->attr.bp_type) !=find_slot_idx (attr->bp_type)) +return -EINVAL ; + + ret =hw_breakpoint_arch_parse (bp, attr,counter_arch_bp (bp)); +if (ret) +return ret; + + bp->attr.bp_addr = attr->bp_addr; + +return arch_reinstall_hw_breakpoint (bp); +} +#else +int modify_wide_hw_breakpoint_local (struct perf_event *bp, +struct perf_event_attr *attr) +{ +return -EOPNOTSUPP ; +} +#endif +EXPORT_SYMBOL_GPL (modify_wide_hw_breakpoint_local); +/** * hw_breakpoint_is_used - check if breakpoints are currently used *
[email protected]
AI reviewRe: [PATCH v7 08/10] HWBP: Add modify_wide_hw_breakpoint_local() API
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] State Corruption on Error Path during breakpoint modification - [High] Hardware Register…
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] State Corruption on Error Path during breakpoint modification
- [High] Hardware Register Corruption (DR7 missing bit clear) on Reinstall
- [Medium] Inconsistent Software State after breakpoint modification
- [Medium] Race Condition with Debug Exception Handlers during in-place modification --
--- Patch [8]: [PATCH v7 08/10] HWBP: Add modify_wide_hw_breakpoint_local() API --- commit e82221932a08cae24063fb7cb3fe49f250391aa5
Author: Masami Hiramatsu (Google) <[email protected]>
HWBP: Add modify_wide_hw_breakpoint_local() API
This commit adds the modify_wide_hw_breakpoint_local() arch-wide interface which allows hwbp users to update watch addresses on-line. It reuses the current hardware breakpoint slot without releasing and reserving it, meaning type changes are only allowed between compatible types.
Quoted 4 lines
kernel/events/hw_breakpoint.c +0
-0
[ ... ]
Quoted 17 lines
kernel/events/hw_breakpoint.c +13
-0@@ -888,6 +888,45 @@ void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events) }EXPORT_SYMBOL_GPL (unregister_wide_hw_breakpoint); +/** +* modify_wide_hw_breakpoint_local - update breakpoint config for local CPU +* @bp: the hwbp perf event for this CPU +* @attr: the new attribute for @bp +* +* This does not release and reserve the slot of a HWBP; it just reuses the +* current slot on local CPU. So the users must update the other CPUs by +* themselves. +* Also, since this does not release/reserve the slot, this can not change the +* type to incompatible type of the HWBP. +* Return err if attr is invalid or the CPU fails to update debug register +* for new @attr. +*/
kernel/events/hw_breakpoint.c void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events) · line 913
Quoted 10 lines
kernel/events/hw_breakpoint.c +10
-0+#ifdef CONFIG_HAVE_REINSTALL_HW_BREAKPOINT +int modify_wide_hw_breakpoint_local (struct perf_event *bp, +struct perf_event_attr *attr) +{ +int ret; + +if (find_slot_idx (bp->attr.bp_type) !=find_slot_idx (attr->bp_type)) +return -EINVAL ; + + ret =hw_breakpoint_arch_parse (bp, attr,counter_arch_bp (bp));
[Severity: Medium]
Is it safe to mutate the active hardware state structure in-place while the hardware breakpoint is still active? If a debug exception (#DB) triggers on the same CPU during this non-atomic update, could hw_breakpoint_handler() read a partially updated bp->hw.info and generate flawed samples?
[Severity: High]
Could this corrupt the active state on error paths? By passing the active hardware configuration pointer via counter_arch_bp(bp) directly, the architecture parser mutates it in place.
For example, in arch_build_bp_info() on x86, if length or type is updated but a subsequent alignment check fails, we return an error but leave the active struct partially modified. When the breakpoint is later uninstalled, could this use the corrupted length/type to mask out DR7, leaving residual bits that permanently corrupt the slot?
kernel/events/hw_breakpoint.c void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events) · line 917
Quoted 4 lines
kernel/events/hw_breakpoint.c +4
-0+if (ret) +return ret; + + bp->attr.bp_addr = attr->bp_addr;
[Severity: Medium]
Does this leave the logical perf_event attributes disconnected from the hardware configuration? The parser updates the hardware state to reflect the new type and length, but we only copy attr->bp_addr here. Should this also update bp->attr.bp_type and bp->attr.bp_len?
kernel/events/hw_breakpoint.c void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events) · line 919
Quoted 2 lines
kernel/events/hw_breakpoint.c +2
-0+ +return arch_reinstall_hw_breakpoint (bp);
[Severity: High]
Can reinstalling the breakpoint without clearing the old hardware state corrupt the debug registers? When arch_reinstall_hw_breakpoint() invokes setup_hwbp() on x86, it ORs the new length and type bits into DR7:
arch/x86/kernel/hw_breakpoint.c:setup_hwbp()
if (enable)
dr7 |= encode_dr7(slot, info->len, info->type);Since we did not disable the breakpoint first, won't a bitwise OR of the old and new encoded values produce an invalid configuration?
Additionally, if the new configuration sets info->mask to 0, setup_hwbp() skips resetting the hardware mask MSR because it evaluates if (info->mask). Could this leave the old mask permanently active?
Quoted 2 lines
kernel/events/hw_breakpoint.c +2
-0+} +#else
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=8
Masami Hiramatsu (Google)
maintainerPATCH v7 9/10 tracing: wprobe: Add wprobe event trigger
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add wprobe event trigger to set and clear the watch event dynamically. This allows us to set an watchpoint on a given local variables and a slab object instead of static objects.
The trigger syntax is below:
- set_wprobe:WPROBE:FIELD[+OFFSET] [if FILTER]
- clear_wprobe:WPROBE[:FIELD[+OFFSET]] [if FILTER]
set_wprobe sets the address pointed by FIELD[+offset] to the WPROBE event. The FIELD is the field name of trigger event. clear_wprobe clears the watch address of WPROBE event. If the FIELD option is specified, it clears only if the current watch address is same as the given FIELD[+OFFSET] value.
The set_wprobe trigger does not change the type and length, these must be set when creating a new wprobe.
Also, the WPROBE event must be disabled when setting the new trigger and it will be busy afterwards. Recommended usage is to add a new wprobe at NULL address and keep disabled.
---
Changes in v7:
- Use kzalloc_obj().
- Update sample code in document.
Changes in v6:
- Update according to the latest change of trigger ops.
Changes in v5:
- Following the suggestions, the documentation was revised to suit rst.
Changes in v3:
- Add FIELD option support for clear_wprobe and update document.
- Fix to unregister/free event_trigger_data on file correctly.
- Fix syntax comments.
Changes in v2:
- Getting local cpu perf_event from trace_wprobe directly.
- Remove trace_wprobe_local_perf() because it is conditionally unused.
- Make CONFIG_WPROBE_TRIGGERS a hidden config.
--- Documentation/trace/wprobetrace.rst | 89 ++++++++ include/linux/trace_events.h | 1 kernel/trace/Kconfig | 10 + kernel/trace/trace_wprobe.c | 415 +++++++++++++++++++++++++++++++++++ 4 files changed, 515 insertions(+)
Documentation/trace/wprobetrace.rst +89 -0
@@ -67,3 +67,92 @@ Here is an example to add a wprobe event on a variable `jiffies`. <idle>-0 [000] d.Z1. 717.026373: my_jiffies: (tick_do_update_jiffies64+0xbe/0x130) You can see the code which writes to `jiffies` is `tick_do_update_jiffies64()`. + +Combination with trigger action +------------------------------- +The event trigger action can extend the utilization of this wprobe. + +- set_wprobe:WPEVENT:FIELD[+|-ADJUST] +- clear_wprobe:WPEVENT[:FIELD[+|-]ADJUST] + +Set these triggers to the target event, then the WPROBE event will be +setup to trace the memory access at FIELD[+|-ADJUST] address. +When clear_wprobe is hit, if FIELD is NOT specified, the WPEVENT is +forcibly cleared. If FIELD[[+|-]ADJUST] is set, it clears WPEVENT only +if its watching address is the same as the FIELD[[+|-]ADJUST] value. + +Notes: +The set_wprobe trigger does not change the type and length, these +must be set when creating a new wprobe. + +The WPROBE event must be disabled when setting the new trigger +and it will be busy afterwards. Recommended usage is to add a new +wprobe at NULL address and keep disabled. + + +For example, trace the first 8 bytes of the dentry data structure passed +to do_truncate() until it is deleted by dentry_kill(). +(Note: all tracefs setup uses '>>' so that it does not kick do_truncate()) +:: + + # echo 'w:watch rw@0:8 address=$addr value=+0($addr)' >> dynamic_events + # echo 'f:truncate do_truncate dentry=$arg2' >> dynamic_events + # echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger + # echo 'f:dentry_kill dentry_kill dentry=$arg1' >> dynamic_events + # echo 'clear_wprobe:watch:dentry' >> events/fprobes/dentry_kill/trigger + # echo 1 >> events/fprobes/truncate/enable + # echo 1 >> events/fprobes/dentry_kill/enable + + # echo aaa > /tmp/hoge + # echo bbb > /tmp/hoge + # echo ccc > /tmp/hoge + # rm /tmp/hoge + +Then, the trace data will show:: + + # tracer: nop + # + # entries-in-buffer/entries-written: 32/32 #P:8 + # + # _-----=> irqs-off/BH-disabled + # / _----=> need-resched + # | / _---=> hardirq/softirq + # || / _--=> preempt-depth + # ||| / _-=> migrate-disable + # |||| / delay + # TASK-PID CPU# ||||| TIMESTAMP FUNCTION + # | | | ||||| | | + sh-107 [004] ...1. 9.990418: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618 + sh-107 [004] ...1. 9.990914: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b3de78 + sh-107 [004] ...1. 9.993175: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049ddd40 + sh-107 [004] ..... 9.995198: truncate: (do_truncate+0x4/0x120) dentry=0xffff8880048083a8 + sh-107 [004] ...1. 9.995389: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db998 + sh-107 [004] ..Zff 9.997503: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080 + sh-107 [004] ..Zff 9.997509: watch: (path_openat+0x211/0xda0) address=0xffff8880048083a8 value=0x8200080 + sh-107 [004] ..Zff 9.997514: watch: (path_openat+0xa56/0xda0) address=0xffff8880048083a8 value=0x8200080 + sh-107 [004] ..Zff 9.997518: watch: (path_openat+0xae2/0xda0) address=0xffff8880048083a8 value=0x8200080 + sh-107 [004] ..... 9.997521: truncate: (do_truncate+0x4/0x120) dentry=0xffff8880048083a8 + sh-107 [004] ...1. 9.997582: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004808270 + sh-107 [004] ...1. 9.999365: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db728 + sh-107 [004] ...1. 9.999388: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b1c000 + rm-113 [005] ..Zff 10.000965: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] ..Zff 10.000971: watch: (path_lookupat+0x97/0x1e0) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] ..Zff 10.000984: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] ..Zff 10.000988: watch: (path_lookupat+0x97/0x1e0) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] ..Zff 10.001010: watch: (lookup_one_qstr_excl+0x28/0x140) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] ..Zff 10.001014: watch: (lookup_one_qstr_excl+0xd1/0x140) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] ..Zff 10.001018: watch: (may_delete_dentry+0x1c/0x200) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] ..Zff 10.001021: watch: (may_delete_dentry+0x195/0x200) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] ..Zff 10.001031: watch: (vfs_unlink+0x5e/0x260) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] d.Z.. 10.001067: watch: (d_make_discardable+0x1b/0x40) address=0xffff8880048083a8 value=0x8200080 + rm-113 [005] d.Z.. 10.001071: watch: (d_make_discardable+0x29/0x40) address=0xffff8880048083a8 value=0x200080 + rm-113 [005] ...1. 10.001072: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880048083a8 + rm-113 [005] ...1. 10.001218: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880048083a8 + sh-107 [004] ...1. 10.001416: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db110 + sh-107 [004] ...1. 10.001444: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db248 + sh-107 [004] ...1. 10.001500: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618 + sh-107 [004] ...1. 10.002067: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b41e78 + sh-107 [004] ...1. 10.904920: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b41e78 + sh-107 [004] ...1. 10.905129: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618 + +You can see the watch event is correctly configured on the dentry.
include/linux/trace_events.h +1 -0
@@ -729,6 +729,7 @@ enum event_trigger_type {ETT_EVENT_HIST = (1 <<4 ),ETT_HIST_ENABLE = (1 <<5 ),ETT_EVENT_EPROBE = (1 <<6 ), + ETT_EVENT_WPROBE = (1 << 7), };extern int filter_match_preds (struct event_filter *filter,void *rec);
kernel/trace/Kconfig +10 -0
@@ -876,6 +876,16 @@ config WPROBE_EVENTS Those events can be inserted wherever hardware breakpoints can be set, and record accessed memory address and values. +config WPROBE_TRIGGERS + depends on WPROBE_EVENTS + depends on HAVE_REINSTALL_HW_BREAKPOINT + bool + default y + help + This adds an event trigger which will set the wprobe on a specific + field of an event. This allows user to trace the memory access of + an address pointed by the event field. + config BPF_EVENTS depends on BPF_SYSCALL depends on (KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS
kernel/trace/trace_wprobe.c +415 -0
435 lines not on this page · raw message
[email protected]
AI reviewRe: [PATCH v7 09/10] tracing: wprobe: Add wprobe event trigger
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider: - [Critical] Deadlocks due to NMI-unsafe operations (schedule_work and raw_spin_lock_irqsave) in tracepoint…
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] Deadlocks due to NMI-unsafe operations (schedule_work and raw_spin_lock_irqsave) in tracepoint context.
- [High] Missing cancel_work_sync() causes a Use-After-Free of wprobe_trigger_data via the workqueue.
- [High] Synchronous free of wprobe_data violates RCU removal ordering, risking concurrent use-after-free.
- [High] Missing trace_event_try_get_ref() and imbalanced trace_event_put_ref() cause module refcount underflow.
- [High] on_each_cpu is called with wait=false for an IPI that references wprobe_data.
- [High] Immediate kfree() of RCU-protected trigger_data and wprobe_data on the error path.
- [Medium] Permanent memory leak of wprobe trigger objects on successful registration.
- [Medium] tw->addr is read and written concurrently by different triggers without a shared lock. --
--- Patch [9]: [PATCH v7 09/10] tracing: wprobe: Add wprobe event trigger --- The format of this report is altered due to recitation restrictions. I am providing a free-form summary of the findings rather than inline quoting the patch.
Commit: 4087513c82b11b98d27cfc4452df9a1a8e41ddd5
Author: Masami Hiramatsu (Google) <[email protected]>
Subject: tracing: wprobe: Add wprobe event trigger
This patch adds a wprobe event trigger to set and clear watch events dynamically, allowing watchpoints on local variables and slab objects.
[Severity: Critical]
Is it safe to call raw_spin_lock_irqsave() and schedule_work() directly from wprobe_trigger()? Tracepoints can execute in arbitrary contexts, including NMI or with scheduler locks held. Calling schedule_work() is unsafe in these contexts. Additionally, acquiring wprobe_data->lock here can cause a hard deadlock if the tracepoint interrupts wprobe_smp_update_func() while it is holding the exact same lock in IPI context.
[Severity: High]
Does free_wprobe_trigger_data() safely handle the embedded work struct? When a trigger is unregistered, this function frees wprobe_data via kfree(). If wprobe_trigger() recently scheduled wprobe_data->work, the workqueue could execute wprobe_work_func() on the freed memory since there is no cancel_work_sync() before freeing.
[Severity: High]
Is an RCU grace period needed in wprobe_trigger_free()? wprobe_unregister_trigger() unlinks the trigger and calls wprobe_trigger_free(), which synchronously frees wprobe_data. Since tracepoints execute under RCU, could a concurrent tracepoint execution still be accessing wprobe_data when it gets freed?
[Severity: High]
Can the error path in wprobe_trigger_cmd_parse() cause a use-after-free? If event_trigger_register() successfully exposes the trigger to RCU readers but a subsequent step fails, returning an error causes the __free(kfree) cleanup to immediately free trigger_data and wprobe_data. This bypasses any RCU grace period for objects that were just briefly exposed to concurrent tracepoint readers.
[Severity: High]
Is it intentional that on_each_cpu() is called with wait=false in wprobe_work_func()? Because it doesn't wait, the work function completes immediately while IPIs are still pending on other CPUs. If the trigger is unregistered and freed right after, wprobe_smp_update_func() will access freed wprobe_data.
[Severity: High]
Does wprobe_trigger_cmd_parse() acquire the target event's module reference? The code calls trace_event_enable_disable() directly without first calling trace_event_try_get_ref(). However, wprobe_trigger_free() unconditionally calls trace_event_put_ref(), which could lead to a module refcount underflow.
[Severity: Medium]
Is there a memory leak on the success path of wprobe_trigger_cmd_parse()? The function increments the refcount via event_trigger_init(), and then event_trigger_register() increments it again. On success, trigger_data is set to NULL to bypass the cleanup macro, but the initial refcount is never dropped. When the trigger is later unregistered, the refcount only drops to 1, permanently leaking the objects.
[Severity: Medium]
How is concurrent access to tw->addr synchronized between different triggers? If a user attaches multiple triggers (e.g., set_wprobe and clear_wprobe) targeting the same wprobe event, they allocate separate wprobe_data instances with independent locks. When wprobe_trigger() executes, they will read and write the shared tw->addr simultaneously without a common lock, potentially corrupting the hardware breakpoint state.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=9
Masami Hiramatsu (Google)
maintainerPATCH v7 10/10 selftests: ftrace: Add wprobe trigger testcase
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add a testcase for checking wprobe trigger. This sets set_wprobe and clear_wprobe triggers on fprobe events to watch dentry access. So this depends on both wprobe and fprobe.
---
Changes in v7:
- Add a newline at the end of file.(style fix)
- Use dentry_kill instead of __dentry_kill.
Changes in v5:
- Enable CONFIG_WPROBE_TRIGGERS in the config for ftrace test.
Changes in v3:
- Newly added.
--- tools/testing/selftests/ftrace/config | 1 .../ftrace/test.d/trigger/trigger-wprobe.tc | 48 ++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc
tools/testing/selftests/ftrace/config +1 -0
@@ -28,3 +28,4 @@ CONFIG_TRACER_SNAPSHOT=y CONFIG_UPROBES=y CONFIG_UPROBE_EVENTS=y CONFIG_WPROBE_EVENTS=y +CONFIG_WPROBE_TRIGGERS=y
tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc new file +48 -0
48 lines not on this page · raw message
[email protected]
AI reviewRe: [PATCH v7 10/10] selftests: ftrace: Add wprobe trigger testcase
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] Permanent leak of trigger_data and wprobe_data when unregistering a wprobe trigger. - [Medium] The…
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Permanent leak of trigger_data and wprobe_data when unregistering a wprobe trigger.
- [Medium] The selftest fails to declare its dependency on fprobe in the # requires directive, causing test failures on unsupported kernels.
- [Low] The test uses a hardcoded path /tmp/hoge for triggering file operations, leading to test brittleness.
- [Low] The test hooks a static function dentry_kill without checking its presence in available_filter_functions. --
--- Patch [10]: [PATCH v7 10/10] selftests: ftrace: Add wprobe trigger testcase --- commit 3a609968e71e94ce3399d95a2f00d2d3b0974ab0
Author: Masami Hiramatsu (Google) <[email protected]>
selftests: ftrace: Add wprobe trigger testcase
This commit adds a testcase to check the wprobe trigger functionality. It sets set_wprobe and clear_wprobe triggers on fprobe events to watch dentry access, validating features that depend on both wprobe and fprobe.
[Severity: High]
While not part of this commit's diff, there appears to be a regression associated with the wprobe trigger registration tested here.
In kernel/trace/trace_wprobe.c:wprobe_trigger_cmd_parse():
ret = event_trigger_register(cmd_ops, file, glob, trigger_data);
if (ret) {
event_trigger_reset_filter(cmd_ops, trigger_data);
trace_event_enable_disable(wprobe_file, 0, 1);
return ret;
}
/* Make it NULL to avoid freeing trigger_data and wprobe_data by __free() */
trigger_data = NULL;
wprobe_data = NULL;
return 0;Does this code permanently leak the trigger_data and wprobe_data structures when unregistering a wprobe trigger?
The successful path returns without calling event_trigger_free(trigger_data), stranding the reference count at 2.
tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc line 4
Quoted 10 lines
tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc new file +4
-0@@ -0,0 +1,48 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: event trigger - test wprobe trigger +# requires: dynamic_events "w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]":README events/sched/sched_process_fork/trigger
[Severity: Medium]
Will this script fail with an error exit code on environments without fprobe support, rather than gracefully skipping the test?
The test relies on fprobe (f:), but does not declare this dependency in the
# requires directive.
Without this requirement, the ftracetest runner will try to write f:truncate to dynamic_events on kernels configured with CONFIG_WPROBE_EVENTS=y but without CONFIG_FPROBE_EVENTS, causing the test to fail.
tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc line 13
Quoted 9 lines
tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc new file +9
-0+ +echo 0 > tracing_on + +:;: "Add a wprobe event used by trigger" ;: +echo 'w:watch rw@0:8 address=$addr value=+0($addr)' > dynamic_events + +:;: "Add events for triggering wprobe" ;: +echo 'f:truncate do_truncate dentry=$arg2' >> dynamic_events +echo 'f:dentry_kill dentry_kill dentry=$arg1' >> dynamic_events
[Severity: Low]
Is it safe to assume the static function dentry_kill is always available for tracing?
The script attaches an fprobe to dentry_kill without checking if it exists in available_filter_functions.
If the compiler aggressively inlines or eliminates dentry_kill, the fprobe creation will fail and abort the test.
[ ... ]
tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc line 34
Quoted 7 lines
tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc new file +7
-0+:;: "Start test workload" ;: +echo 1 >> tracing_on + +echo aaa > /tmp/hoge +echo bbb > /tmp/hoge +echo ccc > /tmp/hoge +rm /tmp/hoge
[Severity: Low]
Could this hardcoded path cause unexpected failures if /tmp/hoge already exists, is owned by another user, or if tests run in parallel?
The test writes to a fixed global path instead of relying on the isolated $TMPDIR standard within ftracetest.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/178407983818.95826.12714571928538799781.stgit@devnote2?part=10
Masami Hiramatsu (Google)
maintainerRe: [PATCH v7 10/10] selftests: ftrace: Add wprobe trigger testcase
Quoted 11 lines · On Wed, 15 Jul 2026 02:05:55 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] Deadlocks due to NMI-unsafe operations (schedule_workandraw_spin_lock_irqsave) in tracepoint context.
- [High] Missingcancel_work_sync()causes a Use-After-Free ofwprobe_trigger_datavia the workqueue.
- [High] Synchronous free ofwprobe_dataviolates RCU removal ordering, risking concurrent use-after-free.
- [High] Missingtrace_event_try_get_ref()and imbalancedtrace_event_put_ref()cause module refcount underflow.
- [High]on_each_cpuis called with wait=false for an IPI that referenceswprobe_data.
- [High] Immediatekfree()of RCU-protectedtrigger_dataandwprobe_dataon the error path.
- [Medium] Permanent memory leak of wprobe trigger objects on successful registration.
- [Medium]tw->addris read and written concurrently by different triggers without a shared lock. --
Thanks for the review comments.
BTW, why this only to linux-perf-users? (Maybe I need to modify Sashiko setting)
Quoted 12 lines
--- Patch [9]: [PATCH v7 09/10] tracing: wprobe: Add wprobe event trigger --- The format of this report is altered due to recitation restrictions. I am providing a free-form summary of the findings rather than inline quoting the patch.
Commit:
4087513c82b11b98d27cfc4452df9a1a8e41ddd5
Author: Masami Hiramatsu (Google) <[email protected]>
Subject: tracing: wprobe: Add wprobe event triggerThis patch adds a wprobe event trigger to set and clear watch events dynamically, allowing watchpoints on local variables and slab objects.
Quoted 7 lines
[Severity: Critical]
Is it safe to callraw_spin_lock_irqsave()andschedule_work()directly fromwprobe_trigger()? Tracepoints can execute in arbitrary contexts, including NMI or with scheduler locks held. Callingschedule_work()is unsafe in these contexts. Additionally, acquiringwprobe_data->lockhere can cause a hard deadlock if the tracepoint interruptswprobe_smp_update_func()while it is holding the exact same lock in IPI context.
Hmm, good catch!
OK, use a pair of irq_work and work to call on_each_cpu() for some interrupt context.
For NMI context, I think wprobe_trigger should check the original state is in_nmi(). This can work for normal trace events or fprobe, but not for kprobes, because kprobe handler itself is working inside NMI context. Hmm, let me check how can I fix this. Until solving this problem, this wprobe trigger may not work with kprobes.
Also, I found the lock should protect the wprobe itself, not data. Let me move the irq_work and lock to trace_wprobe.
Hmm, we may need to pass to original context (calculated by preempt_count) to trigger event so that it can evaluate the original context. (BTW, )
Quoted 6 lines
[Severity: High]
Doesfree_wprobe_trigger_data()safely handle the embedded work struct? When a trigger is unregistered, this function freeswprobe_dataviakfree(). Ifwprobe_trigger()recently scheduledwprobe_data->work, the workqueue could executewprobe_work_func()on the freed memory since there is nocancel_work_sync()before freeing.
OK, so move the worker to wprobe because anyway the worker will update the wprobe itself.
Quoted 7 lines
[Severity: High]
Is an RCU grace period needed inwprobe_trigger_free()?wprobe_unregister_trigger()unlinks the trigger and callswprobe_trigger_free(), which synchronously freeswprobe_data. Since tracepoints execute under RCU, could a concurrent tracepoint execution still be accessingwprobe_datawhen it gets freed?
Indeed, let's use call_rcu().
Quoted 8 lines
[Severity: High]
Can the error path inwprobe_trigger_cmd_parse()cause a use-after-free? Ifevent_trigger_register()successfully exposes the trigger to RCU readers but a subsequent step fails, returning an error causes the__free(kfree)cleanup to immediately freetrigger_dataandwprobe_data. This bypasses any RCU grace period for objects that were just briefly exposed to concurrent tracepoint readers.
Ah, good catch. OK, let me call synchronize_rcu() before returning.
Quoted 7 lines
[Severity: High]
Is it intentional thaton_each_cpu()is called with wait=false inwprobe_work_func()? Because it doesn't wait, the work function completes immediately while IPIs are still pending on other CPUs. If the trigger is unregistered and freed right after,wprobe_smp_update_func()will access freedwprobe_data.
Ah, indeed. I will use a combination of irq_worker and worker and use on_each_cpu() with wait=true for safety.
Quoted 7 lines
[Severity: High]
Doeswprobe_trigger_cmd_parse()acquire the target event's module reference? The code callstrace_event_enable_disable()directly without first callingtrace_event_try_get_ref(). However,wprobe_trigger_free()unconditionally callstrace_event_put_ref(), which could lead to a module refcount underflow.
OK, let me fix it.
Quoted 8 lines
[Severity: Medium]
Is there a memory leak on the success path ofwprobe_trigger_cmd_parse()? The function increments the refcount viaevent_trigger_init(), and thenevent_trigger_register()increments it again. On success,trigger_datais set to NULL to bypass the cleanup macro, but the initial refcount is never dropped. When the trigger is later unregistered, the refcount only drops to 1, permanently leaking the objects.
OK, call trace_event_put_ref directly in wprobe_trigger_cmd_parse().
Quoted 8 lines
[Severity: Medium]
How is concurrent access totw->addrsynchronized between different triggers? If a user attaches multiple triggers (e.g.,set_wprobeandclear_wprobe) targeting the same wprobe event, they allocate separatewprobe_datainstances with independent locks. Whenwprobe_trigger()executes, they will read and write the sharedtw->addrsimultaneously without a common lock, potentially corrupting the hardware breakpoint state.
I decided to use a lock in trace_wprobe side and use READ_ONCE/WRITE_ONCE() which may be enough.
Thank you,
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>
Masami Hiramatsu (Google)
maintainerRe: [PATCH v7 10/10] selftests: ftrace: Add wprobe trigger testcase
Oops, sorry for confusion, this is for [9/10].
Thanks,
Quoted 133 lines · On Thu, 16 Jul 2026 10:59:13 +0900 Masami Hiramatsu (Google)…
Quoted 11 lines · On Wed, 15 Jul 2026 02:05:55 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] Deadlocks due to NMI-unsafe operations (schedule_workandraw_spin_lock_irqsave) in tracepoint context.
- [High] Missingcancel_work_sync()causes a Use-After-Free ofwprobe_trigger_datavia the workqueue.
- [High] Synchronous free ofwprobe_dataviolates RCU removal ordering, risking concurrent use-after-free.
- [High] Missingtrace_event_try_get_ref()and imbalancedtrace_event_put_ref()cause module refcount underflow.
- [High]on_each_cpuis called with wait=false for an IPI that referenceswprobe_data.
- [High] Immediatekfree()of RCU-protectedtrigger_dataandwprobe_dataon the error path.
- [Medium] Permanent memory leak of wprobe trigger objects on successful registration.
- [Medium]tw->addris read and written concurrently by different triggers without a shared lock. --Thanks for the review comments.
BTW, why this only to linux-perf-users? (Maybe I need to modify Sashiko setting)Quoted 12 lines
--- Patch [9]: [PATCH v7 09/10] tracing: wprobe: Add wprobe event trigger --- The format of this report is altered due to recitation restrictions. I am providing a free-form summary of the findings rather than inline quoting the patch.
Commit:
4087513c82b11b98d27cfc4452df9a1a8e41ddd5
Author: Masami Hiramatsu (Google) <[email protected]>
Subject: tracing: wprobe: Add wprobe event triggerThis patch adds a wprobe event trigger to set and clear watch events dynamically, allowing watchpoints on local variables and slab objects.
Quoted 7 lines
[Severity: Critical]
Is it safe to callraw_spin_lock_irqsave()andschedule_work()directly fromwprobe_trigger()? Tracepoints can execute in arbitrary contexts, including NMI or with scheduler locks held. Callingschedule_work()is unsafe in these contexts. Additionally, acquiringwprobe_data->lockhere can cause a hard deadlock if the tracepoint interruptswprobe_smp_update_func()while it is holding the exact same lock in IPI context.Hmm, good catch!
OK, use a pair ofirq_workand work to callon_each_cpu()for some interrupt context.
For NMI context, I thinkwprobe_triggershould check the original state isin_nmi(). This can work for normal trace events or fprobe, but not for kprobes, because kprobe handler itself is working inside NMI context. Hmm, let me check how can I fix this. Until solving this problem, this wprobe trigger may not work with kprobes.
Also, I found the lock should protect the wprobe itself, not data. Let me move theirq_workand lock totrace_wprobe.Hmm, we may need to pass to original context (calculated by
preempt_count) to trigger event so that it can evaluate the original context. (BTW, )Quoted 6 lines
[Severity: High]
Doesfree_wprobe_trigger_data()safely handle the embedded work struct? When a trigger is unregistered, this function freeswprobe_dataviakfree(). Ifwprobe_trigger()recently scheduledwprobe_data->work, the workqueue could executewprobe_work_func()on the freed memory since there is nocancel_work_sync()before freeing.OK, so move the worker to wprobe because anyway the worker will update the wprobe itself.
Quoted 7 lines
[Severity: High]
Is an RCU grace period needed inwprobe_trigger_free()?wprobe_unregister_trigger()unlinks the trigger and callswprobe_trigger_free(), which synchronously freeswprobe_data. Since tracepoints execute under RCU, could a concurrent tracepoint execution still be accessingwprobe_datawhen it gets freed?Indeed, let's use
call_rcu().Quoted 8 lines
[Severity: High]
Can the error path inwprobe_trigger_cmd_parse()cause a use-after-free? Ifevent_trigger_register()successfully exposes the trigger to RCU readers but a subsequent step fails, returning an error causes the__free(kfree)cleanup to immediately freetrigger_dataandwprobe_data. This bypasses any RCU grace period for objects that were just briefly exposed to concurrent tracepoint readers.Ah, good catch. OK, let me call
synchronize_rcu()before returning.Quoted 7 lines
[Severity: High]
Is it intentional thaton_each_cpu()is called with wait=false inwprobe_work_func()? Because it doesn't wait, the work function completes immediately while IPIs are still pending on other CPUs. If the trigger is unregistered and freed right after,wprobe_smp_update_func()will access freedwprobe_data.Ah, indeed. I will use a combination of
irq_workerand worker and useon_each_cpu()with wait=true for safety.Quoted 7 lines
[Severity: High]
Doeswprobe_trigger_cmd_parse()acquire the target event's module reference? The code callstrace_event_enable_disable()directly without first callingtrace_event_try_get_ref(). However,wprobe_trigger_free()unconditionally callstrace_event_put_ref(), which could lead to a module refcount underflow.OK, let me fix it.
Quoted 8 lines
[Severity: Medium]
Is there a memory leak on the success path ofwprobe_trigger_cmd_parse()? The function increments the refcount viaevent_trigger_init(), and thenevent_trigger_register()increments it again. On success,trigger_datais set to NULL to bypass the cleanup macro, but the initial refcount is never dropped. When the trigger is later unregistered, the refcount only drops to 1, permanently leaking the objects.OK, call
trace_event_put_refdirectly inwprobe_trigger_cmd_parse().Quoted 8 lines
[Severity: Medium]
How is concurrent access totw->addrsynchronized between different triggers? If a user attaches multiple triggers (e.g.,set_wprobeandclear_wprobe) targeting the same wprobe event, they allocate separatewprobe_datainstances with independent locks. Whenwprobe_trigger()executes, they will read and write the sharedtw->addrsimultaneously without a common lock, potentially corrupting the hardware breakpoint state.I decided to use a lock in
trace_wprobeside and use READ_ONCE/WRITE_ONCE() which may be enough.Thank you,
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>