aboutsummaryrefslogtreecommitdiff
path: root/sys/compat
diff options
context:
space:
mode:
authorHans Petter Selasky <hselasky@FreeBSD.org>2016-05-12 11:38:28 +0000
committerHans Petter Selasky <hselasky@FreeBSD.org>2016-05-12 11:38:28 +0000
commit3a8bec33ef8c141bbed385909913b110c674f0e5 (patch)
tree04f7e04df6406ff7a834fe475d053f0db4925dfc /sys/compat
parentcdf63a700c77204252e3c2e38d7106965559f3c6 (diff)
downloadsrc-3a8bec33ef8c141bbed385909913b110c674f0e5.tar.gz
src-3a8bec33ef8c141bbed385909913b110c674f0e5.zip
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
Notes
Notes: svn path=/head/; revision=299530
Diffstat (limited to 'sys/compat')
-rw-r--r--sys/compat/linuxkpi/common/include/asm/uaccess.h4
-rw-r--r--sys/compat/linuxkpi/common/include/linux/sched.h2
-rw-r--r--sys/compat/linuxkpi/common/include/linux/uaccess.h26
-rw-r--r--sys/compat/linuxkpi/common/src/linux_compat.c85
4 files changed, 97 insertions, 20 deletions
diff --git a/sys/compat/linuxkpi/common/include/asm/uaccess.h b/sys/compat/linuxkpi/common/include/asm/uaccess.h
index ce90355220f4..f3e743f1b39d 100644
--- a/sys/compat/linuxkpi/common/include/asm/uaccess.h
+++ b/sys/compat/linuxkpi/common/include/asm/uaccess.h
@@ -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, unsigned long n)
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;
}
diff --git a/sys/compat/linuxkpi/common/include/linux/sched.h b/sys/compat/linuxkpi/common/include/linux/sched.h
index ca1effe88600..bdaf7ae070f3 100644
--- a/sys/compat/linuxkpi/common/include/linux/sched.h
+++ b/sys/compat/linuxkpi/common/include/linux/sched.h
@@ -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)
diff --git a/sys/compat/linuxkpi/common/include/linux/uaccess.h b/sys/compat/linuxkpi/common/include/linux/uaccess.h
index e44024ec56df..2cfd950ab4de 100644
--- a/sys/compat/linuxkpi/common/include/linux/uaccess.h
+++ b/sys/compat/linuxkpi/common/include/linux/uaccess.h
@@ -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
diff --git a/sys/compat/linuxkpi/common/src/linux_compat.c b/sys/compat/linuxkpi/common/src/linux_compat.c
index a03063bc9f63..0584f23ee9ed 100644
--- a/sys/compat/linuxkpi/common/src/linux_compat.c
+++ b/sys/compat/linuxkpi/common/src/linux_compat.c
@@ -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 fflag, int devtype, struct thread *td)
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 cmd, caddr_t data, int fflag,
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 cmd, caddr_t data, int fflag,
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