svn commit: r299530 - in head/sys: compat/linuxkpi/common/include/asm compat/linuxkpi/common/include/linux compat/linuxkpi/common/src sys

Hans Petter Selasky hselasky at FreeBSD.org
Thu May 12 11:38:30 UTC 2016


Author: hselasky
Date: Thu May 12 11:38:28 2016
New Revision: 299530
URL: https://svnweb.freebsd.org/changeset/base/299530

Log:
  Fix handling of IOCTLs in the LinuxKPI.
  
  Linux requires that all IOCTL data resides in userspace. FreeBSD
  always moves the main IOCTL structure into a kernel buffer before
  invoking the IOCTL handler and then copies it back into userspace,
  before returning. Hide this difference in the "linux_copyin()" and
  "linux_copyout()" functions by remapping userspace addresses in the
  range from 0x10000 to 0x20000, to the kernel IOCTL data buffer.
  
  It is assumed that the userspace code, data and stack segments starts
  no lower than memory address 0x400000, which is also stated by "man 1
  ld", which means any valid userspace pointer can be passed to regular
  LinuxKPI handled IOCTLs.
  
  Bump the FreeBSD version to force recompilation of all kernel modules.
  
  Discussed with:	kmacy @
  MFC after:	1 week
  Sponsored by:	Mellanox Technologies

Modified:
  head/sys/compat/linuxkpi/common/include/asm/uaccess.h
  head/sys/compat/linuxkpi/common/include/linux/sched.h
  head/sys/compat/linuxkpi/common/include/linux/uaccess.h
  head/sys/compat/linuxkpi/common/src/linux_compat.c
  head/sys/sys/param.h

Modified: head/sys/compat/linuxkpi/common/include/asm/uaccess.h
==============================================================================
--- head/sys/compat/linuxkpi/common/include/asm/uaccess.h	Thu May 12 10:16:16 2016	(r299529)
+++ head/sys/compat/linuxkpi/common/include/asm/uaccess.h	Thu May 12 11:38:28 2016	(r299530)
@@ -36,7 +36,7 @@
 static inline long
 copy_to_user(void *to, const void *from, unsigned long n)
 {
-	if (copyout(from, to, n) != 0)
+	if (linux_copyout(from, to, n) != 0)
 		return n;
 	return 0;
 }
@@ -44,7 +44,7 @@ copy_to_user(void *to, const void *from,
 static inline long
 copy_from_user(void *to, const void *from, unsigned long n)
 {
-	if (copyin(from, to, n) != 0)
+	if (linux_copyin(from, to, n) != 0)
 		return n;
 	return 0;
 }

Modified: head/sys/compat/linuxkpi/common/include/linux/sched.h
==============================================================================
--- head/sys/compat/linuxkpi/common/include/linux/sched.h	Thu May 12 10:16:16 2016	(r299529)
+++ head/sys/compat/linuxkpi/common/include/linux/sched.h	Thu May 12 11:38:28 2016	(r299530)
@@ -66,6 +66,8 @@ struct task_struct {
 	int	should_stop;
 	pid_t	pid;
 	const char    *comm;
+	void	*bsd_ioctl_data;
+	unsigned	bsd_ioctl_len;
 };
 
 #define	current			task_struct_get(curthread)

Modified: head/sys/compat/linuxkpi/common/include/linux/uaccess.h
==============================================================================
--- head/sys/compat/linuxkpi/common/include/linux/uaccess.h	Thu May 12 10:16:16 2016	(r299529)
+++ head/sys/compat/linuxkpi/common/include/linux/uaccess.h	Thu May 12 11:38:28 2016	(r299530)
@@ -34,19 +34,23 @@
 
 #include <linux/compiler.h>
 
-#define	__get_user(_x, _p) ({				\
-	int __err;					\
-	__typeof(*(_p)) __x;				\
-	__err = -copyin((_p), &(__x), sizeof(*(_p)));	\
-	(_x) = __x;					\
-	__err;						\
+#define	__get_user(_x, _p) ({					\
+	int __err;						\
+	__typeof(*(_p)) __x;					\
+	__err = linux_copyin((_p), &(__x), sizeof(*(_p)));	\
+	(_x) = __x;						\
+	__err;							\
 })
-#define	__put_user(_x, _p) ({			\
-	__typeof(*(_p)) __x = (_x);		\
-	-copyout(&(__x), (_p), sizeof(*(_p)));	\
+
+#define	__put_user(_x, _p) ({				\
+	__typeof(*(_p)) __x = (_x);			\
+	linux_copyout(&(__x), (_p), sizeof(*(_p)));	\
 })
-#define	get_user(_x, _p)	-copyin((_p), &(_x), sizeof(*(_p)))
-#define	put_user(_x, _p)	-copyout(&(_x), (_p), sizeof(*(_p)))
+#define	get_user(_x, _p)	linux_copyin((_p), &(_x), sizeof(*(_p)))
+#define	put_user(_x, _p)	linux_copyout(&(_x), (_p), sizeof(*(_p)))
+
+extern int linux_copyin(const void *uaddr, void *kaddr, size_t len);
+extern int linux_copyout(const void *kaddr, void *uaddr, size_t len);
 
 /*
  * NOTE: The returned value from pagefault_disable() must be stored

Modified: head/sys/compat/linuxkpi/common/src/linux_compat.c
==============================================================================
--- head/sys/compat/linuxkpi/common/src/linux_compat.c	Thu May 12 10:16:16 2016	(r299529)
+++ head/sys/compat/linuxkpi/common/src/linux_compat.c	Thu May 12 11:38:28 2016	(r299530)
@@ -66,6 +66,7 @@ __FBSDID("$FreeBSD$");
 #include <linux/workqueue.h>
 #include <linux/rcupdate.h>
 #include <linux/interrupt.h>
+#include <linux/uaccess.h>
 
 #include <vm/vm_pager.h>
 
@@ -461,6 +462,66 @@ linux_dev_close(struct cdev *dev, int ff
 	return (0);
 }
 
+#define	LINUX_IOCTL_MIN_PTR 0x10000UL
+#define	LINUX_IOCTL_MAX_PTR (LINUX_IOCTL_MIN_PTR + IOCPARM_MAX)
+
+static inline int
+linux_remap_address(void **uaddr, size_t len)
+{
+	uintptr_t uaddr_val = (uintptr_t)(*uaddr);
+
+	if (unlikely(uaddr_val >= LINUX_IOCTL_MIN_PTR &&
+	    uaddr_val < LINUX_IOCTL_MAX_PTR)) {
+		struct task_struct *pts = current;
+		if (pts == NULL) {
+			*uaddr = NULL;
+			return (1);
+		}
+
+		/* compute data offset */
+		uaddr_val -= LINUX_IOCTL_MIN_PTR;
+
+		/* check that length is within bounds */
+		if ((len > IOCPARM_MAX) ||
+		    (uaddr_val + len) > pts->bsd_ioctl_len) {
+			*uaddr = NULL;
+			return (1);
+		}
+
+		/* re-add kernel buffer address */
+		uaddr_val += (uintptr_t)pts->bsd_ioctl_data;
+
+		/* update address location */
+		*uaddr = (void *)uaddr_val;
+		return (1);
+	}
+	return (0);
+}
+
+int
+linux_copyin(const void *uaddr, void *kaddr, size_t len)
+{
+	if (linux_remap_address(__DECONST(void **, &uaddr), len)) {
+		if (uaddr == NULL)
+			return (-EFAULT);
+		memcpy(kaddr, uaddr, len);
+		return (0);
+	}
+	return (-copyin(uaddr, kaddr, len));
+}
+
+int
+linux_copyout(const void *kaddr, void *uaddr, size_t len)
+{
+	if (linux_remap_address(&uaddr, len)) {
+		if (uaddr == NULL)
+			return (-EFAULT);
+		memcpy(uaddr, kaddr, len);
+		return (0);
+	}
+	return (-copyout(kaddr, uaddr, len));
+}
+
 static int
 linux_dev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
     struct thread *td)
@@ -469,6 +530,7 @@ linux_dev_ioctl(struct cdev *dev, u_long
 	struct linux_file *filp;
 	struct task_struct t;
 	struct file *file;
+	unsigned size;
 	int error;
 
 	file = td->td_fpop;
@@ -479,13 +541,22 @@ linux_dev_ioctl(struct cdev *dev, u_long
 		return (error);
 	filp->f_flags = file->f_flag;
 	linux_set_current(td, &t);
-	/*
-	 * Linux does not have a generic ioctl copyin/copyout layer.  All
-	 * linux ioctls must be converted to void ioctls which pass a
-	 * pointer to the address of the data.  We want the actual user
-	 * address so we dereference here.
-	 */
-	data = *(void **)data;
+	size = IOCPARM_LEN(cmd);
+	/* refer to logic in sys_ioctl() */
+	if (size > 0) {
+		/*
+		 * Setup hint for linux_copyin() and linux_copyout().
+		 *
+		 * Background: Linux code expects a user-space address
+		 * while FreeBSD supplies a kernel-space address.
+		 */
+		t.bsd_ioctl_data = data;
+		t.bsd_ioctl_len = size;
+		data = (void *)LINUX_IOCTL_MIN_PTR;
+	} else {
+		/* fetch user-space pointer */
+		data = *(void **)data;
+	}
 	if (filp->f_op->unlocked_ioctl)
 		error = -filp->f_op->unlocked_ioctl(filp, cmd, (u_long)data);
 	else

Modified: head/sys/sys/param.h
==============================================================================
--- head/sys/sys/param.h	Thu May 12 10:16:16 2016	(r299529)
+++ head/sys/sys/param.h	Thu May 12 11:38:28 2016	(r299530)
@@ -58,7 +58,7 @@
  *		in the range 5 to 9.
  */
 #undef __FreeBSD_version
-#define __FreeBSD_version 1100107	/* Master, propagated to newvers */
+#define __FreeBSD_version 1100108	/* Master, propagated to newvers */
 
 /*
  * __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,


More information about the svn-src-head mailing list