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 unsigned int airspy_convert_stream(struct airspy *s,
		void *dst, void *src, unsigned int src_len)
{
	unsigned int dst_len;

	if (s->pixelformat == V4L2_SDR_FMT_RU12LE) {
		memcpy(dst, src, src_len);
		dst_len = src_len;
	} else {
		dst_len = 0;
	}

	/* calculate sample rate and output it in 10 seconds intervals */
	if (unlikely(time_is_before_jiffies(s->jiffies_next))) {
		#define MSECS 10000UL
		unsigned int msecs = jiffies_to_msecs(jiffies -
				s->jiffies_next + msecs_to_jiffies(MSECS));
		unsigned int samples = s->sample - s->sample_measured;

		s->jiffies_next = jiffies + msecs_to_jiffies(MSECS);
		s->sample_measured = s->sample;
		dev_dbg(s->dev, "slen=%u samples=%u msecs=%u sample rate=%lu\n",
				src_len, samples, msecs,
				samples * 1000UL / msecs);
	}

	/* total number of samples */
	s->sample += src_len / 2;

	return dst_len;
}

Project: linux
File: airspy.c

static int airspy_ctrl_msg(struct airspy *s, u8 request, u16 value, u16 index,
		u8 *data, u16 size)
{
	int ret;
	unsigned int pipe;
	u8 requesttype;

	switch (request) {
	case CMD_RECEIVER_MODE:
	case CMD_SET_FREQ:
		pipe = usb_sndctrlpipe(s->udev, 0);
		requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT);
		break;
	case CMD_BOARD_ID_READ:
	case CMD_VERSION_STRING_READ:
	case CMD_BOARD_PARTID_SERIALNO_READ:
	case CMD_SET_LNA_GAIN:
	case CMD_SET_MIXER_GAIN:
	case CMD_SET_VGA_GAIN:
	case CMD_SET_LNA_AGC:
	case CMD_SET_MIXER_AGC:
		pipe = usb_rcvctrlpipe(s->udev, 0);
		requesttype = (USB_TYPE_VENDOR | USB_DIR_IN);
		break;
	default:
		dev_err(s->dev, "Unknown command %02x\n", request);
		ret = -EINVAL;
		goto err;
	}

	/* write request */
	if (!(requesttype & USB_DIR_IN))
		memcpy(s->buf, data, size);

	ret = usb_control_msg(s->udev, pipe, request, requesttype, value,
			index, s->buf, size, 1000);
	airspy_dbg_usb_control_msg(s->dev, request, requesttype, value,
			index, s->buf, size);
	if (ret < 0) {
		dev_err(s->dev, "usb_control_msg() failed %d request %02x\n",
				ret, request);
		goto err;
	}

	/* read request */
	if (requesttype & USB_DIR_IN)
		memcpy(data, s->buf, size);

	return 0;
err:
	return ret;
}

Project: linux
File: airspy.c

static int airspy_s_frequency(struct file *file, void *priv,
		const struct v4l2_frequency *f)
{
	struct airspy *s = video_drvdata(file);
	int ret;
	u8 buf[4];

	if (f->tuner == 0) {
		s->f_adc = clamp_t(unsigned int, f->frequency,
				bands[0].rangelow,
				bands[0].rangehigh);
		dev_dbg(s->dev, "ADC frequency=%u Hz\n", s->f_adc);
		ret = 0;
	} else if (f->tuner == 1) {
		s->f_rf = clamp_t(unsigned int, f->frequency,
				bands_rf[0].rangelow,
				bands_rf[0].rangehigh);
		dev_dbg(s->dev, "RF frequency=%u Hz\n", s->f_rf);
		buf[0] = (s->f_rf >>  0) & 0xff;
		buf[1] = (s->f_rf >>  8) & 0xff;
		buf[2] = (s->f_rf >> 16) & 0xff;
		buf[3] = (s->f_rf >> 24) & 0xff;
		ret = airspy_ctrl_msg(s, CMD_SET_FREQ, 0, 0, buf, 4);
	} else {
		ret = -EINVAL;
	}

	return ret;
}