Re: [PATCH v9 3/5] drivers: firmware: add riscv SSE support
Conor Dooley <[email protected]> Fri, 22 May 2026 17:40:58 +0100
| Newsgroups | dev.linux.lists.sashiko,org.infradead.lists.linux-riscv,org.kernel.vger.linux-perf-users |
|---|---|
| Message-ID | <20260522-gala-whacky-9aa54fbfb958@spud> |
--VnWZGohPBEbrtc2y Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Sat, May 09, 2026 at 02:38:15PM +0000, [email protected] wrote: > Thank you for your contribution! Sashiko AI review found 12 potential iss= ue(s) to consider: > - [Critical] Missing braces around `scoped_guard(cpus_read_lock)` in `sse= _event_unregister` releases lock prematurely, leading to a race condition a= nd use-after-free. > - [High] Event registration rollback uses the wrong revert function, leak= ing firmware registrations and causing use-after-free. > - [High] Missing validation of `riscv_hartid_to_cpuid` return value leads= to out-of-bounds array access. > - [High] `sse_cpu_teardown` causes out-of-bounds array access on single-C= PU teardown and fails to unregister global events across reboots. > - [High] 64-bit physical addresses are silently truncated to 32 bits on R= V32. > - [High] CPU hotplug unconditionally enables local events, ignoring drive= r-defined disabled states. > - [High] Early release of `cpus_read_lock` in `sse_event_register` causes= missed event registration on hotplugged CPUs. > - [High] `sse_event_alloc_global` calls `smp_processor_id()` in a preempt= ible context. > - [High] Missing synchronization in `sse_event_unregister()` leads to use= -after-free with concurrently executing hardware event handlers. > - [Medium] `sse_event_set_target_cpu_nolock` silently ignores attribute w= rite failures. > - [Medium] Mixing `goto` error handling with `guard()` and `scoped_guard(= )` declarations violates subsystem cleanup guidelines. > - [Medium] The commit message claims clients can 'complete SSE events', b= ut no such API exists. FWIW, I would like to see some responses to these sashiko reports, they look pretty numerous, so there's a good chance one of them is valid! The 2nd medium one looks valid, can probably hit it with a DEFINE_FREE() for sse_event_free(event) and just return? The critical one I think deserves a comment in the code if what's there is okay. Cheers, Conor. > -- >=20 > commit 1f43f4083e7681521e63a564e1ff80519c1569f5 > Author: Cl=E9ment L=E9ger <[email protected]> > Subject: drivers: firmware: add riscv SSE support >=20 > This commit adds a driver-level interface for RISC-V SSE support, allowing > Linux clients to register handlers and manage SSE events. It supports both > local and global events and handles CPU hotplug and PM transitions. >=20 > > Add a driver-level interface for RISC-V SSE support. The interface= lets > > Linux clients register handlers and enable, disable and complete SSE > > events. It will be used by PMU and GHES drivers. >=20 > The commit message states the interface lets clients complete SSE events. > However, there does not appear to be any API provided to complete an even= t. >=20 > Could the commit message be updated to reflect the actual implementation? >=20 > > diff --git a/drivers/firmware/riscv/riscv_sbi_sse.c b/drivers/firmware/= riscv/riscv_sbi_sse.c > > new file mode 100644 > > index 0000000000000..830011ddd33f7 > > --- /dev/null > > +++ b/drivers/firmware/riscv/riscv_sbi_sse.c > > @@ -0,0 +1,694 @@ > [ ... ] > > +static int sse_event_attr_get_no_lock(struct sse_registered_event *reg= _evt, > > + unsigned long attr_id, unsigned long *val) > > +{ > > + struct sbiret sret; > > + u32 evt =3D reg_evt->event->evt_id; > > + unsigned long phys; > > + > > + phys =3D sse_event_get_attr_phys(reg_evt); >=20 > Can this physical address be truncated on RV32 systems? >=20 > When CONFIG_ARCH_PHYS_ADDR_T_64BIT is enabled, physical addresses are 64 = bits, > but unsigned long is 32 bits. Will the firmware receive a corrupted 32-bit > physical address since the ABI expects 64-bit values to be passed in two > registers on RV32? >=20 > [ ... ] > > +static int sse_event_attr_set_nolock(struct sse_registered_event *reg_= evt, > > + unsigned long attr_id, unsigned long val) > > +{ > > + struct sbiret sret; > > + u32 evt =3D reg_evt->event->evt_id; > > + unsigned long phys; > > + > > + reg_evt->attr =3D val; > > + phys =3D sse_event_get_attr_phys(reg_evt); >=20 > Is this subject to the same RV32 physical address truncation mentioned ab= ove? >=20 > [ ... ] > > +static int sse_event_set_target_cpu_nolock(struct sse_event *event, > > + unsigned int cpu) > > +{ > > + unsigned long hart_id =3D cpuid_to_hartid_map(cpu); > > + struct sse_registered_event *reg_evt =3D event->global; > > + u32 evt =3D event->evt_id; > > + bool was_enabled; > > + int ret; > > + > > + if (!sse_event_is_global(evt)) > > + return -EINVAL; > > + > > + was_enabled =3D reg_evt->is_enabled; > > + if (was_enabled) > > + sse_event_disable_local(event); > > + > > + ret =3D sse_event_attr_set_nolock(reg_evt, SBI_SSE_ATTR_PREFERRED_HAR= T, > > + hart_id); > > + if (ret =3D=3D 0) > > + sse_global_event_update_cpu(event, cpu); > > + > > + if (was_enabled) > > + sse_event_enable_local(event); > > + > > + return 0; > > +} >=20 > Should this function return the error code instead of unconditionally ret= urning 0? >=20 > If the SBI call to set the preferred hart fails, the error is masked from= the > caller, leaving the kernel thinking the migration succeeded while the fir= mware > state is out of sync. >=20 > [ ... ] > > +static int sse_event_alloc_global(struct sse_event *event) > > +{ > > + int err; > > + struct sse_registered_event *reg_evt; > > + > > + reg_evt =3D kzalloc_obj(*reg_evt, GFP_KERNEL); > > + if (!reg_evt) > > + return -ENOMEM; > > + > > + event->global =3D reg_evt; > > + err =3D sse_event_init_registered(smp_processor_id(), reg_evt, event); >=20 > Is it safe to call smp_processor_id() here? >=20 > sse_event_alloc_global() is invoked from sse_event_register(), which only > holds a mutex and runs with preemption enabled. Could this trigger a warn= ing > if CONFIG_DEBUG_PREEMPT is enabled, or allow the thread to migrate > immediately after, yielding an invalid CPU ID? >=20 > [ ... ] > > +struct sse_event *sse_event_register(u32 evt, u32 priority, > > + sse_event_handler_fn *handler, void *arg) > > +{ > > + struct sse_event *event; > > + int cpu; > > + int ret =3D 0; > > + > > + if (!sse_available) > > + return ERR_PTR(-EOPNOTSUPP); > > + > > + guard(mutex)(&sse_mutex); >=20 > This isn't a bug, but is it intended to mix goto-based error handling with > scope-based guard declarations in the same function? >=20 > The cleanup subsystem documentation recommends against this pattern as it= can > create confusing ownership semantics. >=20 > > + if (sse_event_get(evt)) > > + return ERR_PTR(-EEXIST); > > + > > + event =3D sse_event_alloc(evt, priority, handler, arg); > > + if (IS_ERR(event)) > > + return event; > > + > > + scoped_guard(cpus_read_lock) { > > + if (sse_event_is_global(evt)) { > > + unsigned long preferred_hart; > > + > > + ret =3D sse_event_attr_get_no_lock(event->global, > > + SBI_SSE_ATTR_PREFERRED_HART, > > + &preferred_hart); > > + if (ret) > > + goto err_event_free; > > + > > + cpu =3D riscv_hartid_to_cpuid(preferred_hart); >=20 > Can riscv_hartid_to_cpuid() return -ENOENT here? >=20 > If the firmware returns an unknown or unmapped hart ID, it appears the > negative value is passed to sse_global_event_update_cpu(), which uses it = to > index the logical hart array via cpuid_to_hartid_map(), potentially causi= ng > an out-of-bounds memory read. >=20 > > + sse_global_event_update_cpu(event, cpu); > > + > > + ret =3D sse_sbi_register_event(event, event->global); > > + if (ret) > > + goto err_event_free; > > + > > + } else { > > + ret =3D sse_on_each_cpu(event, SBI_SSE_EVENT_REGISTER, > > + SBI_SSE_EVENT_DISABLE); >=20 > Should the revert function be SBI_SSE_EVENT_UNREGISTER instead of > SBI_SSE_EVENT_DISABLE? >=20 > Disabling an event merely masks it without unregistering it from the firm= ware. > If registration fails on any CPU, does this leave the firmware holding an > active registration pointing to freed memory after the error path frees t= he > event structures? >=20 > > + if (ret) > > + goto err_event_free; > > + } > > + } > > + > > + scoped_guard(spinlock, &events_list_lock) > > + list_add(&event->list, &events); >=20 > Because the cpus_read_lock is released before acquiring events_list_lock = to > add the event to the global list, could a CPU hotplug operation occur in > this window? >=20 > If so, sse_cpu_online() would iterate over the list and miss the new even= t, > resulting in the event not being registered on the new CPU. >=20 > > + > > + return event; > > + > > +err_event_free: > > + sse_event_free(event); > > + > > + return ERR_PTR(ret); > > +} > [ ... ] > > +void sse_event_unregister(struct sse_event *event) > > +{ > > + scoped_guard(mutex, &sse_mutex) { > > + scoped_guard(cpus_read_lock) > > + sse_event_unregister_nolock(event); >=20 > Is there a missing compound block around the cpus_read_lock guard? >=20 > Without braces, the lock is dropped before acquiring events_list_lock. Co= uld a > concurrent CPU hotplug operation acquire the list lock, find the partially > unregistered event still in the list, and erroneously register and enable > it in the firmware for the new CPU? >=20 > > + > > + scoped_guard(spinlock, &events_list_lock) > > + list_del(&event->list); > > + > > + sse_event_free(event); >=20 > Since SBI events are delivered as NMI-like notifications, could an event = have > just fired and its handler be actively executing on another CPU? >=20 > Without synchronization like waiting for active handlers or RCU grace per= iods, > it seems sse_handle_event() might dereference evt->handler and > evt->handler_arg after the memory has been freed. >=20 > > + } > > +} > [ ... ] > > +static int sse_cpu_online(unsigned int cpu) > > +{ > > + struct sse_event *event; > > + > > + scoped_guard(spinlock, &events_list_lock) { > > + list_for_each_entry(event, &events, list) { > > + if (sse_event_is_global(event->evt_id)) > > + continue; > > + > > + sse_event_register_local(event); > > + if (sse_get_reg_evt(event)) > > + sse_event_enable_local(event); >=20 > Should this check reg_evt->is_enabled before unconditionally enabling the= event? >=20 > If a driver registers an event but intentionally leaves it disabled, it l= ooks > like it will unexpectedly become enabled on the newly hotplugged CPU. >=20 > > + } > > + } > > + > > + /* Ready to handle events. Unmask SSE. */ > > + return sse_events_unmask(); > > +} > > + > > +static int sse_cpu_teardown(unsigned int cpu) > > +{ > > + int ret =3D 0; > > + unsigned int next_cpu; > > + struct sse_event *event; > > + struct sse_registered_event *reg_evt; > > + > > + /* Mask the sse events */ > > + ret =3D sse_events_mask(); > > + if (ret) > > + return ret; > > + > > + scoped_guard(spinlock, &events_list_lock) { > > + list_for_each_entry(event, &events, list) { > > + /* Disable local event on current cpu */ > > + if (!sse_event_is_global(event->evt_id)) { > > + reg_evt =3D sse_get_reg_evt(event); > > + if (reg_evt->is_enabled) > > + sse_event_disable_local(event); > > + > > + sse_sbi_unregister_event(event); > > + continue; > > + } > > + > > + if (event->cpu !=3D smp_processor_id()) > > + continue; > > + > > + /* Update destination hart for global event */ > > + next_cpu =3D cpumask_any_but(cpu_online_mask, cpu); > > + ret =3D sse_event_set_target_cpu_nolock(event, next_cpu); >=20 > What happens if there are no other CPUs online during teardown? >=20 > If cpumask_any_but() returns a value greater than or equal to nr_cpu_ids, > passing it to sse_event_set_target_cpu_nolock() might result in an > out-of-bounds array read in cpuid_to_hartid_map(). >=20 > Furthermore, by migrating rather than unregistering global events on rebo= ot, > does this break kexec by leaving the event actively registered for the ne= xt > kernel? >=20 > > + } > > + } > > + > > + return ret; > > +} >=20 > --=20 > Sashiko AI review =B7 https://sashiko.dev/#/patchset/cover.1778331862.git= [email protected]?part=3D3 --VnWZGohPBEbrtc2y Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQRh246EGq/8RLhDjO14tDGHoIJi0gUCahCHGgAKCRB4tDGHoIJi 0o9QAP9Qn4XnM9BjrA2zaCDTOzuEX0fWl1cZoHFZq3tzt7J7RwD+NTYxsPP7V4nv pNk45zI4/X7URCcQE72Q4wWvL1jQzAE= =2CvW -----END PGP SIGNATURE----- --VnWZGohPBEbrtc2y--