From: sashiko-bot@kernel.org
To: "Jinchao Wang" <wangjinchao600@gmail.com>
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [RFC PATCH 08/13] mm/kwatch: add hardware breakpoint backend
Date: Tue, 14 Jul 2026 18:50:35 +0000 [thread overview]
Message-ID: <20260714185036.6C8EA1F00A3A@smtp.kernel.org> (raw)
In-Reply-To: <20260714183206.12688-1-wangjinchao600@gmail.com>
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [Critical] Repeated calls to `kwatch_hwbp_prealloc` write out of bounds of the `kwatch_wp_slots` array, leading to a heap buffer overflow.
- [High] CPU hotplug online callback overwrites active watchpoints with the dummy address, breaking them on newly onlined CPUs.
- [High] A static global per-CPU rate limit in `kwatch_hwbp_arm` silently drops arming requests, causing independent watchpoints to fail.
- [High] Missing IPI completion tracking for arm requests allows `csd_arm` to be reused while still pending, causing subsequent arm requests to silently fail.
- [High] `kwatch_hwbp_get` locklessly accesses `kwatch_wp_slots` while `kwatch_hwbp_free` frees it without synchronization.
- [Medium] Uninitialized kernel memory leaked to the trace ring buffer when the stack depth is less than KWATCH_STACK_DEPTH.
- [Medium] Function calls with side effects in `TP_printk` arguments cause trace buffer corruption.
- [Medium] `kwatch_hwbp_handler` logs the globally shared address instead of the local address, recording incorrect data during concurrent updates.
- [Low] 64-bit division in tracepoint printk causes linker errors on 32-bit architectures.
--
--- Patch [8]: [RFC PATCH 08/13] mm/kwatch: add hardware breakpoint backend ---
commit e30fa607776b2ca60d90cdf9ad4735ab9b2801f7
Author: Jinchao Wang <wangjinchao600@gmail.com>
mm/kwatch: add hardware breakpoint backend
Manage a preallocated pool of wide (per-CPU) perf hardware breakpoints.
All breakpoints are registered up front against a dummy address; arming
a watchpoint only re-points an already-registered event, so the arm path
can run from a kprobe handler.
> diff --git a/include/trace/events/kwatch.h b/include/trace/events/kwatch.h
> new file mode 100644
> index 0000000000000..edb95405c3869
> --- /dev/null
> +++ b/include/trace/events/kwatch.h
[ ... ]
> + TP_fast_assign(
> + unsigned int i;
> +
> + __entry->ip = ip;
> + __entry->sp = sp;
> + __entry->addr = addr;
> + __entry->time_ns = time_ns;
> + __entry->stack_nr = min_t(unsigned int, stack_nr,
> + KWATCH_STACK_DEPTH);
> + for (i = 0; i < __entry->stack_nr; i++)
> + __entry->stack[i] = stack_entries[i];
> + ),
[Severity: Medium]
Will this leave the remaining elements in __entry->stack uninitialized when
stack_nr is less than KWATCH_STACK_DEPTH?
Because this memory comes from the tracing ring buffer, leaking uninitialized
kernel data might allow userspace to read stale kernel trace records.
> +
> + TP_printk("KWatch HIT: time=%llu.%06lu ip=%pS addr=0x%lx%s",
> + __entry->time_ns / 1000000000ULL,
> + (unsigned long)((__entry->time_ns / 1000ULL) % 1000000ULL),
[Severity: Low]
Is it safe to use 64-bit division and modulo operators here?
Native 64-bit division on __entry->time_ns can cause build failures due to
unresolved __udivdi3 symbols on 32-bit architectures. Could div64_u64() or
do_div() be used instead?
> + (void *)__entry->ip, __entry->addr,
> + kwatch_trace_print_stack(p, __entry->stack,
> + __entry->stack_nr))
> +);
[Severity: Medium]
Can executing kwatch_trace_print_stack() as an argument to TP_printk()
corrupt the trace output buffer?
Due to argument evaluation order, kwatch_trace_print_stack() could evaluate
before trace_seq_printf(). Since it writes directly to the shared trace_seq
buffer and inserts a null byte:
trace_seq_putc(p, 0);
trace_seq_printf() would append the main event string after the null byte.
When userspace reads the trace, it stops at the null byte, resulting in a
mangled or truncated trace log.
> diff --git a/mm/kwatch/hwbp.c b/mm/kwatch/hwbp.c
> new file mode 100644
> index 0000000000000..19498ba038260
> --- /dev/null
> +++ b/mm/kwatch/hwbp.c
[ ... ]
> +static void kwatch_hwbp_handler(struct perf_event *bp,
> + struct perf_sample_data *data,
> + struct pt_regs *regs)
> +{
> + struct kwatch_watchpoint *wp = bp->overflow_handler_context;
> + unsigned long stack_entries[KWATCH_STACK_DEPTH];
> + unsigned int stack_nr;
> +
> + if (!kwatch_probe_validate_hit(regs, wp->arm_tsk))
> + return;
> +
> + stack_nr = stack_trace_save_regs(regs, stack_entries, KWATCH_STACK_DEPTH, 2);
> + trace_kwatch_hit(instruction_pointer(regs), kernel_stack_pointer(regs),
> + wp->attr.bp_addr, local_clock(),
> + stack_entries, stack_nr);
> +}
[Severity: Medium]
Could this tracepoint log the wrong address during a concurrent update?
If another CPU changes the watchpoint address, it updates the global
wp->attr.bp_addr and sends IPIs. If a hit on the old hardware breakpoint
triggers here before the IPI is received, it will log the newly updated
global wp->attr.bp_addr instead of the local perf event's configured
address (bp->attr.bp_addr).
[ ... ]
> +static int kwatch_hwbp_cpu_online(unsigned int cpu)
> +{
> + struct perf_event_attr attr;
> + struct kwatch_watchpoint *wp;
> + struct perf_event *bp;
> +
> + mutex_lock(&kwatch_all_wp_mutex);
> + list_for_each_entry(wp, &kwatch_all_wp_list, list) {
> + attr = wp->attr;
> + attr.bp_addr = (unsigned long)&kwatch_dummy_holder;
[Severity: High]
Does this accidentally force newly onlined CPUs to monitor the dummy address
instead of the active watchpoint address?
If a CPU is onlined on a system with active watchpoints, they will silently
fail to trigger on the new CPU because it explicitly configures the hardware
breakpoint for kwatch_dummy_holder instead of the active wp->attr.bp_addr.
[ ... ]
> +int kwatch_hwbp_get(struct kwatch_watchpoint **out_wp)
> +{
> + struct kwatch_watchpoint *wp;
> + int i;
> +
> + /*
> + * Per-slot cmpxchg claim: safe for concurrent consumers on any CPU,
> + * unlike llist_del_first() which requires a single consumer.
> + */
> + for (i = 0; i < kwatch_wp_nr; i++) {
> + wp = kwatch_wp_slots[i];
[Severity: High]
Is it safe to access kwatch_wp_slots here without holding a lock or
using RCU?
If an error occurs during preallocation or a module unload triggers
kwatch_hwbp_free(), the array can be freed:
kwatch_wp_nr = 0;
kfree(kwatch_wp_slots);
If kwatch_hwbp_get() reads kwatch_wp_nr before it is zeroed and dereferences
the array after it is freed, it could lead to a use-after-free.
[ ... ]
> +void kwatch_hwbp_arm(struct kwatch_watchpoint *wp, unsigned long addr, u16 len)
> +{
> + static DEFINE_PER_CPU(u64, last_ipi_time);
> + int cur_cpu;
> + call_single_data_t *csd;
> + int cpu;
> + bool is_disarm = (addr == (unsigned long)&kwatch_dummy_holder);
> +
> + wp->attr.bp_addr = addr;
> + wp->attr.bp_len = len;
> +
> + if (!is_disarm)
> + wp->arm_tsk = current;
> +
> + /* ensure attr update visible to other cpu before sending IPI */
> + smp_wmb();
> +
> + atomic_set(&wp->pending_ipis, 1);
> + cur_cpu = get_cpu();
> +
> + if (!is_disarm) {
> + u64 now = local_clock();
> + u64 last = this_cpu_read(last_ipi_time);
> +
> + if (now - last < 1000000ULL) {
> + put_cpu();
> + return;
> + }
[Severity: High]
Could this static global per-CPU rate limit silently break watchpoints?
If two distinct watchpoints are armed on the same CPU within 1 millisecond,
the second request will return early here. However, this happens after the
global wp->attr.bp_addr and wp->arm_tsk have already been modified.
This leaves the hardware monitoring the old address while the metadata claims
it is armed, causing the new watchpoint to miss events.
> + this_cpu_write(last_ipi_time, now);
> + }
> + for_each_online_cpu(cpu) {
> + if (cpu == cur_cpu)
> + continue;
> +
> + if (is_disarm)
> + atomic_inc(&wp->pending_ipis);
> +
> + csd = per_cpu_ptr(is_disarm ? wp->csd_disarm : wp->csd_arm,
> + cpu);
> + if (smp_call_function_single_async(cpu, csd) && is_disarm)
> + kwatch_hwbp_try_recycle(wp);
> + }
[Severity: High]
Should arm requests also handle the case where the IPI fails with -EBUSY?
If a watchpoint is rapidly toggled, the second arm attempt can try to queue
csd_arm while the remote CPU is still processing the first one. This causes
smp_call_function_single_async() to return -EBUSY.
Because the error is only checked for disarm requests (the && is_disarm
check), the failure to arm is silently ignored, and the new address is never
programmed on the busy CPU.
[ ... ]
> +int kwatch_hwbp_prealloc(u16 max_watch, enum kwatch_access_type access_type)
> +{
[ ... ]
> + kwatch_wp_slots = kcalloc(success, sizeof(*kwatch_wp_slots),
> + GFP_KERNEL);
> + if (!kwatch_wp_slots) {
> + kwatch_hwbp_free();
> + return -ENOMEM;
> + }
> + mutex_lock(&kwatch_all_wp_mutex);
> + list_for_each_entry(wp, &kwatch_all_wp_list, list)
> + kwatch_wp_slots[kwatch_wp_nr++] = wp;
[Severity: Critical]
Will subsequent calls to kwatch_hwbp_prealloc() cause a heap buffer
overflow?
If this function is called more than once, it allocates a new
kwatch_wp_slots array sized only for the new success count. However, the
static kwatch_all_wp_list retains elements from prior calls, and the static
kwatch_wp_nr is never reset.
Iterating the accumulated global list and writing elements via
kwatch_wp_slots[kwatch_wp_nr++] will write out of bounds of the newly
allocated array.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260714182243.10687-1-wangjinchao600@gmail.com?part=8
next prev parent reply other threads:[~2026-07-14 18:50 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-14 18:22 [RFC PATCH 00/13] mm/kwatch: dynamic hardware watchpoints for hunting memory corruption Jinchao Wang
2026-07-14 18:22 ` [RFC PATCH 01/13] arch: add HAVE_REINSTALL_HW_BREAKPOINT Jinchao Wang
2026-07-14 18:22 ` [RFC PATCH 02/13] x86/hw_breakpoint: Unify breakpoint install/uninstall Jinchao Wang
2026-07-14 18:47 ` sashiko-bot
2026-07-14 18:22 ` [RFC PATCH 03/13] x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint Jinchao Wang
2026-07-14 19:22 ` sashiko-bot
2026-07-14 18:30 ` [RFC PATCH 04/13] HWBP: Add modify_wide_hw_breakpoint_local() API Jinchao Wang
2026-07-14 19:41 ` sashiko-bot
2026-07-14 18:31 ` [RFC PATCH 05/13] mm/kwatch: add watch expression parser and dereference engine Jinchao Wang
2026-07-14 18:45 ` sashiko-bot
2026-07-14 18:31 ` [RFC PATCH 06/13] mm/kwatch: add lockless per-task context pool Jinchao Wang
2026-07-14 18:44 ` sashiko-bot
2026-07-14 18:31 ` [RFC PATCH 07/13] stacktrace: export stack_trace_save_regs() Jinchao Wang
2026-07-14 18:42 ` sashiko-bot
2026-07-14 18:32 ` [RFC PATCH 08/13] mm/kwatch: add hardware breakpoint backend Jinchao Wang
2026-07-14 18:50 ` sashiko-bot [this message]
2026-07-14 18:32 ` [RFC PATCH 09/13] mm/kwatch: add probe lifecycle runtime Jinchao Wang
2026-07-14 19:53 ` sashiko-bot
2026-07-14 18:32 ` [RFC PATCH 10/13] mm/kwatch: add anchor thread for global watchpoints Jinchao Wang
2026-07-14 18:48 ` sashiko-bot
2026-07-14 18:33 ` [RFC PATCH 11/13] mm/kwatch: add debugfs control plane Jinchao Wang
2026-07-14 18:58 ` sashiko-bot
2026-07-14 18:33 ` [RFC PATCH 12/13] mm/kwatch: add KUnit tests for the watch expression parser Jinchao Wang
2026-07-14 18:50 ` sashiko-bot
2026-07-14 18:33 ` [RFC PATCH 13/13] Documentation/dev-tools: document KWatch Jinchao Wang
2026-07-14 18:48 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260714185036.6C8EA1F00A3A@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=wangjinchao600@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox