Thread (74 messages) flat view 74 messages, 3 authors, 5d ago

Re: [RFC PATCH 1/2] HID: topping-m62: driver for the M62's vendor controls

From: sashiko-bot@kernel.org
Date: 2026-09-04 11:38:01

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Use-After-Free of `snd_kcontrol` and potentially `snd_card` in `topping_raw_event` due to dereferencing pointers after releasing the spinlock
- [High] Race condition in `topping_unbind` can strand delayed work `m62->keepalive`, leading to a Use-After-Free
- [Medium] API Contract Violation: Unconditional cleanup loop passes NULL pointers to `snd_ctl_remove()`, triggering kernel warnings
--

commit 172808f08cae9fcb08d59263a0babfa30be89c2c
Author: Mikhail Gavrilov [off-list ref]

    HID: topping-m62: driver for the M62's vendor controls
    
    The Topping M62 is a USB audio interface whose analogue input gains,
    output volumes and output source selectors are not described by the USB
    Audio Class. They live behind a vendor protocol on a HID-class
    interface, spoken by Topping's M Control Center, which has no Linux
    build.
quoted hunk ↗ jump to hunk
diff --git a/drivers/hid/hid-topping-m62.c b/drivers/hid/hid-topping-m62.c
index 0000000000000..0ef407ffb46b7
--- /dev/null
+++ b/drivers/hid/hid-topping-m62.c
[ ... ]
+static int topping_raw_event(struct hid_device *hdev,
+			     struct hid_report *report, u8 *data, int size)
+{
[ ... ]
+	scoped_guard(spinlock_irqsave, &m62->lock) {
+		card = m62->card;
+		if (m62->val[idx] != value) {
+			m62->val[idx] = value;
+			kctl = m62->kctl[idx];
+		}
+	}
+
+	/*
+	 * A frame that arrived just as the audio side was unbinding can
+	 * notify an id that has already gone.  snd_ctl_notify() takes a
+	 * copy of the id and touches nothing that unbind frees, so such
+	 * an event is merely wasted.
+	 */
+	if (card && kctl)
+		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
                                                                 ^^^^^^^^^

[Severity: High]
Does this code risk a use-after-free by passing a pointer to the kctl id
after releasing the lock?

If the audio component unbinds concurrently, topping_unbind() will execute,
clearing m62->card under the lock and freeing the kctl structures via
snd_ctl_remove(). 

When topping_raw_event() resumes and calls snd_ctl_notify(), the &kctl->id
pointer references freed memory. Because snd_ctl_notify() takes the ID by
reference rather than by copy, it will dereference this freed memory,
leading to memory corruption.

[ ... ]
+static void topping_drop_kctls(struct topping_m62 *m62, struct snd_card *card)
+{
+	int i;
+
+	for (i = 0; i < TOPPING_NUM_KCTLS; i++) {
+		snd_ctl_remove(card, m62->kctl[i]);
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Medium]
Will this unconditionally pass a NULL pointer to snd_ctl_remove() if a
control fails to allocate during probe?

If topping_add_kctl() fails, topping_bind() jumps to the err_drop label
and calls topping_drop_kctls(). The loop iterates over all indices in
the array and calls snd_ctl_remove() on them.

For any controls that were never allocated, m62->kctl[i] will be NULL.
Calling snd_ctl_remove() on a NULL pointer triggers ALSA's internal
snd_BUG_ON macro, which generates stack traces in dmesg.
+		m62->kctl[i] = NULL;
+	}
+}
[ ... ]
+static void topping_unbind(struct device *comp, struct device *master,
+			   void *master_data)
+{
+	struct hid_device *hdev = to_hid_device(comp);
+	struct topping_m62 *m62 = hid_get_drvdata(hdev);
+	struct snd_card *card = master_data;
+
+	cancel_delayed_work_sync(&m62->keepalive);
+
+	/*
+	 * Stop reporting before the controls go, so that a frame in
+	 * flight cannot look up a control this function is removing.
+	 */
+	scoped_guard(spinlock_irqsave, &m62->lock)
+		m62->card = NULL;
                ^^^^^^^^^^^^^^^^

[Severity: High]
Can a concurrent resume strand the delayed work and cause a use-after-free
if it is scheduled after cancel_delayed_work_sync() executes?

Because the work is cancelled before the m62->card synchronization flag is
cleared, a runtime resume occurring in this narrow window can observe that
m62->card is still valid. 

The topping_resume() function would then erroneously call
schedule_delayed_work(). Since the component is already unbound,
topping_remove() will eventually free the m62 structure without canceling
this newly scheduled work, allowing the timer to fire on freed memory.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260904112610.3286659-1-mikhail.v.gavrilov@gmail.com?part=1
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help