Vulnerability: 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.
Vulnerability ID: CVE-2014-4653
CWE: CWE-416
Severity score: 6.4
Project: https://github.com/torvalds/linux
Published: 2014-07-03
File: sound/core/control.c
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>
Commit author: Lars-Peter Clausen

Vulnerable version (before fix):
 int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) { 	struct snd_ctl_elem_id id; 	unsigned int idx; 	int err = -EINVAL;  	if (! kcontrol) int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) 	card->controls_count += kcontrol->count; 	kcontrol->id.numid = card->last_numid + 1; 	card->last_numid += kcontrol->count; 	up_write(&card->controls_rwsem); 	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) 		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); 	return 0;  int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, 		    bool add_on_replace) { 	struct snd_ctl_elem_id id; 	unsigned int idx; 	struct snd_kcontrol *old; 	int ret; int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, 	card->controls_count += kcontrol->count; 	kcontrol->id.numid = card->last_numid + 1; 	card->last_numid += kcontrol->count; 	up_write(&card->controls_rwsem); 	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) 		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); 	return 0;  static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, 			result = kctl->put(kctl, control); 		} 		if (result > 0) { 			up_read(&card->controls_rwsem); 			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, 				       &control->id); 			return 0; 		} 	} static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, 		} 		err = kctl->tlv.c(kctl, op_flag, tlv.length, _tlv->tlv); 		if (err > 0) { 			up_read(&card->controls_rwsem); 			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &kctl->id); 			return 0; 		} 	} else {

Fixed version (after fix):
 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) int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) 	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 < count; idx++, id.index++, id.numid++) 		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); 	return 0;  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; int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, 	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 < count; idx++, id.index++, id.numid++) 		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); 	return 0;  static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, 			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, &id); 			return 0; 		} 	} static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, 		} 		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, &id); 			return 0; 		} 	} else {

Patch:
patch: @@ -330,6 +330,7 @@ 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)
@@ -358,8 +359,9 @@ int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
 	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;
 
@@ -388,6 +390,7 @@ 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;
@@ -423,8 +426,9 @@ int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol,
 	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;
 
@@ -897,9 +901,9 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
 			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;
 		}
 	}
@@ -1333,8 +1337,9 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
 		}
 		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 {
path: sound/core/control.c