Commit message: ALSA: seq: Fix use-after-free at creating a port

There is a potential race window opened at creating and deleting a
port via ioctl, as spotted by fuzzing.  snd_seq_create_port() creates
a port object and returns its pointer, but it doesn't take the
refcount, thus it can be deleted immediately by another thread.
Meanwhile, snd_seq_ioctl_create_port() still calls the function
snd_seq_system_client_ev_port_start() with the created port object
that is being deleted, and this triggers use-after-free like:

 BUG: KASAN: use-after-free in snd_seq_ioctl_create_port+0x504/0x630 [snd_seq] at addr ffff8801f2241cb1
 =============================================================================
 BUG kmalloc-512 (Tainted: G    B          ): kasan: bad access detected
 -----------------------------------------------------------------------------
 INFO: Allocated in snd_seq_create_port+0x94/0x9b0 [snd_seq] age=1 cpu=3 pid=4511
 	___slab_alloc+0x425/0x460
 	__slab_alloc+0x20/0x40
  	kmem_cache_alloc_trace+0x150/0x190
	snd_seq_create_port+0x94/0x9b0 [snd_seq]
	snd_seq_ioctl_create_port+0xd1/0x630 [snd_seq]
 	snd_seq_do_ioctl+0x11c/0x190 [snd_seq]
 	snd_seq_ioctl+0x40/0x80 [snd_seq]
 	do_vfs_ioctl+0x54b/0xda0
 	SyS_ioctl+0x79/0x90
 	entry_SYSCALL_64_fastpath+0x16/0x75
 INFO: Freed in port_delete+0x136/0x1a0 [snd_seq] age=1 cpu=2 pid=4717
 	__slab_free+0x204/0x310
 	kfree+0x15f/0x180
 	port_delete+0x136/0x1a0 [snd_seq]
 	snd_seq_delete_port+0x235/0x350 [snd_seq]
 	snd_seq_ioctl_delete_port+0xc8/0x180 [snd_seq]
 	snd_seq_do_ioctl+0x11c/0x190 [snd_seq]
 	snd_seq_ioctl+0x40/0x80 [snd_seq]
 	do_vfs_ioctl+0x54b/0xda0
 	SyS_ioctl+0x79/0x90
 	entry_SYSCALL_64_fastpath+0x16/0x75
 Call Trace:
  [<ffffffff81b03781>] dump_stack+0x63/0x82
  [<ffffffff81531b3b>] print_trailer+0xfb/0x160
  [<ffffffff81536db4>] object_err+0x34/0x40
  [<ffffffff815392d3>] kasan_report.part.2+0x223/0x520
  [<ffffffffa07aadf4>] ? snd_seq_ioctl_create_port+0x504/0x630 [snd_seq]
  [<ffffffff815395fe>] __asan_report_load1_noabort+0x2e/0x30
  [<ffffffffa07aadf4>] snd_seq_ioctl_create_port+0x504/0x630 [snd_seq]
  [<ffffffffa07aa8f0>] ? snd_seq_ioctl_delete_port+0x180/0x180 [snd_seq]
  [<ffffffff8136be50>] ? taskstats_exit+0xbc0/0xbc0
  [<ffffffffa07abc5c>] snd_seq_do_ioctl+0x11c/0x190 [snd_seq]
  [<ffffffffa07abd10>] snd_seq_ioctl+0x40/0x80 [snd_seq]
  [<ffffffff8136d433>] ? acct_account_cputime+0x63/0x80
  [<ffffffff815b515b>] do_vfs_ioctl+0x54b/0xda0
  .....

We may fix this in a few different ways, and in this patch, it's fixed
simply by taking the refcount properly at snd_seq_create_port() and
letting the caller unref the object after use.  Also, there is another
potential use-after-free by sprintf() call in snd_seq_create_port(),
and this is moved inside the lock.

This fix covers CVE-2017-15265.

Reported-and-tested-by: Michael23 Yu <ycqzsy@gmail.com>
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
CVE: CVE-2017-15265
CWE: CWE-416
Vulnerability description: Race condition in the ALSA subsystem in the Linux kernel before 4.13.8 allows local users to cause a denial of service (use-after-free) or possibly have unspecified other impact via crafted /dev/snd/seq ioctl calls, related to sound/core/seq/seq_clientmgr.c and sound/core/seq/seq_ports.c.

Project: linux
File: seq_clientmgr.c

static int snd_seq_ioctl_create_queue(struct snd_seq_client *client, void *arg)
{
	struct snd_seq_queue_info *info = arg;
	struct snd_seq_queue *q;

	q = snd_seq_queue_alloc(client->number, info->locked, info->flags);
	if (IS_ERR(q))
		return PTR_ERR(q);

	info->queue = q->queue;
	info->locked = q->locked;
	info->owner = q->owner;

	/* set queue name */
	if (!info->name[0])
		snprintf(info->name, sizeof(info->name), "Queue-%d", q->queue);
	strlcpy(q->name, info->name, sizeof(q->name));
	snd_use_lock_free(&q->use_lock);

	return 0;
}

Project: linux
File: seq_clientmgr.c

static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg)
{
	struct snd_seq_query_subs *subs = arg;
	int result = -ENXIO;
	struct snd_seq_client *cptr = NULL;
	struct snd_seq_client_port *port = NULL;
	struct snd_seq_port_subs_info *group;
	struct list_head *p;
	int i;

	if ((cptr = snd_seq_client_use_ptr(subs->root.client)) == NULL)
		goto __end;
	if ((port = snd_seq_port_use_ptr(cptr, subs->root.port)) == NULL)
		goto __end;

	switch (subs->type) {
	case SNDRV_SEQ_QUERY_SUBS_READ:
		group = &port->c_src;
		break;
	case SNDRV_SEQ_QUERY_SUBS_WRITE:
		group = &port->c_dest;
		break;
	default:
		goto __end;
	}

	down_read(&group->list_mutex);
	/* search for the subscriber */
	subs->num_subs = group->count;
	i = 0;
	result = -ENOENT;
	list_for_each(p, &group->list_head) {
		if (i++ == subs->index) {
			/* found! */
			struct snd_seq_subscribers *s;
			if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) {
				s = list_entry(p, struct snd_seq_subscribers, src_list);
				subs->addr = s->info.dest;
			} else {
				s = list_entry(p, struct snd_seq_subscribers, dest_list);
				subs->addr = s->info.sender;
			}
			subs->flags = s->info.flags;
			subs->queue = s->info.queue;
			result = 0;
			break;
		}
	}
	up_read(&group->list_mutex);

      __end:
   	if (port)
		snd_seq_port_unlock(port);
	if (cptr)
		snd_seq_client_unlock(cptr);

	return result;
}

Project: linux
File: seq_clientmgr.c

static int snd_seq_ioctl_running_mode(struct snd_seq_client *client, void  *arg)
{
	struct snd_seq_running_info *info = arg;
	struct snd_seq_client *cptr;
	int err = 0;

	/* requested client number */
	cptr = snd_seq_client_use_ptr(info->client);
	if (cptr == NULL)
		return -ENOENT;		/* don't change !!! */

#ifdef SNDRV_BIG_ENDIAN
	if (!info->big_endian) {
		err = -EINVAL;
		goto __err;
	}
#else
	if (info->big_endian) {
		err = -EINVAL;
		goto __err;
	}

#endif
	if (info->cpu_mode > sizeof(long)) {
		err = -EINVAL;
		goto __err;
	}
	cptr->convert32 = (info->cpu_mode < sizeof(long));
 __err:
	snd_seq_client_unlock(cptr);
	return err;
}

Project: linux
File: seq_clientmgr.c

static int snd_seq_ioctl_set_queue_info(struct snd_seq_client *client,
					void *arg)
{
	struct snd_seq_queue_info *info = arg;
	struct snd_seq_queue *q;

	if (info->owner != client->number)
		return -EINVAL;

	/* change owner/locked permission */
	if (snd_seq_queue_check_access(info->queue, client->number)) {
		if (snd_seq_queue_set_owner(info->queue, client->number, info->locked) < 0)
			return -EPERM;
		if (info->locked)
			snd_seq_queue_use(info->queue, client->number, 1);
	} else {
		return -EPERM;
	}	

	q = queueptr(info->queue);
	if (! q)
		return -EINVAL;
	if (q->owner != client->number) {
		queuefree(q);
		return -EPERM;
	}
	strlcpy(q->name, info->name, sizeof(q->name));
	queuefree(q);

	return 0;
}

Project: linux
File: seq_clientmgr.c

static int snd_seq_ioctl_system_info(struct snd_seq_client *client, void *arg)
{
	struct snd_seq_system_info *info = arg;

	memset(info, 0, sizeof(*info));
	/* fill the info fields */
	info->queues = SNDRV_SEQ_MAX_QUEUES;
	info->clients = SNDRV_SEQ_MAX_CLIENTS;
	info->ports = SNDRV_SEQ_MAX_PORTS;
	info->channels = 256;	/* fixed limit */
	info->cur_clients = client_usage.cur;
	info->cur_queues = snd_seq_queue_get_cur_queues();

	return 0;
}

Project: linux
File: seq_ports.c

static int port_delete(struct snd_seq_client *client,
		       struct snd_seq_client_port *port)
{
	/* set closing flag and wait for all port access are gone */
	port->closing = 1;
	snd_use_lock_sync(&port->use_lock); 

	/* clear subscribers info */
	clear_subscriber_list(client, port, &port->c_src, true);
	clear_subscriber_list(client, port, &port->c_dest, false);

	if (port->private_free)
		port->private_free(port->private_data);

	snd_BUG_ON(port->c_src.count != 0);
	snd_BUG_ON(port->c_dest.count != 0);

	kfree(port);
	return 0;
}

Project: linux
File: seq_ports.c

struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client,
						       struct snd_seq_port_info *pinfo)
{
	int num;
	struct snd_seq_client_port *port, *found;

	num = pinfo->addr.port;
	found = NULL;
	read_lock(&client->ports_lock);
	list_for_each_entry(port, &client->ports_list_head, list) {
		if (port->addr.port < num)
			continue;
		if (port->addr.port == num) {
			found = port;
			break;
		}
		if (found == NULL || port->addr.port < found->addr.port)
			found = port;
	}
	if (found) {
		if (found->closing)
			found = NULL;
		else
			snd_use_lock_use(&found->use_lock);
	}
	read_unlock(&client->ports_lock);
	return found;
}

Project: linux
File: seq_ports.c

static int unsubscribe_port(struct snd_seq_client *client,
			    struct snd_seq_client_port *port,
			    struct snd_seq_port_subs_info *grp,
			    struct snd_seq_port_subscribe *info,
			    int send_ack)
{
	int err = 0;

	if (! grp->count)
		return -EINVAL;
	grp->count--;
	if (grp->close && grp->count == 0)
		err = grp->close(port->private_data, info);
	if (send_ack && client->type == USER_CLIENT)
		snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
						   info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
	module_put(port->owner);
	return err;
}