Commit message: misc: mic: Fix for double fetch security bug in VOP driver

The MIC VOP driver does two successive reads from user space to read a
variable length data structure. Kernel memory corruption can result if
the data structure changes between the two reads. This patch disallows
the chance of this happening.

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=116651
Reported by: Pengfei Wang <wpengfeinudt@gmail.com>
Reviewed-by: Sudeep Dutt <sudeep.dutt@intel.com>
Signed-off-by: Ashutosh Dixit <ashutosh.dixit@intel.com>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
CVE: CVE-2016-5728
CWE: CWE-119
Vulnerability description: Race condition in the vop_ioctl function in drivers/misc/mic/vop/vop_vringh.c in the MIC VOP driver in the Linux kernel before 4.6.1 allows local users to obtain sensitive information from kernel memory or cause a denial of service (memory corruption and system crash) by changing a certain header, aka a "double fetch" vulnerability.

Project: linux

static void vop_bh_handler(struct work_struct *work)
{
	struct vop_vdev *vdev = container_of(work, struct vop_vdev,
			virtio_bh_work);

	if (vdev->dc->used_address_updated)
		vop_virtio_init_post(vdev);

	if (vdev->dc->vdev_reset)
		vop_virtio_device_reset(vdev);

	vdev->poll_wake = 1;
	wake_up(&vdev->waitq);
}

Project: linux

int vop_host_init(struct vop_info *vi)
{
	int rc;
	struct miscdevice *mdev;
	struct vop_device *vpdev = vi->vpdev;

	INIT_LIST_HEAD(&vi->vdev_list);
	vi->dma_ch = vpdev->dma_ch;
	mdev = &vi->miscdev;
	mdev->minor = MISC_DYNAMIC_MINOR;
	snprintf(vi->name, sizeof(vi->name), "vop_virtio%d", vpdev->index);
	mdev->name = vi->name;
	mdev->fops = &vop_fops;
	mdev->parent = &vpdev->dev;

	rc = misc_register(mdev);
	if (rc)
		dev_err(&vpdev->dev, "%s failed rc %d\n", __func__, rc);
	return rc;
}

Project: linux

static int vop_virtio_add_device(struct vop_vdev *vdev,
				 struct mic_device_desc *argp)
{
	struct vop_info *vi = vdev->vi;
	struct vop_device *vpdev = vi->vpdev;
	struct mic_device_desc *dd = NULL;
	struct mic_vqconfig *vqconfig;
	int vr_size, i, j, ret;
	u8 type = 0;
	s8 db = -1;
	char irqname[16];
	struct mic_bootparam *bootparam;
	u16 num;
	dma_addr_t vr_addr;

	bootparam = vpdev->hw_ops->get_dp(vpdev);
	init_waitqueue_head(&vdev->waitq);
	INIT_LIST_HEAD(&vdev->list);
	vdev->vpdev = vpdev;

	ret = vop_copy_dp_entry(vdev, argp, &type, &dd);
	if (ret) {
		dev_err(vop_dev(vdev), "%s %d err %d\n",
			__func__, __LINE__, ret);
		kfree(vdev);
		return ret;
	}

	vop_init_device_ctrl(vdev, dd);

	vdev->dd = dd;
	vdev->virtio_id = type;
	vqconfig = mic_vq_config(dd);
	INIT_WORK(&vdev->virtio_bh_work, vop_bh_handler);

	for (i = 0; i < dd->num_vq; i++) {
		struct vop_vringh *vvr = &vdev->vvr[i];
		struct mic_vring *vr = &vdev->vvr[i].vring;

		num = le16_to_cpu(vqconfig[i].num);
		mutex_init(&vvr->vr_mutex);
		vr_size = PAGE_ALIGN(vring_size(num, MIC_VIRTIO_RING_ALIGN) +
			sizeof(struct _mic_vring_info));
		vr->va = (void *)
			__get_free_pages(GFP_KERNEL | __GFP_ZERO,
					 get_order(vr_size));
		if (!vr->va) {
			ret = -ENOMEM;
			dev_err(vop_dev(vdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			goto err;
		}
		vr->len = vr_size;
		vr->info = vr->va + vring_size(num, MIC_VIRTIO_RING_ALIGN);
		vr->info->magic = cpu_to_le32(MIC_MAGIC + vdev->virtio_id + i);
		vr_addr = dma_map_single(&vpdev->dev, vr->va, vr_size,
					 DMA_BIDIRECTIONAL);
		if (dma_mapping_error(&vpdev->dev, vr_addr)) {
			free_pages((unsigned long)vr->va, get_order(vr_size));
			ret = -ENOMEM;
			dev_err(vop_dev(vdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			goto err;
		}
		vqconfig[i].address = cpu_to_le64(vr_addr);

		vring_init(&vr->vr, num, vr->va, MIC_VIRTIO_RING_ALIGN);
		ret = vringh_init_kern(&vvr->vrh,
				       *(u32 *)mic_vq_features(vdev->dd),
				       num, false, vr->vr.desc, vr->vr.avail,
				       vr->vr.used);
		if (ret) {
			dev_err(vop_dev(vdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			goto err;
		}
		vringh_kiov_init(&vvr->riov, NULL, 0);
		vringh_kiov_init(&vvr->wiov, NULL, 0);
		vvr->head = USHRT_MAX;
		vvr->vdev = vdev;
		vvr->vrh.notify = _vop_notify;
		dev_dbg(&vpdev->dev,
			"%s %d index %d va %p info %p vr_size 0x%x\n",
			__func__, __LINE__, i, vr->va, vr->info, vr_size);
		vvr->buf = (void *)__get_free_pages(GFP_KERNEL,
					get_order(VOP_INT_DMA_BUF_SIZE));
		vvr->buf_da = dma_map_single(&vpdev->dev,
					  vvr->buf, VOP_INT_DMA_BUF_SIZE,
					  DMA_BIDIRECTIONAL);
	}

	snprintf(irqname, sizeof(irqname), "vop%dvirtio%d", vpdev->index,
		 vdev->virtio_id);
	vdev->virtio_db = vpdev->hw_ops->next_db(vpdev);
	vdev->virtio_cookie = vpdev->hw_ops->request_irq(vpdev,
			_vop_virtio_intr_handler, irqname, vdev,
			vdev->virtio_db);
	if (IS_ERR(vdev->virtio_cookie)) {
		ret = PTR_ERR(vdev->virtio_cookie);
		dev_dbg(&vpdev->dev, "request irq failed\n");
		goto err;
	}

	vdev->dc->c2h_vdev_db = vdev->virtio_db;

	/*
	 * Order the type update with previous stores. This write barrier
	 * is paired with the corresponding read barrier before the uncached
	 * system memory read of the type, on the card while scanning the
	 * device page.
	 */
	smp_wmb();
	dd->type = type;
	argp->type = type;

	if (bootparam) {
		db = bootparam->h2c_config_db;
		if (db != -1)
			vpdev->hw_ops->send_intr(vpdev, db);
	}
	dev_dbg(&vpdev->dev, "Added virtio id %d db %d\n", dd->type, db);
	return 0;
err:
	vqconfig = mic_vq_config(dd);
	for (j = 0; j < i; j++) {
		struct vop_vringh *vvr = &vdev->vvr[j];

		dma_unmap_single(&vpdev->dev, le64_to_cpu(vqconfig[j].address),
				 vvr->vring.len, DMA_BIDIRECTIONAL);
		free_pages((unsigned long)vvr->vring.va,
			   get_order(vvr->vring.len));
	}
	return ret;
}

Project: linux

static int vop_virtio_copy_from_user(struct vop_vdev *vdev, void __user *ubuf,
				     size_t len, u64 daddr, size_t dlen,
				     int vr_idx)
{
	struct vop_device *vpdev = vdev->vpdev;
	void __iomem *dbuf = vpdev->hw_ops->ioremap(vpdev, daddr, len);
	struct vop_vringh *vvr = &vdev->vvr[vr_idx];
	struct vop_info *vi = dev_get_drvdata(&vdev->vpdev->dev);
	size_t dma_alignment = 1 << vi->dma_ch->device->copy_align;
	bool x200 = is_dma_copy_aligned(vi->dma_ch->device, 1, 1, 1);
	size_t partlen;
	bool dma = VOP_USE_DMA;
	int err = 0;

	if (daddr & (dma_alignment - 1)) {
		vdev->tx_dst_unaligned += len;
		dma = false;
	} else if (ALIGN(len, dma_alignment) > dlen) {
		vdev->tx_len_unaligned += len;
		dma = false;
	}

	if (!dma)
		goto memcpy;

	/*
	 * X100 uses DMA addresses as seen by the card so adding
	 * the aperture base is not required for DMA. However x200
	 * requires DMA addresses to be an offset into the bar so
	 * add the aperture base for x200.
	 */
	if (x200)
		daddr += vpdev->aper->pa;
	while (len) {
		partlen = min_t(size_t, len, VOP_INT_DMA_BUF_SIZE);

		if (copy_from_user(vvr->buf, ubuf, partlen)) {
			err = -EFAULT;
			dev_err(vop_dev(vdev), "%s %d err %d\n",
				__func__, __LINE__, err);
			goto err;
		}
		err = vop_sync_dma(vdev, daddr, vvr->buf_da,
				   ALIGN(partlen, dma_alignment));
		if (err) {
			dev_err(vop_dev(vdev), "%s %d err %d\n",
				__func__, __LINE__, err);
			goto err;
		}
		daddr += partlen;
		ubuf += partlen;
		dbuf += partlen;
		vdev->out_bytes_dma += partlen;
		vdev->out_bytes += partlen;
		len -= partlen;
	}
memcpy:
	/*
	 * We are copying to IO below and should ideally use something
	 * like copy_from_user_toio(..) if it existed.
	 */
	if (copy_from_user((void __force *)dbuf, ubuf, len)) {
		err = -EFAULT;
		dev_err(vop_dev(vdev), "%s %d err %d\n",
			__func__, __LINE__, err);
		goto err;
	}
	vdev->out_bytes += len;
	err = 0;
err:
	vpdev->hw_ops->iounmap(vpdev, dbuf);
	dev_dbg(vop_dev(vdev),
		"%s: ubuf %p dbuf %p len 0x%lx vr_idx 0x%x\n",
		__func__, ubuf, dbuf, len, vr_idx);
	return err;
}

Project: linux

static void vop_virtio_del_device(struct vop_vdev *vdev)
{
	struct vop_info *vi = vdev->vi;
	struct vop_device *vpdev = vdev->vpdev;
	int i;
	struct mic_vqconfig *vqconfig;
	struct mic_bootparam *bootparam = vpdev->hw_ops->get_dp(vpdev);

	if (!bootparam)
		goto skip_hot_remove;
	vop_dev_remove(vi, vdev->dc, vpdev);
skip_hot_remove:
	vpdev->hw_ops->free_irq(vpdev, vdev->virtio_cookie, vdev);
	flush_work(&vdev->virtio_bh_work);
	vqconfig = mic_vq_config(vdev->dd);
	for (i = 0; i < vdev->dd->num_vq; i++) {
		struct vop_vringh *vvr = &vdev->vvr[i];

		dma_unmap_single(&vpdev->dev,
				 vvr->buf_da, VOP_INT_DMA_BUF_SIZE,
				 DMA_BIDIRECTIONAL);
		free_pages((unsigned long)vvr->buf,
			   get_order(VOP_INT_DMA_BUF_SIZE));
		vringh_kiov_cleanup(&vvr->riov);
		vringh_kiov_cleanup(&vvr->wiov);
		dma_unmap_single(&vpdev->dev, le64_to_cpu(vqconfig[i].address),
				 vvr->vring.len, DMA_BIDIRECTIONAL);
		free_pages((unsigned long)vvr->vring.va,
			   get_order(vvr->vring.len));
	}
	/*
	 * Order the type update with previous stores. This write barrier
	 * is paired with the corresponding read barrier before the uncached
	 * system memory read of the type, on the card while scanning the
	 * device page.
	 */
	smp_wmb();
	vdev->dd->type = -1;
}

Project: linux

static int vop_vringh_copy(struct vop_vdev *vdev, struct vringh_kiov *iov,
			   void __user *ubuf, size_t len, bool read, int vr_idx,
			   size_t *out_len)
{
	int ret = 0;
	size_t partlen, tot_len = 0;

	while (len && iov->i < iov->used) {
		struct kvec *kiov = &iov->iov[iov->i];

		partlen = min(kiov->iov_len, len);
		if (read)
			ret = vop_virtio_copy_to_user(vdev, ubuf, partlen,
						      (u64)kiov->iov_base,
						      kiov->iov_len,
						      vr_idx);
		else
			ret = vop_virtio_copy_from_user(vdev, ubuf, partlen,
							(u64)kiov->iov_base,
							kiov->iov_len,
							vr_idx);
		if (ret) {
			dev_err(vop_dev(vdev), "%s %d err %d\n",
				__func__, __LINE__, ret);
			break;
		}
		len -= partlen;
		ubuf += partlen;
		tot_len += partlen;
		iov->consumed += partlen;
		kiov->iov_len -= partlen;
		kiov->iov_base += partlen;
		if (!kiov->iov_len) {
			/* Fix up old iov element then increment. */
			kiov->iov_len = iov->consumed;
			kiov->iov_base -= iov->consumed;

			iov->consumed = 0;
			iov->i++;
		}
	}
	*out_len = tot_len;
	return ret;
}