diff --git a/sys/compat/linuxkpi/common/include/asm/uaccess.h b/sys/compat/linuxkpi/common/include/asm/uaccess.h index ce90355220f..f3e743f1b39 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 ca1effe8860..bdaf7ae070f 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 e44024ec56d..2cfd950ab4d 100644 --- a/sys/compat/linuxkpi/common/include/linux/uaccess.h +++ b/sys/compat/linuxkpi/common/include/linux/uaccess.h @@ -34,19 +34,23 @@ #include -#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 a03063bc9f6..0584f23ee9e 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 #include #include +#include #include @@ -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 diff --git a/sys/sys/param.h b/sys/sys/param.h index 6c64074d41d..5074c5ad537 100644 --- a/sys/sys/param.h +++ b/sys/sys/param.h @@ -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,