ALSA: seq: Fix race at creating a queue

When a sequencer queue is created in snd_seq_queue_alloc(),it adds the
new queue element to the public list before referencing it.  Thus the
queue might be deleted before the call of snd_seq_queue_use(), and it
results in the use-after-free error, as spotted by syzkaller.

The fix is to reference the queue object at the right time.

Reported-by: Dmitry Vyukov <dvyukov@google.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2017-02-08 12:35:39 +01:00
parent f3d83317a6
commit 4842e98f26
1 changed files with 20 additions and 13 deletions

View File

@ -181,6 +181,8 @@ void __exit snd_seq_queues_delete(void)
} }
} }
static void queue_use(struct snd_seq_queue *queue, int client, int use);
/* allocate a new queue - /* allocate a new queue -
* return queue index value or negative value for error * return queue index value or negative value for error
*/ */
@ -192,11 +194,11 @@ int snd_seq_queue_alloc(int client, int locked, unsigned int info_flags)
if (q == NULL) if (q == NULL)
return -ENOMEM; return -ENOMEM;
q->info_flags = info_flags; q->info_flags = info_flags;
queue_use(q, client, 1);
if (queue_list_add(q) < 0) { if (queue_list_add(q) < 0) {
queue_delete(q); queue_delete(q);
return -ENOMEM; return -ENOMEM;
} }
snd_seq_queue_use(q->queue, client, 1); /* use this queue */
return q->queue; return q->queue;
} }
@ -502,19 +504,9 @@ int snd_seq_queue_timer_set_tempo(int queueid, int client,
return result; return result;
} }
/* use or unuse this queue */
/* use or unuse this queue - static void queue_use(struct snd_seq_queue *queue, int client, int use)
* if it is the first client, starts the timer.
* if it is not longer used by any clients, stop the timer.
*/
int snd_seq_queue_use(int queueid, int client, int use)
{ {
struct snd_seq_queue *queue;
queue = queueptr(queueid);
if (queue == NULL)
return -EINVAL;
mutex_lock(&queue->timer_mutex);
if (use) { if (use) {
if (!test_and_set_bit(client, queue->clients_bitmap)) if (!test_and_set_bit(client, queue->clients_bitmap))
queue->clients++; queue->clients++;
@ -529,6 +521,21 @@ int snd_seq_queue_use(int queueid, int client, int use)
} else { } else {
snd_seq_timer_close(queue); snd_seq_timer_close(queue);
} }
}
/* use or unuse this queue -
* if it is the first client, starts the timer.
* if it is not longer used by any clients, stop the timer.
*/
int snd_seq_queue_use(int queueid, int client, int use)
{
struct snd_seq_queue *queue;
queue = queueptr(queueid);
if (queue == NULL)
return -EINVAL;
mutex_lock(&queue->timer_mutex);
queue_use(queue, client, use);
mutex_unlock(&queue->timer_mutex); mutex_unlock(&queue->timer_mutex);
queuefree(queue); queuefree(queue);
return 0; return 0;