From: sashiko-bot@kernel.org
To: "Himanshu Chauhan" <himanshu.chauhan@oss.qualcomm.com>,
"Paul Walmsley" <pjw@kernel.org>,
"Palmer Dabbelt" <palmer@dabbelt.com>,
"Albert Ou" <aou@eecs.berkeley.edu>
Cc: "Alexandre Ghiti" <alex@ghiti.fr>,
"Jesse Taube" <jtaubepe@redhat.com>,
"Oleg Nesterov" <oleg@redhat.com>, "Kees Cook" <kees@kernel.org>,
"Peter Zijlstra" <peterz@infradead.org>,
"Ingo Molnar" <mingo@redhat.com>,
"Arnaldo Carvalho de Melo" <acme@kernel.org>,
"Namhyung Kim" <namhyung@kernel.org>,
"Mark Rutland" <mark.rutland@arm.com>,
"Alexander Shishkin" <alexander.shishkin@linux.intel.com>,
"Jiri Olsa" <jolsa@kernel.org>, "Ian Rogers" <irogers@google.com>,
"Adrian Hunter" <adrian.hunter@intel.com>,
"James Clark" <james.clark@linaro.org>,
"Shuah Khan" <shuah@kernel.org>,
linux-mm@kvack.org, linux-perf-users@vger.kernel.org
Subject: Re: [PATCH v8 4/8] riscv: add regset access for hardware breakpoints
Date: Sun, 08 Nov 2026 02:41:37 +0000 [thread overview]
Message-ID: <sashiko-outbox-203525@kernel.org> (raw)
In-Reply-To: <20261107023917.40606-5-himanshu.chauhan@oss.qualcomm.com>
Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [High] riscv: ptrace: broken breakpoint disable in
ptrace_hbp_fill_attr_ctrl()
- [Medium] riscv: ptrace: zero-filled holes in compat_riscv_user_regset
cause div-by-zero
- [Medium] riscv: ptrace: ignored control fields in decode_ctrl_reg()
- [Medium] riscv: ptrace: wrong return value and slot bound in
riscv_hw_break_get()
- [Medium] riscv: ptrace: leaked perf_event on error in
riscv_hw_break_set()
--
commit 5940d70e24c6c5b6aa9c305717a9b36acc0903b2
Author: Himanshu Chauhan <himanshu.chauhan@oss.qualcomm.com>
riscv: add regset access for hardware breakpoints
This commit adds the NT_RISCV_HW_BREAK and NT_RISCV_HW_WATCH regsets to allow
reading and programming RISC-V hardware breakpoint and watchpoint slots via
PTRACE_GETREGSET and PTRACE_SETREGSET.
> diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c
> index be076865d02d0..9959a8e4a3079 100644
> --- a/arch/riscv/kernel/ptrace.c
> +++ b/arch/riscv/kernel/ptrace.c
> @@ -37,6 +37,10 @@ enum riscv_regset {
> #ifdef CONFIG_RISCV_USER_CFI
> REGSET_CFI,
> #endif
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + REGSET_RISCV_HW_BREAK,
> + REGSET_RISCV_HW_WATCH,
> +#endif
> };
>
> static int riscv_gpr_get(struct task_struct *target,
> @@ -526,6 +530,302 @@ static struct perf_event *ptrace_hbp_create(enum riscv_hbp_type hbp_type,
> return bp;
> }
>
> +static int ptrace_hbp_note_to_type(unsigned int note_type,
> + enum riscv_hbp_type *hbp_type)
> +{
> + switch (note_type) {
> + case NT_RISCV_HW_BREAK:
> + *hbp_type = RISCV_HBP_BREAK;
> + return 0;
> + case NT_RISCV_HW_WATCH:
> + *hbp_type = RISCV_HBP_WATCH;
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ptrace_hbp_fill_attr_ctrl(enum riscv_hbp_type hbp_type,
> + struct arch_hw_breakpoint *bpctrl,
> + struct perf_event_attr *attr)
> +{
> + int len, type;
> +
> + attr->disabled = 0;
> + type = bpctrl->type;
> + len = bpctrl->len;
> +
> + switch (hbp_type) {
> + case RISCV_HBP_BREAK:
> + if ((type & HW_BREAKPOINT_X) != type)
> + return -EINVAL;
> + break;
> + case RISCV_HBP_WATCH:
> + if ((type & HW_BREAKPOINT_RW) != type)
> + return -EINVAL;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + attr->bp_len = len;
> + attr->bp_type = type;
> + attr->bp_addr = bpctrl->address;
> +
> + return 0;
> +}
[ ... ]
> +static u32 encode_ctrl_reg(struct perf_event *bp)
> +{
> + struct arch_hw_breakpoint *bpctrl = counter_arch_bp(bp);
> + u32 ctrl = 0;
> +
> + /* Expose the generic UAPI bp_type values in ptrace control bits. */
> + ctrl |= HWDEBUG_MK_TYPE(bp->attr.bp_type);
> + ctrl |= HWDEBUG_MK_MATCH(bpctrl->match);
> + ctrl |= HWDEBUG_MK_SELECT(bpctrl->select);
> + ctrl |= HWDEBUG_MK_WHEN(bpctrl->time);
> + ctrl |= HWDEBUG_MK_SIZE(bp->attr.bp_len);
> + ctrl |= HWDEBUG_MK_CHAIN(bpctrl->chain);
> +
> + return ctrl;
> +}
[Severity: High]
As reported by Rui Qi on v6 [1], how can a tracer disable a hardware
breakpoint or watchpoint slot, or write a user_hwdebug_state buffer with
unused zeroed slots, when ptrace_hbp_fill_attr_ctrl() unconditionally sets
attr->disabled = 0?
The control word has no separate enable bit, so an unused or disabled slot
has control == 0 (bpctrl->type == HW_BREAKPOINT_EMPTY). Because (0 & mask) !=
0 is false, ptrace_hbp_fill_attr_ctrl() passes attr->disabled = 0 and
attr->bp_type = 0 to modify_user_hw_breakpoint(), which fails with -EINVAL in
hw_breakpoint_arch_parse() -> rv_init_mcontrol_trigger() while re-enabling an
already-active event at the new address set by ptrace_hbp_set_addr().
Conversely, encode_ctrl_reg() encodes bp->attr.bp_type without checking
bp->attr.disabled, so disabled events in a slot are reported as active by
PTRACE_GETREGSET. When uctrl == 0 (or type == HW_BREAKPOINT_EMPTY),
ptrace_hbp_fill_attr_ctrl() should set attr->disabled = 1 and return early,
and encode_ctrl_reg() should return 0 when bp->attr.disabled is set.
[ ... ]
> +static void decode_ctrl_reg(u32 uctrl, struct arch_hw_breakpoint *bpctrl)
> +{
> + bpctrl->type = HWDEBUG_TYPE(uctrl);
> + bpctrl->match = HWDEBUG_MATCH(uctrl);
> + bpctrl->select = HWDEBUG_SELECT(uctrl);
> + bpctrl->time = HWDEBUG_WHEN(uctrl);
> + bpctrl->len = HWDEBUG_SIZE(uctrl);
> + bpctrl->chain = HWDEBUG_CHAIN(uctrl);
> +}
> +
> +static int ptrace_hbp_set_ctrl(enum riscv_hbp_type hbp_type,
> + struct task_struct *tsk,
> + unsigned long idx,
> + u32 uctrl)
> +{
> + int err;
> + struct perf_event *bp;
> + struct perf_event_attr attr;
> + struct arch_hw_breakpoint bpctrl;
> +
> + bp = ptrace_hbp_get_initialised_bp(hbp_type, tsk, idx);
> + if (IS_ERR(bp)) {
> + err = PTR_ERR(bp);
> + return err;
> + }
> +
> + attr = bp->attr;
> + decode_ctrl_reg(uctrl, &bpctrl);
> + bpctrl.address = attr.bp_addr;
> + err = ptrace_hbp_fill_attr_ctrl(hbp_type, &bpctrl, &attr);
> + if (err)
> + return err;
> +
> + return modify_user_hw_breakpoint(bp, &attr);
> +}
[Severity: Medium]
Does ptrace_hbp_set_ctrl() silently discard the match, select, time, and
chain fields decoded by decode_ctrl_reg(), as well as any non-zero reserved
bits in uctrl?
decode_ctrl_reg() unpacks HWDEBUG_MATCH, HWDEBUG_SELECT, HWDEBUG_WHEN, and
HWDEBUG_CHAIN into the stack-local bpctrl, but ptrace_hbp_fill_attr_ctrl()
only copies len, type, and address into attr, and modify_user_hw_breakpoint()
re-initializes counter_arch_bp(bp) from attr alone. Any non-zero values
written to those fields or to reserved bits [31:17] are accepted without
taking effect and read back as 0 in encode_ctrl_reg(), preventing userspace
from detecting unsupported trigger modes.
> +static int ptrace_hbp_set_addr(enum riscv_hbp_type hbp_type,
> + struct task_struct *tsk,
> + unsigned long idx,
> + u64 addr)
> +{
> + int err;
> + struct perf_event *bp;
> + struct perf_event_attr attr;
> +
> + bp = ptrace_hbp_get_initialised_bp(hbp_type, tsk, idx);
> + if (IS_ERR(bp)) {
> + err = PTR_ERR(bp);
> + return err;
> + }
> +
> + attr = bp->attr;
> + attr.bp_addr = addr;
> + err = modify_user_hw_breakpoint(bp, &attr);
> + return err;
> +}
[Severity: Medium]
Does ptrace_hbp_set_addr() leave a newly created perf_event registered in
tsk->thread.debug when modify_user_hw_breakpoint() or the subsequent control
write in riscv_hw_break_set() fails?
When a slot is initially empty, ptrace_hbp_get_initialised_bp() calls
ptrace_hbp_create(), which registers a disabled perf_event and reserves one
of the task's shared hardware trigger slots via reserve_bp_slot(). If
modify_user_hw_breakpoint() fails (for example on an address >= TASK_SIZE) or
riscv_hw_break_set() fails while copying or applying ctrl, the event is not
unregistered, exhausting the task's hardware trigger slots until task exit or
execve.
> +#define PTRACE_HBP_ADDR_SZ sizeof(u64)
> +#define PTRACE_HBP_CTRL_SZ sizeof(u32)
> +#define PTRACE_HBP_PAD_SZ sizeof(u32)
> +
> +static int riscv_hw_break_get(struct task_struct *target,
> + const struct user_regset *regset,
> + struct membuf to)
> +{
> + unsigned int note_type = regset->core_note_type;
> + enum riscv_hbp_type hbp_type;
> + int ret, idx, num_slots;
> + u32 info, ctrl;
> + u64 addr;
> +
> + ret = ptrace_hbp_note_to_type(note_type, &hbp_type);
> + if (ret)
> + return ret;
> +
> + /* Resource info: number of available slots */
> + ret = ptrace_hbp_get_resource_info(hbp_type, &info);
> + if (ret)
> + return ret;
> +
> + membuf_write(&to, &info, sizeof(info));
> + membuf_zero(&to, sizeof(u32));
> +
> + /* Emit one (address, ctrl, pad) entry per available slot */
> + num_slots = (int)info;
> + for (idx = 0; idx < num_slots; idx++) {
> + ret = ptrace_hbp_get_addr(hbp_type, target, idx, &addr);
> + if (ret)
> + return ret;
> + ret = ptrace_hbp_get_ctrl(hbp_type, target, idx, &ctrl);
> + if (ret)
> + return ret;
> + membuf_store(&to, addr);
> + membuf_store(&to, ctrl);
> + membuf_zero(&to, sizeof(u32));
> + }
> + return 0;
> +}
> +
> +static int riscv_hw_break_set(struct task_struct *target,
> + const struct user_regset *regset,
> + unsigned int pos, unsigned int count,
> + const void *kbuf, const void __user *ubuf)
> +{
[ ... ]
> + /* (address, ctrl) registers */
> + limit = regset->n * regset->size;
> + while (count && offset < limit) {
[Severity: Medium]
Should riscv_hw_break_get() return to.left instead of 0, and should
riscv_hw_break_set() bound writes by num_slots rather than all 16 entries in
struct user_hwdebug_state?
__regset_get() computes the number of bytes produced as size - res, so
returning 0 after writing only num_slots entries causes PTRACE_GETREGSET and
ELF core dumps to report all 16 slots (264 bytes) as populated even when
hw_breakpoint_slots() is smaller. Meanwhile, riscv_hw_break_set() allows
programming slot indices num_slots <= idx < RISCV_MAX_BP, creating active
breakpoints that riscv_hw_break_get() never reads back.
[ ... ]
> @@ -919,6 +1237,28 @@ static const struct user_regset compat_riscv_user_regset[] = {
> .set = riscv_fpr_set,
> },
> #endif
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + /*
> + * The hardware-debug regset uses fixed-width UAPI fields, so the
> + * native layout is also the compat layout.
> + */
> + [REGSET_RISCV_HW_BREAK] = {
> + USER_REGSET_NOTE_TYPE(RISCV_HW_BREAK),
> + .n = sizeof(struct user_hwdebug_state) / sizeof(u32),
> + .size = sizeof(u32),
> + .align = sizeof(u32),
> + .regset_get = riscv_hw_break_get,
> + .set = riscv_hw_break_set,
> + },
> + [REGSET_RISCV_HW_WATCH] = {
> + USER_REGSET_NOTE_TYPE(RISCV_HW_WATCH),
> + .n = sizeof(struct user_hwdebug_state) / sizeof(u32),
> + .size = sizeof(u32),
> + .align = sizeof(u32),
> + .regset_get = riscv_hw_break_get,
> + .set = riscv_hw_break_set,
> + },
> +#endif
> };
[Severity: Medium]
Does indexing compat_riscv_user_regset[] with REGSET_RISCV_HW_BREAK and
REGSET_RISCV_HW_WATCH create zero-initialized struct user_regset holes that
cause a division by zero in ptrace_regset()?
In enum riscv_regset, REGSET_RISCV_HW_BREAK and REGSET_RISCV_HW_WATCH follow
REGSET_V, REGSET_TAGGED_ADDR_CTRL, and REGSET_CFI, which are omitted from
compat_riscv_user_regset[]. When any of CONFIG_RISCV_ISA_V,
CONFIG_RISCV_ISA_SUPM, or CONFIG_RISCV_USER_CFI is enabled, those intermediate
slots in compat_riscv_user_regset[] are zero-filled with core_note_type == 0
and size == 0. Calling ptrace(PTRACE_GETREGSET, pid, 0, &iov) on a compat
task matches the hole in find_regset(view, 0) and evaluates kiov->iov_len %
regset->size with regset->size == 0 in kernel/ptrace.c:ptrace_regset().
[1] https://lore.kernel.org/r/5a7c08bd-4313-4424-b649-a19ffe5242e4@bytedance.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261107023917.40606-1-himanshu.chauhan@oss.qualcomm.com?part=4
next prev parent reply other threads:[~2026-11-08 2:41 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 2:39 [PATCH v8 0/8] riscv: Introduce support for hardware break/watchpoints Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-07 2:39 ` [PATCH v8 1/8] " Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-08 2:41 ` sashiko-bot
2026-11-07 2:39 ` [PATCH v8 2/8] riscv: Add breakpoint and watchpoint test for riscv Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-08 2:41 ` sashiko-bot
2026-11-07 2:39 ` [PATCH v8 3/8] riscv: add ptrace requests for hardware breakpoints Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-07 7:04 ` kernel test robot
2026-11-07 7:04 ` kernel test robot
2026-11-08 2:41 ` sashiko-bot
2026-11-07 2:39 ` [PATCH v8 4/8] riscv: add regset access " Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-08 2:41 ` sashiko-bot [this message]
2026-11-07 2:39 ` [PATCH v8 5/8] selftests/breakpoints: extend riscv test for ptrace hw break/watchpoints Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-08 2:41 ` sashiko-bot
2026-11-07 2:39 ` [PATCH v8 6/8] riscv: Add shared instruction fetch and decode helpers Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-08 2:41 ` sashiko-bot
2026-11-07 2:39 ` [PATCH v8 7/8] riscv: Add software supported single stepping with mc/mc6 triggers Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-08 2:41 ` sashiko-bot
2026-11-07 2:39 ` [PATCH v8 8/8] perf tests: keep bp_signal memory accesses visible Himanshu Chauhan
2026-11-07 2:39 ` Himanshu Chauhan
2026-11-08 2:41 ` 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=sashiko-outbox-203525@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alex@ghiti.fr \
--cc=alexander.shishkin@linux.intel.com \
--cc=aou@eecs.berkeley.edu \
--cc=himanshu.chauhan@oss.qualcomm.com \
--cc=irogers@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=jtaubepe@redhat.com \
--cc=kees@kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=oleg@redhat.com \
--cc=palmer@dabbelt.com \
--cc=peterz@infradead.org \
--cc=pjw@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=shuah@kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.