From: sashiko-bot@kernel.org
To: "Himanshu Chauhan" <himanshu.chauhan@oss.qualcomm.com>
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [PATCH v7 1/8] riscv: Introduce support for hardware break/watchpoints
Date: Wed, 30 Sep 2026 06:58:22 +0000 [thread overview]
Message-ID: <20260930065823.14F431F000FF@smtp.kernel.org> (raw)
In-Reply-To: <20260930063919.608911-2-himanshu.chauhan@oss.qualcomm.com>
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] perf/riscv: deadlock and IRQ state corruption via per-CPU lock flags
- [High] perf/riscv: hardware trigger hit bit is not cleared causing stuck triggers
- [High] perf/riscv: arch_update_hw_breakpoint passes zero count and corrupts memory
- [High] perf/riscv: use-after-free of sbi_dbtr_shmem on hotplug setup failure
- [Medium] perf/riscv: hardware triggers are leaked on software allocation failure
- [Medium] perf/riscv: integer underflow in arch_check_bp_in_kernelspace
- [Medium] perf/riscv: race condition in arch_uninstall_hw_breakpoint crashes kernel
- [Medium] perf/riscv: uninitialized tstate passed to hypervisor on trigger install
- [Low] perf/riscv: arch_hw_breakpoint_init returns positive integer on success
--
commit 43e04941130b3d79825bf1347e698fc0ed2e7a0d
Author: Himanshu Chauhan <himanshu.chauhan@oss.qualcomm.com>
riscv: Introduce support for hardware break/watchpoints
This commit introduces hardware breakpoint and watchpoint support for RISC-V.
It builds on the perf subsystem and uses the SBI debug trigger extension to
manage hardware triggers.
> diff --git a/arch/riscv/kernel/hw_breakpoint.c b/arch/riscv/kernel/hw_breakpoint.c
> --- /dev/null
> +++ b/arch/riscv/kernel/hw_breakpoint.c
> @@ -0,0 +1,746 @@
[ ... ]
> +/* Registered per-cpu bp/wp */
> +static DEFINE_PER_CPU(struct perf_event *, pcpu_hw_bp_events[RISCV_HW_BP_NUM_MAX]);
> +static DEFINE_PER_CPU(unsigned long, ecall_lock_flags);
[Severity: High]
Does defining ecall_lock_flags as a global per-CPU variable cause IRQ state
corruption? The raw_spin_lock_irqsave() macro expects a local stack variable
to save the IRQ state, but this variable is shared globally across the CPU.
> +static DEFINE_PER_CPU(raw_spinlock_t, ecall_lock);
[ ... ]
> +int arch_check_bp_in_kernelspace(struct arch_hw_breakpoint *hw)
> +{
> + unsigned int len;
> + unsigned long va;
> +
> + va = hw->address;
> + len = hw->len;
> +
> + return (va >= TASK_SIZE) && ((va + len - 1) >= TASK_SIZE);
[Severity: Medium]
For execute breakpoints, len is 0. Does va + len - 1 underflow, causing the
bounds check to wrap around when added to a 64-bit kernel address? This
might evaluate to less than TASK_SIZE and erroneously return false.
> +}
[ ... ]
> +static int hw_breakpoint_handler(struct die_args *args)
> +{
> + int ret = NOTIFY_DONE;
> + struct arch_hw_breakpoint *bp;
> + struct perf_event *event;
> + int i;
> +
> + for (i = 0; i < dbtr_total_num; i++) {
> + event = this_cpu_read(pcpu_hw_bp_events[i]);
> + if (!event)
> + continue;
> +
> + bp = counter_arch_bp(event);
> + switch (bp->type) {
> + /* Breakpoint */
> + case HW_BREAKPOINT_X:
> + {
> + bool hit = bp->address == args->regs->epc;
> +
> + if (!hit && dbtr_type == RISCV_DBTR_TRIG_MCONTROL6)
> + hit = mc6_read_and_clear_hit(i) != RISCV_DBTR_MC6_HIT_FALSE;
[Severity: High]
If the address matches, this short-circuit logic prevents calling
mc6_read_and_clear_hit(i). Does skipping this leave the hardware hit bit
stuck at 1, causing endless spurious traps on unrelated exceptions?
> +
> + if (hit) {
> + perf_bp_event(event, args->regs);
> + ret = NOTIFY_STOP;
> + }
> + break;
> + }
[ ... ]
> + if (dbtr_type == RISCV_DBTR_TRIG_MCONTROL) {
> + unsigned long tdata1;
> + struct sbiret sret;
> + union sbi_dbtr_shmem_entry *shmem;
> +
> + raw_spin_lock_irqsave(this_cpu_ptr(&ecall_lock),
> + *this_cpu_ptr(&ecall_lock_flags));
[Severity: High]
If a synchronous exception like a hardware breakpoint occurs while ecall_lock
is already held, this trap handler will attempt to acquire the exact same
non-recursive local lock and overwrite ecall_lock_flags. Could this result
in an immediate inescapable deadlock?
> + shmem = this_cpu_ptr(sbi_dbtr_shmem);
> + sret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIG_READ,
> + i, 1, 0, 0, 0, 0);
> + if (!sret.error) {
> + tdata1 = le_to_cpu(shmem->data.tdata1);
> + hit = !!(tdata1 & RISCV_DBTR_MC_HIT_BIT_MASK);
[Severity: High]
For MCONTROL triggers, the hit bit is read here, but it doesn't appear to be
cleared in tdata1 or updated via SBI_EXT_DBTR_TRIG_UPDATE. Does this leave
the hardware state corrupted with the hit bit permanently set?
> + }
> + raw_spin_unlock_irqrestore(this_cpu_ptr(&ecall_lock),
> + *this_cpu_ptr(&ecall_lock_flags));
> + }
[ ... ]
> +/* atomic: counter->ctx->lock is held */
> +int arch_install_hw_breakpoint(struct perf_event *event)
> +{
> + struct arch_hw_breakpoint *bp = counter_arch_bp(event);
> + union sbi_dbtr_shmem_entry *shmem = this_cpu_ptr(sbi_dbtr_shmem);
> + struct sbi_dbtr_data_msg *xmit;
> + struct sbi_dbtr_id_msg *recv;
> + struct perf_event **slot;
> + unsigned long idx;
> + struct sbiret ret;
> + int err = 0;
> +
> + raw_spin_lock_irqsave(this_cpu_ptr(&ecall_lock),
> + *this_cpu_ptr(&ecall_lock_flags));
> +
> + xmit = &shmem->data;
> + recv = &shmem->id;
> + xmit->tdata1 = cpu_to_le(bp->tdata1);
> + xmit->tdata2 = cpu_to_le(bp->tdata2);
> + xmit->tdata3 = cpu_to_le(bp->tdata3);
[Severity: Medium]
Because shmem->id and shmem->data share the same memory via a union, does
xmit->tstate need to be initialized here? Otherwise, it might retain the
trigger index from previous recv->idx calls and pass uninitialized garbage
to the hypervisor as the trigger configuration state.
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIG_INSTALL,
> + 1, 0, 0, 0, 0, 0);
> +
> + if (ret.error) {
> + pr_warn("Failed to install trigger\n");
> + err = sbi_err_map_linux_errno(ret.error);
> + goto done;
> + }
> +
> + idx = le_to_cpu(recv->idx);
> + if (idx >= dbtr_total_num) {
> + pr_warn("Invalid trigger index %lu\n", idx);
> + err = -EINVAL;
> + goto done;
[Severity: Medium]
If this software validation fails, the error path jumps to done without
issuing an SBI_EXT_DBTR_TRIG_UNINSTALL. Does this permanently leak the
successfully allocated hardware trigger?
> + }
> +
> + slot = this_cpu_ptr(&pcpu_hw_bp_events[idx]);
> + if (*slot) {
> + pr_warn("Slot %lu is in use\n", idx);
> + err = -EBUSY;
> + goto done;
[Severity: Medium]
Similarly, does this slot check also bail out without cleaning up the
hardware state, causing a trigger leak?
> + }
[ ... ]
> +/* atomic: counter->ctx->lock is held */
> +void arch_uninstall_hw_breakpoint(struct perf_event *event)
> +{
> + struct sbiret ret;
> + int i;
> +
> + raw_spin_lock_irqsave(this_cpu_ptr(&ecall_lock),
> + *this_cpu_ptr(&ecall_lock_flags));
> +
> + for (i = 0; i < dbtr_total_num; i++) {
> + struct perf_event **slot = this_cpu_ptr(&pcpu_hw_bp_events[i]);
> +
> + if (*slot == event) {
> + *slot = NULL;
> + break;
> + }
> + }
[Severity: Medium]
Does clearing the software slot before uninstalling the hardware trigger
open a dangerous race window? If a kernel watchpoint fires right here before
SBI_EXT_DBTR_TRIG_UNINSTALL completes, the handler will observe a NULL slot
and return NOTIFY_DONE, causing handle_break() to crash the system with an
unhandled DIE_DEBUG trap.
> +
> + if (i == dbtr_total_num) {
> + pr_warn("Breakpoint not installed.\n");
> + goto out;
> + }
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIG_UNINSTALL,
> + i, 1, 0, 0, 0, 0);
[ ... ]
> +void arch_update_hw_breakpoint(struct perf_event *event)
> +{
> + struct arch_hw_breakpoint *bp = counter_arch_bp(event);
> + union sbi_dbtr_shmem_entry *shmem = this_cpu_ptr(sbi_dbtr_shmem);
> + struct sbi_dbtr_data_msg *xmit;
> + struct sbi_dbtr_id_msg *id;
> + struct perf_event **slot;
> + struct sbiret ret;
> + int i;
[ ... ]
> + raw_spin_lock_irqsave(this_cpu_ptr(&ecall_lock),
> + *this_cpu_ptr(&ecall_lock_flags));
> +
> + id = &shmem->id;
> + xmit = &shmem->data;
> + id->idx = cpu_to_le(i);
> + xmit->tdata1 = cpu_to_le(bp->tdata1);
[Severity: High]
Writing to id->idx and xmit->tdata1 here accesses the same shared memory union.
Does writing to id->idx corrupt the aliased xmit->tstate before it is sent to
the hypervisor?
> + xmit->tdata2 = cpu_to_le(bp->tdata2);
> + xmit->tdata3 = cpu_to_le(bp->tdata3);
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIG_UPDATE,
> + 1, 0, 0, 0, 0, 0);
[Severity: High]
Are we invoking the update call with a hardcoded zero count in a1? This seems
like it would make the update a hardware no-op and cause breakpoint updates
to silently fail.
> + if (ret.error)
> + pr_warn("Failed to update trigger %d.\n", i);
[ ... ]
> +static int __init arch_hw_breakpoint_init(void)
> +{
[ ... ]
> + /* Allocate per-cpu shared memory */
> + sbi_dbtr_shmem = __alloc_percpu(sizeof(*sbi_dbtr_shmem) * dbtr_total_num,
> + PAGE_SIZE);
> +
> + if (!sbi_dbtr_shmem) {
> + pr_warn("Failed to allocate shared memory.\n");
> + rc = -ENOMEM;
> + goto out;
> + }
> +
> + /* Hotplug handler to register/unregister shared memory with SBI */
> + rc = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
> + "riscv/hw_breakpoint:prepare",
> + arch_smp_setup_sbi_shmem,
> + arch_smp_teardown_sbi_shmem);
> +
> + if (rc < 0) {
> + pr_warn("Failed to setup CPU hotplug state\n");
> + free_percpu(sbi_dbtr_shmem);
> + return rc;
> + }
[Severity: High]
If cpuhp_setup_state() fails, we free sbi_dbtr_shmem. However, since the PMU
has already been registered earlier in the generic subsystem, can users still
create hardware breakpoints and cause a use-after-free when they access the
freed sbi_dbtr_shmem?
> + out:
> + return rc;
[Severity: Low]
When cpuhp_setup_state() succeeds, it returns a positive state ID which is
stored in rc. Should this initcall return 0 on success to avoid the initcall
core logging an erroneous failure message?
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260930063919.608911-1-himanshu.chauhan@oss.qualcomm.com?part=1
next prev parent reply other threads:[~2026-09-30 6:58 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 6:39 [PATCH v7 0/8] riscv: Introduce support for hardware break/watchpoints Himanshu Chauhan
2026-09-30 6:39 ` [PATCH v7 1/8] " Himanshu Chauhan
2026-09-30 6:58 ` sashiko-bot [this message]
2026-09-30 6:39 ` [PATCH v7 2/8] riscv: Add breakpoint and watchpoint test for riscv Himanshu Chauhan
2026-09-30 6:49 ` sashiko-bot
2026-09-30 6:39 ` [PATCH v7 3/8] riscv: ptrace support for hardware break/watchpoints Himanshu Chauhan
2026-09-30 6:56 ` sashiko-bot
2026-09-30 6:39 ` [PATCH v7 4/8] selftests/breakpoints: extend riscv test for ptrace hw break/watchpoints Himanshu Chauhan
2026-09-30 6:51 ` sashiko-bot
2026-09-30 6:39 ` [PATCH v7 5/8] RISC-V: Add fetch and decode helpers to a common file Himanshu Chauhan
2026-09-30 6:50 ` sashiko-bot
2026-09-30 6:39 ` [PATCH v7 6/8] riscv: Add software supported single stepping with mc/mc6 triggers Himanshu Chauhan
2026-09-30 6:56 ` sashiko-bot
2026-09-30 6:39 ` [PATCH v7 7/8] perf tests: add noinline to __test_function Himanshu Chauhan
2026-09-30 6:45 ` sashiko-bot
2026-09-30 6:39 ` [PATCH v7 8/8] MAINTAINERS: Add entry for RISC-V Debugging Himanshu Chauhan
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=20260930065823.14F431F000FF@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=himanshu.chauhan@oss.qualcomm.com \
--cc=linux-perf-users@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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