Vulnerability: The video_usercopy function in drivers/media/video/v4l2-ioctl.c in the Linux kernel before 2.6.39 relies on the count value of a v4l2_ext_controls data structure to determine a kmalloc size, which might allow local users to cause a denial of service (memory consumption) via a large value.
Vulnerability ID: CVE-2010-5329
CWE: CWE-399
Severity score: 6.9
Project: https://github.com/torvalds/linux
Published: 2017-04-24
File: drivers/media/video/v4l2-ioctl.c
Commit message: [media] v4l: Share code between video_usercopy and video_ioctl2

The two functions are mostly identical. They handle the copy_from_user
and copy_to_user operations related with V4L2 ioctls and call the real
ioctl handler.

Create a __video_usercopy function that implements the core of
video_usercopy and video_ioctl2, and call that function from both.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Commit author: Laurent Pinchart

Vulnerable version (before fix):
 void v4l_printk_ioctl(unsigned int cmd) } EXPORT_SYMBOL(v4l_printk_ioctl);  /*  * helper function -- handles userspace copying for ioctl arguments  * Obsolete usercopy function - Should be removed soon  */ long video_usercopy(struct file *file, unsigned int cmd, unsigned long arg, 		v4l2_kioctl func) { 	char	sbuf[128]; 	void    *mbuf = NULL; 	void	*parg = NULL; 	long	err  = -EINVAL; 	int     is_ext_ctrl; 	size_t  ctrls_size = 0; 	void __user *user_ptr = NULL;  	is_ext_ctrl = (cmd == VIDIOC_S_EXT_CTRLS || cmd == VIDIOC_G_EXT_CTRLS || 		       cmd == VIDIOC_TRY_EXT_CTRLS);  	/*  Copy arguments into temp kernel buffer  */ 	switch (_IOC_DIR(cmd)) { 	case _IOC_NONE: 		parg = NULL; 		break; 	case _IOC_READ: 	case _IOC_WRITE: 	case (_IOC_WRITE | _IOC_READ): 		if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { 			parg = sbuf; 		} else { 			/* too big to allocate from stack */ 			mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL); 			if (NULL == mbuf) 				return -ENOMEM; 			parg = mbuf; 		}  		err = -EFAULT; 		if (_IOC_DIR(cmd) & _IOC_WRITE) 			if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) 				goto out; 		break; 	} 	if (is_ext_ctrl) { 		struct v4l2_ext_controls *p = parg;  		/* In case of an error, tell the caller that it wasn't 		   a specific control that caused it. */ 		p->error_idx = p->count; 		user_ptr = (void __user *)p->controls; 		if (p->count) { 			ctrls_size = sizeof(struct v4l2_ext_control) * p->count; 			/* Note: v4l2_ext_controls fits in sbuf[] so mbuf is still NULL. */ 			mbuf = kmalloc(ctrls_size, GFP_KERNEL); 			err = -ENOMEM; 			if (NULL == mbuf) 				goto out_ext_ctrl; 			err = -EFAULT; 			if (copy_from_user(mbuf, user_ptr, ctrls_size)) 				goto out_ext_ctrl; 			p->controls = mbuf; 		} 	}  	/* call driver */ 	err = func(file, cmd, parg); 	if (err == -ENOIOCTLCMD) 		err = -EINVAL; 	if (is_ext_ctrl) { 		struct v4l2_ext_controls *p = parg;  		p->controls = (void *)user_ptr; 		if (p->count && err == 0 && copy_to_user(user_ptr, mbuf, ctrls_size)) 			err = -EFAULT; 		goto out_ext_ctrl; 	} 	if (err < 0) 		goto out;  out_ext_ctrl: 	/*  Copy results into user buffer  */ 	switch (_IOC_DIR(cmd)) { 	case _IOC_READ: 	case (_IOC_WRITE | _IOC_READ): 		if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) 			err = -EFAULT; 		break; 	}  out: 	kfree(mbuf); 	return err; } EXPORT_SYMBOL(video_usercopy);  static void dbgbuf(unsigned int cmd, struct video_device *vfd, 					struct v4l2_buffer *p) { static int check_array_args(unsigned int cmd, void *parg, size_t *array_size, 	return ret; }  long video_ioctl2(struct file *file, 	       unsigned int cmd, unsigned long arg) { 	char	sbuf[128]; 	void    *mbuf = NULL; long video_ioctl2(struct file *file, 	}  	/* Handles IOCTL */ 	err = __video_do_ioctl(file, cmd, parg); 	if (err == -ENOIOCTLCMD) 		err = -EINVAL;  long video_ioctl2(struct file *file, 	kfree(mbuf); 	return err; } EXPORT_SYMBOL(video_ioctl2);

Fixed version (after fix):
 void v4l_printk_ioctl(unsigned int cmd) } EXPORT_SYMBOL(v4l_printk_ioctl);  static void dbgbuf(unsigned int cmd, struct video_device *vfd, 					struct v4l2_buffer *p) { static int check_array_args(unsigned int cmd, void *parg, size_t *array_size, 	return ret; }  long video_usercopy(struct file *file, unsigned int cmd, unsigned long arg, 	       v4l2_kioctl func) { 	char	sbuf[128]; 	void    *mbuf = NULL; long video_ioctl2(struct file *file, 	}  	/* Handles IOCTL */ 	err = func(file, cmd, parg); 	if (err == -ENOIOCTLCMD) 		err = -EINVAL;  long video_ioctl2(struct file *file, 	kfree(mbuf); 	return err; } EXPORT_SYMBOL(video_usercopy);  long video_ioctl2(struct file *file, 	       unsigned int cmd, unsigned long arg) { 	return video_usercopy(file, cmd, arg, __video_do_ioctl); } EXPORT_SYMBOL(video_ioctl2);

Patch:
patch: @@ -294,101 +294,6 @@ void v4l_printk_ioctl(unsigned int cmd)
 }
 EXPORT_SYMBOL(v4l_printk_ioctl);
 
-/*
- * helper function -- handles userspace copying for ioctl arguments
- * Obsolete usercopy function - Should be removed soon
- */
-long
-video_usercopy(struct file *file, unsigned int cmd, unsigned long arg,
-		v4l2_kioctl func)
-{
-	char	sbuf[128];
-	void    *mbuf = NULL;
-	void	*parg = NULL;
-	long	err  = -EINVAL;
-	int     is_ext_ctrl;
-	size_t  ctrls_size = 0;
-	void __user *user_ptr = NULL;
-
-	is_ext_ctrl = (cmd == VIDIOC_S_EXT_CTRLS || cmd == VIDIOC_G_EXT_CTRLS ||
-		       cmd == VIDIOC_TRY_EXT_CTRLS);
-
-	/*  Copy arguments into temp kernel buffer  */
-	switch (_IOC_DIR(cmd)) {
-	case _IOC_NONE:
-		parg = NULL;
-		break;
-	case _IOC_READ:
-	case _IOC_WRITE:
-	case (_IOC_WRITE | _IOC_READ):
-		if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
-			parg = sbuf;
-		} else {
-			/* too big to allocate from stack */
-			mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
-			if (NULL == mbuf)
-				return -ENOMEM;
-			parg = mbuf;
-		}
-
-		err = -EFAULT;
-		if (_IOC_DIR(cmd) & _IOC_WRITE)
-			if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd)))
-				goto out;
-		break;
-	}
-	if (is_ext_ctrl) {
-		struct v4l2_ext_controls *p = parg;
-
-		/* In case of an error, tell the caller that it wasn't
-		   a specific control that caused it. */
-		p->error_idx = p->count;
-		user_ptr = (void __user *)p->controls;
-		if (p->count) {
-			ctrls_size = sizeof(struct v4l2_ext_control) * p->count;
-			/* Note: v4l2_ext_controls fits in sbuf[] so mbuf is still NULL. */
-			mbuf = kmalloc(ctrls_size, GFP_KERNEL);
-			err = -ENOMEM;
-			if (NULL == mbuf)
-				goto out_ext_ctrl;
-			err = -EFAULT;
-			if (copy_from_user(mbuf, user_ptr, ctrls_size))
-				goto out_ext_ctrl;
-			p->controls = mbuf;
-		}
-	}
-
-	/* call driver */
-	err = func(file, cmd, parg);
-	if (err == -ENOIOCTLCMD)
-		err = -EINVAL;
-	if (is_ext_ctrl) {
-		struct v4l2_ext_controls *p = parg;
-
-		p->controls = (void *)user_ptr;
-		if (p->count && err == 0 && copy_to_user(user_ptr, mbuf, ctrls_size))
-			err = -EFAULT;
-		goto out_ext_ctrl;
-	}
-	if (err < 0)
-		goto out;
-
-out_ext_ctrl:
-	/*  Copy results into user buffer  */
-	switch (_IOC_DIR(cmd)) {
-	case _IOC_READ:
-	case (_IOC_WRITE | _IOC_READ):
-		if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
-			err = -EFAULT;
-		break;
-	}
-
-out:
-	kfree(mbuf);
-	return err;
-}
-EXPORT_SYMBOL(video_usercopy);
-
 static void dbgbuf(unsigned int cmd, struct video_device *vfd,
 					struct v4l2_buffer *p)
 {
@@ -2332,8 +2237,9 @@ static int check_array_args(unsigned int cmd, void *parg, size_t *array_size,
 	return ret;
 }
 
-long video_ioctl2(struct file *file,
-	       unsigned int cmd, unsigned long arg)
+long
+video_usercopy(struct file *file, unsigned int cmd, unsigned long arg,
+	       v4l2_kioctl func)
 {
 	char	sbuf[128];
 	void    *mbuf = NULL;
@@ -2395,7 +2301,7 @@ long video_ioctl2(struct file *file,
 	}
 
 	/* Handles IOCTL */
-	err = __video_do_ioctl(file, cmd, parg);
+	err = func(file, cmd, parg);
 	if (err == -ENOIOCTLCMD)
 		err = -EINVAL;
 
@@ -2422,4 +2328,11 @@ long video_ioctl2(struct file *file,
 	kfree(mbuf);
 	return err;
 }
+EXPORT_SYMBOL(video_usercopy);
+
+long video_ioctl2(struct file *file,
+	       unsigned int cmd, unsigned long arg)
+{
+	return video_usercopy(file, cmd, arg, __video_do_ioctl);
+}
 EXPORT_SYMBOL(video_ioctl2);
path: drivers/media/video/v4l2-ioctl.c