Skip to content

Commit e1a7bfe

Browse files
committed
ALSA: control: Fix race between adding and removing a user element
The procedure for adding a user control element has some window opened for race against the concurrent removal of a user element. This was caught by syzkaller, hitting a KASAN use-after-free error. This patch addresses the bug by wrapping the whole procedure to add a user control element with the card->controls_rwsem, instead of only around the increment of card->user_ctl_count. This required a slight code refactoring, too. The function snd_ctl_add() is split to two parts: a core function to add the control element and a part calling it. The former is called from the function for adding a user control element inside the controls_rwsem. One change to be noted is that snd_ctl_notify() for adding a control element gets called inside the controls_rwsem as well while it was called outside the rwsem. But this should be OK, as snd_ctl_notify() takes another (finer) rwlock instead of rwsem, and the call of snd_ctl_notify() inside rwsem is already done in another code path. Reported-by: syzbot+dc09047bce3820621ba2@syzkaller.appspotmail.com Cc: <stable@vger.kernel.org> Signed-off-by: Takashi Iwai <tiwai@suse.de>
1 parent 9a20332 commit e1a7bfe

File tree

1 file changed

+45
-35
lines changed

1 file changed

+45
-35
lines changed

sound/core/control.c

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,40 @@ static int snd_ctl_find_hole(struct snd_card *card, unsigned int count)
348348
return 0;
349349
}
350350

351+
/* add a new kcontrol object; call with card->controls_rwsem locked */
352+
static int __snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
353+
{
354+
struct snd_ctl_elem_id id;
355+
unsigned int idx;
356+
unsigned int count;
357+
358+
id = kcontrol->id;
359+
if (id.index > UINT_MAX - kcontrol->count)
360+
return -EINVAL;
361+
362+
if (snd_ctl_find_id(card, &id)) {
363+
dev_err(card->dev,
364+
"control %i:%i:%i:%s:%i is already present\n",
365+
id.iface, id.device, id.subdevice, id.name, id.index);
366+
return -EBUSY;
367+
}
368+
369+
if (snd_ctl_find_hole(card, kcontrol->count) < 0)
370+
return -ENOMEM;
371+
372+
list_add_tail(&kcontrol->list, &card->controls);
373+
card->controls_count += kcontrol->count;
374+
kcontrol->id.numid = card->last_numid + 1;
375+
card->last_numid += kcontrol->count;
376+
377+
id = kcontrol->id;
378+
count = kcontrol->count;
379+
for (idx = 0; idx < count; idx++, id.index++, id.numid++)
380+
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
381+
382+
return 0;
383+
}
384+
351385
/**
352386
* snd_ctl_add - add the control instance to the card
353387
* @card: the card instance
@@ -364,45 +398,18 @@ static int snd_ctl_find_hole(struct snd_card *card, unsigned int count)
364398
*/
365399
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
366400
{
367-
struct snd_ctl_elem_id id;
368-
unsigned int idx;
369-
unsigned int count;
370401
int err = -EINVAL;
371402

372403
if (! kcontrol)
373404
return err;
374405
if (snd_BUG_ON(!card || !kcontrol->info))
375406
goto error;
376-
id = kcontrol->id;
377-
if (id.index > UINT_MAX - kcontrol->count)
378-
goto error;
379407

380408
down_write(&card->controls_rwsem);
381-
if (snd_ctl_find_id(card, &id)) {
382-
up_write(&card->controls_rwsem);
383-
dev_err(card->dev, "control %i:%i:%i:%s:%i is already present\n",
384-
id.iface,
385-
id.device,
386-
id.subdevice,
387-
id.name,
388-
id.index);
389-
err = -EBUSY;
390-
goto error;
391-
}
392-
if (snd_ctl_find_hole(card, kcontrol->count) < 0) {
393-
up_write(&card->controls_rwsem);
394-
err = -ENOMEM;
395-
goto error;
396-
}
397-
list_add_tail(&kcontrol->list, &card->controls);
398-
card->controls_count += kcontrol->count;
399-
kcontrol->id.numid = card->last_numid + 1;
400-
card->last_numid += kcontrol->count;
401-
id = kcontrol->id;
402-
count = kcontrol->count;
409+
err = __snd_ctl_add(card, kcontrol);
403410
up_write(&card->controls_rwsem);
404-
for (idx = 0; idx < count; idx++, id.index++, id.numid++)
405-
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
411+
if (err < 0)
412+
goto error;
406413
return 0;
407414

408415
error:
@@ -1361,9 +1368,12 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
13611368
kctl->tlv.c = snd_ctl_elem_user_tlv;
13621369

13631370
/* This function manage to free the instance on failure. */
1364-
err = snd_ctl_add(card, kctl);
1365-
if (err < 0)
1366-
return err;
1371+
down_write(&card->controls_rwsem);
1372+
err = __snd_ctl_add(card, kctl);
1373+
if (err < 0) {
1374+
snd_ctl_free_one(kctl);
1375+
goto unlock;
1376+
}
13671377
offset = snd_ctl_get_ioff(kctl, &info->id);
13681378
snd_ctl_build_ioff(&info->id, kctl, offset);
13691379
/*
@@ -1374,10 +1384,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
13741384
* which locks the element.
13751385
*/
13761386

1377-
down_write(&card->controls_rwsem);
13781387
card->user_ctl_count++;
1379-
up_write(&card->controls_rwsem);
13801388

1389+
unlock:
1390+
up_write(&card->controls_rwsem);
13811391
return 0;
13821392
}
13831393

0 commit comments

Comments
 (0)