Commit message: media: fix airspy usb probe error path

Fix a memory leak on probe error of the airspy usb device driver.

The problem is triggered when more than 64 usb devices register with
v4l2 of type VFL_TYPE_SDR or VFL_TYPE_SUBDEV.

The memory leak is caused by the probe function of the airspy driver
mishandeling errors and not freeing the corresponding control structures
when an error occours registering the device to v4l2 core.

A badusb device can emulate 64 of these devices, and then through
continual emulated connect/disconnect of the 65th device, cause the
kernel to run out of RAM and crash the kernel, thus causing a local DOS
vulnerability.

Fixes CVE-2016-5400

Signed-off-by: James Patrick-Evans <james@jmp-e.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Cc: stable@vger.kernel.org # 3.17+
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
CVE: CVE-2016-5400
CWE: CWE-119
Vulnerability description: Memory leak in the airspy_probe function in drivers/media/usb/airspy/airspy.c in the airspy USB driver in the Linux kernel before 4.7 allows local users to cause a denial of service (memory consumption) via a crafted USB device that emulates many VFL_TYPE_SDR or VFL_TYPE_SUBDEV devices and performs many connect and disconnect operations.

Project: linux
File: airspy.c

static int airspy_alloc_stream_bufs(struct airspy *s)
{
	s->buf_num = 0;
	s->buf_size = BULK_BUFFER_SIZE;

	dev_dbg(s->dev, "all in all I will use %u bytes for streaming\n",
			MAX_BULK_BUFS * BULK_BUFFER_SIZE);

	for (s->buf_num = 0; s->buf_num < MAX_BULK_BUFS; s->buf_num++) {
		s->buf_list[s->buf_num] = usb_alloc_coherent(s->udev,
				BULK_BUFFER_SIZE, GFP_ATOMIC,
				&s->dma_addr[s->buf_num]);
		if (!s->buf_list[s->buf_num]) {
			dev_dbg(s->dev, "alloc buf=%d failed\n", s->buf_num);
			airspy_free_stream_bufs(s);
			return -ENOMEM;
		}

		dev_dbg(s->dev, "alloc buf=%d %p (dma %llu)\n", s->buf_num,
				s->buf_list[s->buf_num],
				(long long)s->dma_addr[s->buf_num]);
		set_bit(USB_STATE_URB_BUF, &s->flags);
	}

	return 0;
}

Project: linux
File: airspy.c

static int airspy_alloc_urbs(struct airspy *s)
{
	int i, j;

	/* allocate the URBs */
	for (i = 0; i < MAX_BULK_BUFS; i++) {
		dev_dbg(s->dev, "alloc urb=%d\n", i);
		s->urb_list[i] = usb_alloc_urb(0, GFP_ATOMIC);
		if (!s->urb_list[i]) {
			dev_dbg(s->dev, "failed\n");
			for (j = 0; j < i; j++)
				usb_free_urb(s->urb_list[j]);
			return -ENOMEM;
		}
		usb_fill_bulk_urb(s->urb_list[i],
				s->udev,
				usb_rcvbulkpipe(s->udev, 0x81),
				s->buf_list[i],
				BULK_BUFFER_SIZE,
				airspy_urb_complete, s);

		s->urb_list[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
		s->urb_list[i]->transfer_dma = s->dma_addr[i];
		s->urbs_initialized++;
	}

	return 0;
}

Project: linux
File: airspy.c

static void airspy_buf_queue(struct vb2_buffer *vb)
{
	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
	struct airspy *s = vb2_get_drv_priv(vb->vb2_queue);
	struct airspy_frame_buf *buf =
			container_of(vbuf, struct airspy_frame_buf, vb);
	unsigned long flags;

	/* Check the device has not disconnected between prep and queuing */
	if (unlikely(!s->udev)) {
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
		return;
	}

	spin_lock_irqsave(&s->queued_bufs_lock, flags);
	list_add_tail(&buf->list, &s->queued_bufs);
	spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
}

Project: linux
File: airspy.c

static void airspy_cleanup_queued_bufs(struct airspy *s)
{
	unsigned long flags;

	dev_dbg(s->dev, "\n");

	spin_lock_irqsave(&s->queued_bufs_lock, flags);
	while (!list_empty(&s->queued_bufs)) {
		struct airspy_frame_buf *buf;

		buf = list_entry(s->queued_bufs.next,
				struct airspy_frame_buf, list);
		list_del(&buf->list);
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
	}
	spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
}

Project: linux
File: airspy.c

static void airspy_disconnect(struct usb_interface *intf)
{
	struct v4l2_device *v = usb_get_intfdata(intf);
	struct airspy *s = container_of(v, struct airspy, v4l2_dev);

	dev_dbg(s->dev, "\n");

	mutex_lock(&s->vb_queue_lock);
	mutex_lock(&s->v4l2_lock);
	/* No need to keep the urbs around after disconnection */
	s->udev = NULL;
	v4l2_device_disconnect(&s->v4l2_dev);
	video_unregister_device(&s->vdev);
	mutex_unlock(&s->v4l2_lock);
	mutex_unlock(&s->vb_queue_lock);

	v4l2_device_put(&s->v4l2_dev);
}

Project: linux
File: airspy.c

static int airspy_enum_fmt_sdr_cap(struct file *file, void *priv,
		struct v4l2_fmtdesc *f)
{
	if (f->index >= NUM_FORMATS)
		return -EINVAL;

	strlcpy(f->description, formats[f->index].name, sizeof(f->description));
	f->pixelformat = formats[f->index].pixelformat;

	return 0;
}

Project: linux
File: airspy.c

static int airspy_enum_freq_bands(struct file *file, void *priv,
		struct v4l2_frequency_band *band)
{
	int ret;

	if (band->tuner == 0) {
		if (band->index >= ARRAY_SIZE(bands)) {
			ret = -EINVAL;
		} else {
			*band = bands[band->index];
			ret = 0;
		}
	} else if (band->tuner == 1) {
		if (band->index >= ARRAY_SIZE(bands_rf)) {
			ret = -EINVAL;
		} else {
			*band = bands_rf[band->index];
			ret = 0;
		}
	} else {
		ret = -EINVAL;
	}

	return ret;
}

Project: linux
File: airspy.c

static int airspy_free_stream_bufs(struct airspy *s)
{
	if (test_bit(USB_STATE_URB_BUF, &s->flags)) {
		while (s->buf_num) {
			s->buf_num--;
			dev_dbg(s->dev, "free buf=%d\n", s->buf_num);
			usb_free_coherent(s->udev, s->buf_size,
					  s->buf_list[s->buf_num],
					  s->dma_addr[s->buf_num]);
		}
	}
	clear_bit(USB_STATE_URB_BUF, &s->flags);

	return 0;
}

Project: linux
File: airspy.c

static int airspy_free_urbs(struct airspy *s)
{
	int i;

	airspy_kill_urbs(s);

	for (i = s->urbs_initialized - 1; i >= 0; i--) {
		if (s->urb_list[i]) {
			dev_dbg(s->dev, "free urb=%d\n", i);
			/* free the URBs */
			usb_free_urb(s->urb_list[i]);
		}
	}
	s->urbs_initialized = 0;

	return 0;
}

Project: linux
File: airspy.c

static int airspy_g_fmt_sdr_cap(struct file *file, void *priv,
		struct v4l2_format *f)
{
	struct airspy *s = video_drvdata(file);

	f->fmt.sdr.pixelformat = s->pixelformat;
	f->fmt.sdr.buffersize = s->buffersize;
	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));

	return 0;
}

Project: linux
File: airspy.c

static int airspy_g_frequency(struct file *file, void *priv,
		struct v4l2_frequency *f)
{
	struct airspy *s = video_drvdata(file);
	int ret;

	if (f->tuner == 0) {
		f->type = V4L2_TUNER_ADC;
		f->frequency = s->f_adc;
		dev_dbg(s->dev, "ADC frequency=%u Hz\n", s->f_adc);
		ret = 0;
	} else if (f->tuner == 1) {
		f->type = V4L2_TUNER_RF;
		f->frequency = s->f_rf;
		dev_dbg(s->dev, "RF frequency=%u Hz\n", s->f_rf);
		ret = 0;
	} else {
		ret = -EINVAL;
	}

	return ret;
}

Project: linux
File: airspy.c

static int airspy_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
{
	int ret;

	if (v->index == 0) {
		strlcpy(v->name, "AirSpy ADC", sizeof(v->name));
		v->type = V4L2_TUNER_ADC;
		v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
		v->rangelow  = bands[0].rangelow;
		v->rangehigh = bands[0].rangehigh;
		ret = 0;
	} else if (v->index == 1) {
		strlcpy(v->name, "AirSpy RF", sizeof(v->name));
		v->type = V4L2_TUNER_RF;
		v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
		v->rangelow  = bands_rf[0].rangelow;
		v->rangehigh = bands_rf[0].rangehigh;
		ret = 0;
	} else {
		ret = -EINVAL;
	}

	return ret;
}

Project: linux
File: airspy.c

static struct airspy_frame_buf *airspy_get_next_fill_buf(struct airspy *s)
{
	unsigned long flags;
	struct airspy_frame_buf *buf = NULL;

	spin_lock_irqsave(&s->queued_bufs_lock, flags);
	if (list_empty(&s->queued_bufs))
		goto leave;

	buf = list_entry(s->queued_bufs.next,
			struct airspy_frame_buf, list);
	list_del(&buf->list);
leave:
	spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
	return buf;
}

Project: linux
File: airspy.c

static int airspy_kill_urbs(struct airspy *s)
{
	int i;

	for (i = s->urbs_submitted - 1; i >= 0; i--) {
		dev_dbg(s->dev, "kill urb=%d\n", i);
		/* stop the URB */
		usb_kill_urb(s->urb_list[i]);
	}
	s->urbs_submitted = 0;

	return 0;
}

Project: linux
File: airspy.c

static int airspy_querycap(struct file *file, void *fh,
		struct v4l2_capability *cap)
{
	struct airspy *s = video_drvdata(file);

	strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
	strlcpy(cap->card, s->vdev.name, sizeof(cap->card));
	usb_make_path(s->udev, cap->bus_info, sizeof(cap->bus_info));
	cap->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_STREAMING |
			V4L2_CAP_READWRITE | V4L2_CAP_TUNER;
	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;

	return 0;
}

Project: linux
File: airspy.c

static int airspy_queue_setup(struct vb2_queue *vq,
		unsigned int *nbuffers,
		unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[])
{
	struct airspy *s = vb2_get_drv_priv(vq);

	dev_dbg(s->dev, "nbuffers=%d\n", *nbuffers);

	/* Need at least 8 buffers */
	if (vq->num_buffers + *nbuffers < 8)
		*nbuffers = 8 - vq->num_buffers;
	*nplanes = 1;
	sizes[0] = PAGE_ALIGN(s->buffersize);

	dev_dbg(s->dev, "nbuffers=%d sizes[0]=%d\n", *nbuffers, sizes[0]);
	return 0;
}

Project: linux
File: airspy.c

static int airspy_s_ctrl(struct v4l2_ctrl *ctrl)
{
	struct airspy *s = container_of(ctrl->handler, struct airspy, hdl);
	int ret;

	switch (ctrl->id) {
	case  V4L2_CID_RF_TUNER_LNA_GAIN_AUTO:
	case  V4L2_CID_RF_TUNER_LNA_GAIN:
		ret = airspy_set_lna_gain(s);
		break;
	case  V4L2_CID_RF_TUNER_MIXER_GAIN_AUTO:
	case  V4L2_CID_RF_TUNER_MIXER_GAIN:
		ret = airspy_set_mixer_gain(s);
		break;
	case  V4L2_CID_RF_TUNER_IF_GAIN:
		ret = airspy_set_if_gain(s);
		break;
	default:
		dev_dbg(s->dev, "unknown ctrl: id=%d name=%s\n",
				ctrl->id, ctrl->name);
		ret = -EINVAL;
	}

	return ret;
}

Project: linux
File: airspy.c

static int airspy_s_fmt_sdr_cap(struct file *file, void *priv,
		struct v4l2_format *f)
{
	struct airspy *s = video_drvdata(file);
	struct vb2_queue *q = &s->vb_queue;
	int i;

	if (vb2_is_busy(q))
		return -EBUSY;

	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
	for (i = 0; i < NUM_FORMATS; i++) {
		if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
			s->pixelformat = formats[i].pixelformat;
			s->buffersize = formats[i].buffersize;
			f->fmt.sdr.buffersize = formats[i].buffersize;
			return 0;
		}
	}

	s->pixelformat = formats[0].pixelformat;
	s->buffersize = formats[0].buffersize;
	f->fmt.sdr.pixelformat = formats[0].pixelformat;
	f->fmt.sdr.buffersize = formats[0].buffersize;

	return 0;
}

Project: linux
File: airspy.c

static int airspy_s_tuner(struct file *file, void *priv,
		const struct v4l2_tuner *v)
{
	int ret;

	if (v->index == 0)
		ret = 0;
	else if (v->index == 1)
		ret = 0;
	else
		ret = -EINVAL;

	return ret;
}

Project: linux
File: airspy.c

static int airspy_set_if_gain(struct airspy *s)
{
	int ret;
	u8 u8tmp;

	dev_dbg(s->dev, "val=%d->%d\n", s->if_gain->cur.val, s->if_gain->val);

	ret = airspy_ctrl_msg(s, CMD_SET_VGA_GAIN, 0, s->if_gain->val,
			&u8tmp, 1);
	if (ret)
		dev_dbg(s->dev, "failed=%d\n", ret);

	return ret;
}

Project: linux
File: airspy.c

static int airspy_set_lna_gain(struct airspy *s)
{
	int ret;
	u8 u8tmp;

	dev_dbg(s->dev, "lna auto=%d->%d val=%d->%d\n",
			s->lna_gain_auto->cur.val, s->lna_gain_auto->val,
			s->lna_gain->cur.val, s->lna_gain->val);

	ret = airspy_ctrl_msg(s, CMD_SET_LNA_AGC, 0, s->lna_gain_auto->val,
			&u8tmp, 1);
	if (ret)
		goto err;

	if (s->lna_gain_auto->val == false) {
		ret = airspy_ctrl_msg(s, CMD_SET_LNA_GAIN, 0, s->lna_gain->val,
				&u8tmp, 1);
		if (ret)
			goto err;
	}
err:
	if (ret)
		dev_dbg(s->dev, "failed=%d\n", ret);

	return ret;
}

Project: linux
File: airspy.c

static int airspy_set_mixer_gain(struct airspy *s)
{
	int ret;
	u8 u8tmp;

	dev_dbg(s->dev, "mixer auto=%d->%d val=%d->%d\n",
			s->mixer_gain_auto->cur.val, s->mixer_gain_auto->val,
			s->mixer_gain->cur.val, s->mixer_gain->val);

	ret = airspy_ctrl_msg(s, CMD_SET_MIXER_AGC, 0, s->mixer_gain_auto->val,
			&u8tmp, 1);
	if (ret)
		goto err;

	if (s->mixer_gain_auto->val == false) {
		ret = airspy_ctrl_msg(s, CMD_SET_MIXER_GAIN, 0,
				s->mixer_gain->val, &u8tmp, 1);
		if (ret)
			goto err;
	}
err:
	if (ret)
		dev_dbg(s->dev, "failed=%d\n", ret);

	return ret;
}

Project: linux
File: airspy.c

static int airspy_start_streaming(struct vb2_queue *vq, unsigned int count)
{
	struct airspy *s = vb2_get_drv_priv(vq);
	int ret;

	dev_dbg(s->dev, "\n");

	if (!s->udev)
		return -ENODEV;

	mutex_lock(&s->v4l2_lock);

	s->sequence = 0;

	set_bit(POWER_ON, &s->flags);

	ret = airspy_alloc_stream_bufs(s);
	if (ret)
		goto err_clear_bit;

	ret = airspy_alloc_urbs(s);
	if (ret)
		goto err_free_stream_bufs;

	ret = airspy_submit_urbs(s);
	if (ret)
		goto err_free_urbs;

	/* start hardware streaming */
	ret = airspy_ctrl_msg(s, CMD_RECEIVER_MODE, 1, 0, NULL, 0);
	if (ret)
		goto err_kill_urbs;

	goto exit_mutex_unlock;

err_kill_urbs:
	airspy_kill_urbs(s);
err_free_urbs:
	airspy_free_urbs(s);
err_free_stream_bufs:
	airspy_free_stream_bufs(s);
err_clear_bit:
	clear_bit(POWER_ON, &s->flags);

	/* return all queued buffers to vb2 */
	{
		struct airspy_frame_buf *buf, *tmp;

		list_for_each_entry_safe(buf, tmp, &s->queued_bufs, list) {
			list_del(&buf->list);
			vb2_buffer_done(&buf->vb.vb2_buf,
					VB2_BUF_STATE_QUEUED);
		}
	}

exit_mutex_unlock:
	mutex_unlock(&s->v4l2_lock);

	return ret;
}

Project: linux
File: airspy.c

static void airspy_stop_streaming(struct vb2_queue *vq)
{
	struct airspy *s = vb2_get_drv_priv(vq);

	dev_dbg(s->dev, "\n");

	mutex_lock(&s->v4l2_lock);

	/* stop hardware streaming */
	airspy_ctrl_msg(s, CMD_RECEIVER_MODE, 0, 0, NULL, 0);

	airspy_kill_urbs(s);
	airspy_free_urbs(s);
	airspy_free_stream_bufs(s);

	airspy_cleanup_queued_bufs(s);

	clear_bit(POWER_ON, &s->flags);

	mutex_unlock(&s->v4l2_lock);
}

Project: linux
File: airspy.c

static int airspy_submit_urbs(struct airspy *s)
{
	int i, ret;

	for (i = 0; i < s->urbs_initialized; i++) {
		dev_dbg(s->dev, "submit urb=%d\n", i);
		ret = usb_submit_urb(s->urb_list[i], GFP_ATOMIC);
		if (ret) {
			dev_err(s->dev, "Could not submit URB no. %d - get them all back\n",
					i);
			airspy_kill_urbs(s);
			return ret;
		}
		s->urbs_submitted++;
	}

	return 0;
}

Project: linux
File: airspy.c

static int airspy_try_fmt_sdr_cap(struct file *file, void *priv,
		struct v4l2_format *f)
{
	int i;

	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
	for (i = 0; i < NUM_FORMATS; i++) {
		if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
			f->fmt.sdr.buffersize = formats[i].buffersize;
			return 0;
		}
	}

	f->fmt.sdr.pixelformat = formats[0].pixelformat;
	f->fmt.sdr.buffersize = formats[0].buffersize;

	return 0;
}

Project: linux
File: airspy.c

static void airspy_urb_complete(struct urb *urb)
{
	struct airspy *s = urb->context;
	struct airspy_frame_buf *fbuf;

	dev_dbg_ratelimited(s->dev, "status=%d length=%d/%d errors=%d\n",
			urb->status, urb->actual_length,
			urb->transfer_buffer_length, urb->error_count);

	switch (urb->status) {
	case 0:             /* success */
	case -ETIMEDOUT:    /* NAK */
		break;
	case -ECONNRESET:   /* kill */
	case -ENOENT:
	case -ESHUTDOWN:
		return;
	default:            /* error */
		dev_err_ratelimited(s->dev, "URB failed %d\n", urb->status);
		break;
	}

	if (likely(urb->actual_length > 0)) {
		void *ptr;
		unsigned int len;
		/* get free framebuffer */
		fbuf = airspy_get_next_fill_buf(s);
		if (unlikely(fbuf == NULL)) {
			s->vb_full++;
			dev_notice_ratelimited(s->dev,
					"videobuf is full, %d packets dropped\n",
					s->vb_full);
			goto skip;
		}

		/* fill framebuffer */
		ptr = vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0);
		len = airspy_convert_stream(s, ptr, urb->transfer_buffer,
				urb->actual_length);
		vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, len);
		fbuf->vb.vb2_buf.timestamp = ktime_get_ns();
		fbuf->vb.sequence = s->sequence++;
		vb2_buffer_done(&fbuf->vb.vb2_buf, VB2_BUF_STATE_DONE);
	}
skip:
	usb_submit_urb(urb, GFP_ATOMIC);
}

Project: linux
File: airspy.c

static void airspy_video_release(struct v4l2_device *v)
{
	struct airspy *s = container_of(v, struct airspy, v4l2_dev);

	v4l2_ctrl_handler_free(&s->hdl);
	v4l2_device_unregister(&s->v4l2_dev);
	kfree(s);
}