Commit 8dbcc3fa authored by Arnd Bergmann's avatar Arnd Bergmann Committed by Mauro Carvalho Chehab
Browse files

media: v4l2: prepare compat-ioctl rework



The v4l2-compat-ioctl32() currently takes an extra round trip through user
space pointers when converting the data structure formats. In particular,
this involves using the compat_alloc_user_space() and copy_in_user()
helpers that often lead to worse compat handlers compared to using
in_compat_syscall() checks when copying the data.

The native implementation already gained a simpler method to deal with
the conversion for the time32 conversion.  Hook into the same places to
provide a location for reading and writing user space data from inside
of the generic video_usercopy() helper.

Hans Verkuil rewrote the video_get_user() function here to simplify
the zeroing of the extra input fields and fixed a couple of bugs in
the original implementation.

[hverkuil: fix: CHECK: Please don't use multiple blank lines]

Co-developed-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab+huawei@kernel.org>
parent 3650b228
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
@@ -1414,6 +1414,59 @@ static int put_v4l2_edid32(struct v4l2_edid __user *p64,
#define VIDIOC_G_OUTPUT32	_IOR ('V', 46, s32)
#define VIDIOC_S_OUTPUT32	_IOWR('V', 47, s32)

unsigned int v4l2_compat_translate_cmd(unsigned int cmd)
{
	switch (cmd) {
	}
	return cmd;
}

int v4l2_compat_get_user(void __user *arg, void *parg, unsigned int cmd)
{
	switch (cmd) {
	}
	return 0;
}

int v4l2_compat_put_user(void __user *arg, void *parg, unsigned int cmd)
{
	switch (cmd) {
	}
	return 0;
}

int v4l2_compat_get_array_args(struct file *file, void *mbuf,
			       void __user *user_ptr, size_t array_size,
			       unsigned int cmd, void *arg)
{
	int err = 0;

	switch (cmd) {
	default:
		if (copy_from_user(mbuf, user_ptr, array_size))
			err = -EFAULT;
		break;
	}

	return err;
}

int v4l2_compat_put_array_args(struct file *file, void __user *user_ptr,
			       void *mbuf, size_t array_size,
			       unsigned int cmd, void *arg)
{
	int err = 0;

	switch (cmd) {
	default:
		if (copy_to_user(user_ptr, mbuf, array_size))
			err = -EFAULT;
		break;
	}

	return err;
}

/**
 * alloc_userspace() - Allocates a 64-bits userspace pointer compatible
 *	for calling the native 64-bits version of an ioctl.
+92 −67
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
 *              Mauro Carvalho Chehab <mchehab@kernel.org> (version 2)
 */

#include <linux/compat.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/slab.h>
@@ -3104,14 +3105,18 @@ static unsigned int video_translate_cmd(unsigned int cmd)
		return VIDIOC_PREPARE_BUF;
#endif
	}
	if (in_compat_syscall())
		return v4l2_compat_translate_cmd(cmd);

	return cmd;
}

static int video_get_user(void __user *arg, void *parg, unsigned int cmd,
static int video_get_user(void __user *arg, void *parg,
			  unsigned int real_cmd, unsigned int cmd,
			  bool *always_copy)
{
	unsigned int n = _IOC_SIZE(cmd);
	unsigned int n = _IOC_SIZE(real_cmd);
	int err = 0;

	if (!(_IOC_DIR(cmd) & _IOC_WRITE)) {
		/* read-only ioctl */
@@ -3119,6 +3124,27 @@ static int video_get_user(void __user *arg, void *parg, unsigned int cmd,
		return 0;
	}

	/*
	 * In some cases, only a few fields are used as input,
	 * i.e. when the app sets "index" and then the driver
	 * fills in the rest of the structure for the thing
	 * with that index.  We only need to copy up the first
	 * non-input field.
	 */
	if (v4l2_is_known_ioctl(real_cmd)) {
		u32 flags = v4l2_ioctls[_IOC_NR(real_cmd)].flags;

		if (flags & INFO_FL_CLEAR_MASK)
			n = (flags & INFO_FL_CLEAR_MASK) >> 16;
		*always_copy = flags & INFO_FL_ALWAYS_COPY;
	}

	if (cmd == real_cmd) {
		if (copy_from_user(parg, (void __user *)arg, n))
			err = -EFAULT;
	} else if (in_compat_syscall()) {
		err = v4l2_compat_get_user(arg, parg, cmd);
	} else {
		switch (cmd) {
#ifdef CONFIG_COMPAT_32BIT_TIME
		case VIDIOC_QUERYBUF_TIME32:
@@ -3146,46 +3172,34 @@ static int video_get_user(void __user *arg, void *parg, unsigned int cmd,
					.length		= vb32.length,
					.request_fd	= vb32.request_fd,
			};

		if (cmd == VIDIOC_QUERYBUF_TIME32)
			vb->request_fd = 0;

			break;
		}
#endif
	default:
		/*
		 * In some cases, only a few fields are used as input,
		 * i.e. when the app sets "index" and then the driver
		 * fills in the rest of the structure for the thing
		 * with that index.  We only need to copy up the first
		 * non-input field.
		 */
		if (v4l2_is_known_ioctl(cmd)) {
			u32 flags = v4l2_ioctls[_IOC_NR(cmd)].flags;

			if (flags & INFO_FL_CLEAR_MASK)
				n = (flags & INFO_FL_CLEAR_MASK) >> 16;
			*always_copy = flags & INFO_FL_ALWAYS_COPY;
		}

		if (copy_from_user(parg, (void __user *)arg, n))
			return -EFAULT;

		/* zero out anything we don't copy from userspace */
		if (n < _IOC_SIZE(cmd))
			memset((u8 *)parg + n, 0, _IOC_SIZE(cmd) - n);
		break;
	}

	return 0;
	/* zero out anything we don't copy from userspace */
	if (!err && n < _IOC_SIZE(real_cmd))
		memset((u8 *)parg + n, 0, _IOC_SIZE(real_cmd) - n);
	return err;
}

static int video_put_user(void __user *arg, void *parg, unsigned int cmd)
static int video_put_user(void __user *arg, void *parg,
			  unsigned int real_cmd, unsigned int cmd)
{
	if (!(_IOC_DIR(cmd) & _IOC_READ))
		return 0;

	if (cmd == real_cmd) {
		/*  Copy results into user buffer  */
		if (copy_to_user(arg, parg, _IOC_SIZE(cmd)))
			return -EFAULT;
		return 0;
	}

	if (in_compat_syscall())
		return v4l2_compat_put_user(arg, parg, cmd);

	switch (cmd) {
#ifdef CONFIG_COMPAT_32BIT_TIME
	case VIDIOC_DQEVENT_TIME32: {
@@ -3236,11 +3250,6 @@ static int video_put_user(void __user *arg, void *parg, unsigned int cmd)
		break;
	}
#endif
	default:
		/*  Copy results into user buffer  */
		if (copy_to_user(arg, parg, _IOC_SIZE(cmd)))
			return -EFAULT;
		break;
	}

	return 0;
@@ -3274,8 +3283,8 @@ video_usercopy(struct file *file, unsigned int orig_cmd, unsigned long arg,
			parg = mbuf;
		}

		err = video_get_user((void __user *)arg, parg, orig_cmd,
				     &always_copy);
		err = video_get_user((void __user *)arg, parg, cmd,
				     orig_cmd, &always_copy);
		if (err)
			goto out;
	}
@@ -3297,7 +3306,14 @@ video_usercopy(struct file *file, unsigned int orig_cmd, unsigned long arg,
		if (NULL == mbuf)
			goto out_array_args;
		err = -EFAULT;
		if (copy_from_user(mbuf, user_ptr, array_size))
		if (in_compat_syscall())
			err = v4l2_compat_get_array_args(file, mbuf, user_ptr,
							 array_size, orig_cmd,
							 parg);
		else
			err = copy_from_user(mbuf, user_ptr, array_size) ?
								-EFAULT : 0;
		if (err)
			goto out_array_args;
		*kernel_ptr = mbuf;
	}
@@ -3318,8 +3334,17 @@ video_usercopy(struct file *file, unsigned int orig_cmd, unsigned long arg,

	if (has_array_args) {
		*kernel_ptr = (void __force *)user_ptr;
		if (copy_to_user(user_ptr, mbuf, array_size))
		if (in_compat_syscall()) {
			int put_err;

			put_err = v4l2_compat_put_array_args(file, user_ptr, mbuf,
							     array_size, orig_cmd,
							     parg);
			if (put_err)
				err = put_err;
		} else if (copy_to_user(user_ptr, mbuf, array_size)) {
			err = -EFAULT;
		}
		goto out_array_args;
	}
	/*
@@ -3330,7 +3355,7 @@ video_usercopy(struct file *file, unsigned int orig_cmd, unsigned long arg,
		goto out;

out_array_args:
	if (video_put_user((void __user *)arg, parg, orig_cmd))
	if (video_put_user((void __user *)arg, parg, cmd, orig_cmd))
		err = -EFAULT;
out:
	kvfree(mbuf);
+10 −0
Original line number Diff line number Diff line
@@ -686,6 +686,16 @@ long int v4l2_compat_ioctl32(struct file *file, unsigned int cmd,
			     unsigned long arg);
#endif

unsigned int v4l2_compat_translate_cmd(unsigned int cmd);
int v4l2_compat_get_user(void __user *arg, void *parg, unsigned int cmd);
int v4l2_compat_put_user(void __user *arg, void *parg, unsigned int cmd);
int v4l2_compat_get_array_args(struct file *file, void *mbuf,
			       void __user *user_ptr, size_t array_size,
			       unsigned int cmd, void *arg);
int v4l2_compat_put_array_args(struct file *file, void __user *user_ptr,
			       void *mbuf, size_t array_size,
			       unsigned int cmd, void *arg);

/**
 * typedef v4l2_kioctl - Typedef used to pass an ioctl handler.
 *