CVE-2014-4653: Use After Free (CWE-416)
value: sound/core/control.c in the ALSA control implementation in the Linux kernel before 3.15.2 does not ensure possession of a read/write lock, which allows local users to cause a denial of service (use-after-free) and obtain sensitive information from kernel memory by leveraging /dev/snd/controlCX access.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
Published: 2014-07-03T04:22Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: sound/core/control.c
Commit date: 2014-06-18 15:13:07 +0200
Commit message: ALSA: control: Don't access controls outside of protected regions

A control that is visible on the card->controls list can be freed at any time.
This means we must not access any of its memory while not holding the
controls_rw_lock. Otherwise we risk a use after free access.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Acked-by: Jaroslav Kysela <perex@perex.cz>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

Diff: diff --git a/sound/core/control.c b/sound/core/control.c
index 1f413c2865113d..5c49f976fc7b3c 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -328,10 +328,11 @@ static int snd_ctl_find_hole(struct snd_card *card, unsigned int count)
  */
 int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
 {
 	struct snd_ctl_elem_id id;
 	unsigned int idx;
+	unsigned int count;
 	int err = -EINVAL;
 
 	if (! kcontrol)
 		return err;
 	if (snd_BUG_ON(!card || !kcontrol->info))
@@ -356,12 +357,13 @@ int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
 	}
 	list_add_tail(&kcontrol->list, &card->controls);
 	card->controls_count += kcontrol->count;
 	kcontrol->id.numid = card->last_numid + 1;
 	card->last_numid += kcontrol->count;
+	count = kcontrol->count;
 	up_write(&card->controls_rwsem);
-	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
+	for (idx = 0; idx < count; idx++, id.index++, id.numid++)
 		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
 	return 0;
 
  error:
 	snd_ctl_free_one(kcontrol);
@@ -386,10 +388,11 @@ EXPORT_SYMBOL(snd_ctl_add);
  */
 int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol,
 		    bool add_on_replace)
 {
 	struct snd_ctl_elem_id id;
+	unsigned int count;
 	unsigned int idx;
 	struct snd_kcontrol *old;
 	int ret;
 
 	if (!kcontrol)
@@ -421,12 +424,13 @@ int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol,
 	}
 	list_add_tail(&kcontrol->list, &card->controls);
 	card->controls_count += kcontrol->count;
 	kcontrol->id.numid = card->last_numid + 1;
 	card->last_numid += kcontrol->count;
+	count = kcontrol->count;
 	up_write(&card->controls_rwsem);
-	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
+	for (idx = 0; idx < count; idx++, id.index++, id.numid++)
 		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
 	return 0;
 
 error:
 	snd_ctl_free_one(kcontrol);
@@ -895,13 +899,13 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
 		} else {
 			snd_ctl_build_ioff(&control->id, kctl, index_offset);
 			result = kctl->put(kctl, control);
 		}
 		if (result > 0) {
+			struct snd_ctl_elem_id id = control->id;
 			up_read(&card->controls_rwsem);
-			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
-				       &control->id);
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id);
 			return 0;
 		}
 	}
 	up_read(&card->controls_rwsem);
 	return result;
@@ -1331,12 +1335,13 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
 			err = -EPERM;
 			goto __kctl_end;
 		}
 		err = kctl->tlv.c(kctl, op_flag, tlv.length, _tlv->tlv);
 		if (err > 0) {
+			struct snd_ctl_elem_id id = kctl->id;
 			up_read(&card->controls_rwsem);
-			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &kctl->id);
+			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &id);
 			return 0;
 		}
 	} else {
 		if (op_flag) {
 			err = -ENXIO;

Vulnerable code: 	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
				       &control->id);
			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &kctl->id);
Fixed code: 	unsigned int count;
	count = kcontrol->count;
	for (idx = 0; idx < count; idx++, id.index++, id.numid++)
	unsigned int count;
	count = kcontrol->count;
	for (idx = 0; idx < count; idx++, id.index++, id.numid++)
			struct snd_ctl_elem_id id = control->id;
			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id);
			struct snd_ctl_elem_id id = kctl->id;
			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &id);