Commit message: ALSA: seq: Fix racy pool initializations

ALSA sequencer core initializes the event pool on demand by invoking
snd_seq_pool_init() when the first write happens and the pool is
empty.  Meanwhile user can reset the pool size manually via ioctl
concurrently, and this may lead to UAF or out-of-bound accesses since
the function tries to vmalloc / vfree the buffer.

A simple fix is to just wrap the snd_seq_pool_init() call with the
recently introduced client->ioctl_mutex; as the calls for
snd_seq_pool_init() from other side are always protected with this
mutex, we can avoid the race.

Reported-by: 范龙飞 <long7573@126.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
CVE: CVE-2018-7566
CWE: CWE-119
Vulnerability description: The Linux kernel 4.15 has a Buffer Overflow via an SNDRV_SEQ_IOCTL_SET_CLIENT_POOL ioctl write operation to /dev/snd/seq by a local user.

Project: linux
File: seq_clientmgr.c

static struct snd_seq_client *seq_create_client1(int client_index, int poolsize)
{
	unsigned long flags;
	int c;
	struct snd_seq_client *client;

	/* init client data */
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (client == NULL)
		return NULL;
	client->pool = snd_seq_pool_new(poolsize);
	if (client->pool == NULL) {
		kfree(client);
		return NULL;
	}
	client->type = NO_CLIENT;
	snd_use_lock_init(&client->use_lock);
	rwlock_init(&client->ports_lock);
	mutex_init(&client->ports_mutex);
	INIT_LIST_HEAD(&client->ports_list_head);
	mutex_init(&client->ioctl_mutex);

	/* find free slot in the client table */
	spin_lock_irqsave(&clients_lock, flags);
	if (client_index < 0) {
		for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN;
		     c < SNDRV_SEQ_MAX_CLIENTS;
		     c++) {
			if (clienttab[c] || clienttablock[c])
				continue;
			clienttab[client->number = c] = client;
			spin_unlock_irqrestore(&clients_lock, flags);
			return client;
		}
	} else {
		if (clienttab[client_index] == NULL && !clienttablock[client_index]) {
			clienttab[client->number = client_index] = client;
			spin_unlock_irqrestore(&clients_lock, flags);
			return client;
		}
	}
	spin_unlock_irqrestore(&clients_lock, flags);
	snd_seq_pool_delete(&client->pool);
	kfree(client);
	return NULL;	/* no free slot found or busy, return failure code */
}

Project: linux
File: seq_clientmgr.c

static long snd_seq_ioctl(struct file *file, unsigned int cmd,
			  unsigned long arg)
{
	struct snd_seq_client *client = file->private_data;
	/* To use kernel stack for ioctl data. */
	union {
		int pversion;
		int client_id;
		struct snd_seq_system_info	system_info;
		struct snd_seq_running_info	running_info;
		struct snd_seq_client_info	client_info;
		struct snd_seq_port_info	port_info;
		struct snd_seq_port_subscribe	port_subscribe;
		struct snd_seq_queue_info	queue_info;
		struct snd_seq_queue_status	queue_status;
		struct snd_seq_queue_tempo	tempo;
		struct snd_seq_queue_timer	queue_timer;
		struct snd_seq_queue_client	queue_client;
		struct snd_seq_client_pool	client_pool;
		struct snd_seq_remove_events	remove_events;
		struct snd_seq_query_subs	query_subs;
	} buf;
	const struct ioctl_handler *handler;
	unsigned long size;
	int err;

	if (snd_BUG_ON(!client))
		return -ENXIO;

	for (handler = ioctl_handlers; handler->cmd > 0; ++handler) {
		if (handler->cmd == cmd)
			break;
	}
	if (handler->cmd == 0)
		return -ENOTTY;

	memset(&buf, 0, sizeof(buf));

	/*
	 * All of ioctl commands for ALSA sequencer get an argument of size
	 * within 13 bits. We can safely pick up the size from the command.
	 */
	size = _IOC_SIZE(handler->cmd);
	if (handler->cmd & IOC_IN) {
		if (copy_from_user(&buf, (const void __user *)arg, size))
			return -EFAULT;
	}

	mutex_lock(&client->ioctl_mutex);
	err = handler->func(client, &buf);
	mutex_unlock(&client->ioctl_mutex);
	if (err >= 0) {
		/* Some commands includes a bug in 'dir' field. */
		if (handler->cmd == SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT ||
		    handler->cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_POOL ||
		    (handler->cmd & IOC_OUT))
			if (copy_to_user((void __user *)arg, &buf, size))
				return -EFAULT;
	}

	return err;
}

Project: linux
File: seq_clientmgr.c

static __poll_t snd_seq_poll(struct file *file, poll_table * wait)
{
	struct snd_seq_client *client = file->private_data;
	__poll_t mask = 0;

	/* check client structures are in place */
	if (snd_BUG_ON(!client))
		return -ENXIO;

	if ((snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT) &&
	    client->data.user.fifo) {

		/* check if data is available in the outqueue */
		if (snd_seq_fifo_poll_wait(client->data.user.fifo, file, wait))
			mask |= EPOLLIN | EPOLLRDNORM;
	}

	if (snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT) {

		/* check if data is available in the pool */
		if (!snd_seq_write_pool_allocated(client) ||
		    snd_seq_pool_poll_wait(client->pool, file, wait))
			mask |= EPOLLOUT | EPOLLWRNORM;
	}

	return mask;
}

Project: linux
File: seq_clientmgr.c

static int snd_seq_ioctl_get_queue_timer(struct snd_seq_client *client,
					 void *arg)
{
	struct snd_seq_queue_timer *timer = arg;
	struct snd_seq_queue *queue;
	struct snd_seq_timer *tmr;

	queue = queueptr(timer->queue);
	if (queue == NULL)
		return -EINVAL;

	if (mutex_lock_interruptible(&queue->timer_mutex)) {
		queuefree(queue);
		return -ERESTARTSYS;
	}
	tmr = queue->timer;
	memset(timer, 0, sizeof(*timer));
	timer->queue = queue->queue;

	timer->type = tmr->type;
	if (tmr->type == SNDRV_SEQ_TIMER_ALSA) {
		timer->u.alsa.id = tmr->alsa_id;
		timer->u.alsa.resolution = tmr->preferred_resolution;
	}
	mutex_unlock(&queue->timer_mutex);
	queuefree(queue);
	
	return 0;
}

Project: linux
File: seq_clientmgr.c

static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_event *event,
				 int atomic, int hop)
{
	int result;

	hop++;
	if (hop >= SNDRV_SEQ_MAX_HOPS) {
		pr_debug("ALSA: seq: too long delivery path (%d:%d->%d:%d)\n",
			   event->source.client, event->source.port,
			   event->dest.client, event->dest.port);
		return -EMLINK;
	}

	if (snd_seq_ev_is_variable(event) &&
	    snd_BUG_ON(atomic && (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR)))
		return -EINVAL;

	if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS ||
	    event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS)
		result = deliver_to_subscribers(client, event, atomic, hop);
#ifdef SUPPORT_BROADCAST
	else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST ||
		 event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST)
		result = broadcast_event(client, event, atomic, hop);
	else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS)
		result = multicast_event(client, event, atomic, hop);
	else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST)
		result = port_broadcast_event(client, event, atomic, hop);
#endif
	else
		result = snd_seq_deliver_single_event(client, event, 0, atomic, hop);

	return result;
}

Project: linux
File: seq_clientmgr.c

static ssize_t snd_seq_write(struct file *file, const char __user *buf,
			     size_t count, loff_t *offset)
{
	struct snd_seq_client *client = file->private_data;
	int written = 0, len;
	int err;
	struct snd_seq_event event;

	if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
		return -ENXIO;

	/* check client structures are in place */
	if (snd_BUG_ON(!client))
		return -ENXIO;
		
	if (!client->accept_output || client->pool == NULL)
		return -ENXIO;

	/* allocate the pool now if the pool is not allocated yet */ 
	if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) {
		mutex_lock(&client->ioctl_mutex);
		err = snd_seq_pool_init(client->pool);
		mutex_unlock(&client->ioctl_mutex);
		if (err < 0)
			return -ENOMEM;
	}

	/* only process whole events */
	err = -EINVAL;
	while (count >= sizeof(struct snd_seq_event)) {
		/* Read in the event header from the user */
		len = sizeof(event);
		if (copy_from_user(&event, buf, len)) {
			err = -EFAULT;
			break;
		}
		event.source.client = client->number;	/* fill in client number */
		/* Check for extension data length */
		if (check_event_type_and_length(&event)) {
			err = -EINVAL;
			break;
		}

		/* check for special events */
		if (event.type == SNDRV_SEQ_EVENT_NONE)
			goto __skip_event;
		else if (snd_seq_ev_is_reserved(&event)) {
			err = -EINVAL;
			break;
		}

		if (snd_seq_ev_is_variable(&event)) {
			int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK;
			if ((size_t)(extlen + len) > count) {
				/* back out, will get an error this time or next */
				err = -EINVAL;
				break;
			}
			/* set user space pointer */
			event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
			event.data.ext.ptr = (char __force *)buf
						+ sizeof(struct snd_seq_event);
			len += extlen; /* increment data length */
		} else {
#ifdef CONFIG_COMPAT
			if (client->convert32 && snd_seq_ev_is_varusr(&event)) {
				void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]);
				event.data.ext.ptr = ptr;
			}
#endif
		}

		/* ok, enqueue it */
		err = snd_seq_client_enqueue_event(client, &event, file,
						   !(file->f_flags & O_NONBLOCK),
						   0, 0);
		if (err < 0)
			break;

	__skip_event:
		/* Update pointers and counts */
		count -= len;
		buf += len;
		written += len;
	}

	return written ? written : err;
}

Project: linux
File: seq_clientmgr.c

static int deliver_to_subscribers(struct snd_seq_client *client,
				  struct snd_seq_event *event,
				  int atomic, int hop)
{
	struct snd_seq_subscribers *subs;
	int err, result = 0, num_ev = 0;
	struct snd_seq_event event_saved;
	struct snd_seq_client_port *src_port;
	struct snd_seq_port_subs_info *grp;

	src_port = snd_seq_port_use_ptr(client, event->source.port);
	if (src_port == NULL)
		return -EINVAL; /* invalid source port */
	/* save original event record */
	event_saved = *event;
	grp = &src_port->c_src;
	
	/* lock list */
	if (atomic)
		read_lock(&grp->list_lock);
	else
		down_read_nested(&grp->list_mutex, hop);
	list_for_each_entry(subs, &grp->list_head, src_list) {
		/* both ports ready? */
		if (atomic_read(&subs->ref_count) != 2)
			continue;
		event->dest = subs->info.dest;
		if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
			/* convert time according to flag with subscription */
			update_timestamp_of_queue(event, subs->info.queue,
						  subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL);
		err = snd_seq_deliver_single_event(client, event,
						   0, atomic, hop);
		if (err < 0) {
			/* save first error that occurs and continue */
			if (!result)
				result = err;
			continue;
		}
		num_ev++;
		/* restore original event record */
		*event = event_saved;
	}
	if (atomic)
		read_unlock(&grp->list_lock);
	else
		up_read(&grp->list_mutex);
	*event = event_saved; /* restore */
	snd_seq_port_unlock(src_port);
	return (result < 0) ? result : num_ev;
}