[PATCH 5/8] ALSA: usb-audio: Fix data race at mixer_ctl_feature_info()

From: Takashi Iwai

Date: Tue Oct 06 2026 - 09:41:24 EST


The info callback for USB-audio mixer controls for feature unit has a
dynamic initialization of the contents with the check of
cval->initialized flag. But, since the info callback may be
concurrently called, this may lead to a data race, giving back an
inconsistent state. Similarly, get and put callbacks may have
concurrent accesses and can get bogus states.

For avoiding the data race, introduce a mutex locking for the
controls and protect against concurrent info callback calls.

Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Signed-off-by: Takashi Iwai <tiwai@xxxxxxx>
---
sound/usb/mixer.c | 11 ++++++++++-
sound/usb/mixer.h | 1 +
2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index 9d8007ea95d5..bc71d7210eb2 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -1539,6 +1539,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
uinfo->count = cval->channels;
if (cval->val_type != USB_MIXER_BOOLEAN &&
cval->val_type != USB_MIXER_INV_BOOLEAN) {
+ guard(mutex)(&cval->head.mixer->lock);
if (!cval->initialized) {
ret = get_min_max_with_quirks(cval, 0, kcontrol);
if ((ret >= 0 || ret == -EAGAIN) &&
@@ -1565,6 +1566,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int c, cnt, val, err;

+ guard(mutex)(&cval->head.mixer->lock);
ucontrol->value.integer.value[0] = cval->min;
if (cval->cmask) {
cnt = 0;
@@ -1595,10 +1597,12 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
- int max_val = get_max_exposed(cval);
+ int max_val;
int c, cnt, val, oval, err;
int changed = 0;

+ guard(mutex)(&cval->head.mixer->lock);
+ max_val = get_max_exposed(cval);
if (cval->cmask) {
cnt = 0;
for (c = 0; c < MAX_CHANNELS; c++) {
@@ -1646,6 +1650,7 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int val, err;

+ guard(mutex)(&cval->head.mixer->lock);
err = snd_usb_get_cur_mix_value(cval, 0, 0, &val);
if (err < 0)
return filter_error(cval, err);
@@ -2592,6 +2597,7 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int err, val;

+ guard(mutex)(&cval->head.mixer->lock);
err = get_cur_ctl_value(cval, cval->control << 8, &val);
if (err < 0) {
ucontrol->value.integer.value[0] = cval->min;
@@ -2609,6 +2615,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int val, oval, err;

+ guard(mutex)(&cval->head.mixer->lock);
err = get_cur_ctl_value(cval, cval->control << 8, &oval);
if (err < 0)
return filter_error(cval, err);
@@ -3249,6 +3256,7 @@ static void snd_usb_mixer_free(struct usb_mixer_interface *mixer)
}
usb_free_urb(mixer->rc_urb);
kfree(mixer->rc_setup_packet);
+ mutex_destroy(&mixer->lock);
kfree(mixer);
}

@@ -3885,6 +3893,7 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif)
mixer = kzalloc_obj(*mixer);
if (!mixer)
return -ENOMEM;
+ mutex_init(&mixer->lock);
mixer->chip = chip;
mixer->ignore_ctl_error = !!(chip->quirk_flags & QUIRK_FLAG_IGNORE_CTL_ERROR);
mixer->id_elems = kzalloc_objs(*mixer->id_elems, MAX_ID_ELEMS);
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h
index cf45c39cbccc..2ff4490f97c2 100644
--- a/sound/usb/mixer.h
+++ b/sound/usb/mixer.h
@@ -18,6 +18,7 @@ struct usb_mixer_interface {
struct usb_host_interface *hostif;
struct list_head list;
unsigned int ignore_ctl_error;
+ struct mutex lock; /* lock for feature unit callbacks */
/* UAC2 status interrupt endpoint; owned by mixer.c */
struct urb *urb;
/* array[MAX_ID_ELEMS], indexed by unit id */
--
2.55.0