Linux Perf Users
 help / color / mirror / Atom feed
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

  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