move compat select-related syscalls to fs/select.c
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
368
fs/compat.c
368
fs/compat.c
@@ -43,7 +43,6 @@
|
|||||||
#include <linux/security.h>
|
#include <linux/security.h>
|
||||||
#include <linux/highmem.h>
|
#include <linux/highmem.h>
|
||||||
#include <linux/signal.h>
|
#include <linux/signal.h>
|
||||||
#include <linux/poll.h>
|
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/fs_struct.h>
|
#include <linux/fs_struct.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
@@ -925,373 +924,6 @@ COMPAT_SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, fla
|
|||||||
return do_sys_open(dfd, filename, flags, mode);
|
return do_sys_open(dfd, filename, flags, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define __COMPAT_NFDBITS (8 * sizeof(compat_ulong_t))
|
|
||||||
|
|
||||||
static int poll_select_copy_remaining(struct timespec *end_time, void __user *p,
|
|
||||||
int timeval, int ret)
|
|
||||||
{
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
if (!p)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
if (current->personality & STICKY_TIMEOUTS)
|
|
||||||
goto sticky;
|
|
||||||
|
|
||||||
/* No update for zero timeout */
|
|
||||||
if (!end_time->tv_sec && !end_time->tv_nsec)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ktime_get_ts(&ts);
|
|
||||||
ts = timespec_sub(*end_time, ts);
|
|
||||||
if (ts.tv_sec < 0)
|
|
||||||
ts.tv_sec = ts.tv_nsec = 0;
|
|
||||||
|
|
||||||
if (timeval) {
|
|
||||||
struct compat_timeval rtv;
|
|
||||||
|
|
||||||
rtv.tv_sec = ts.tv_sec;
|
|
||||||
rtv.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
|
|
||||||
|
|
||||||
if (!copy_to_user(p, &rtv, sizeof(rtv)))
|
|
||||||
return ret;
|
|
||||||
} else {
|
|
||||||
struct compat_timespec rts;
|
|
||||||
|
|
||||||
rts.tv_sec = ts.tv_sec;
|
|
||||||
rts.tv_nsec = ts.tv_nsec;
|
|
||||||
|
|
||||||
if (!copy_to_user(p, &rts, sizeof(rts)))
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* If an application puts its timeval in read-only memory, we
|
|
||||||
* don't want the Linux-specific update to the timeval to
|
|
||||||
* cause a fault after the select has completed
|
|
||||||
* successfully. However, because we're not updating the
|
|
||||||
* timeval, we can't restart the system call.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sticky:
|
|
||||||
if (ret == -ERESTARTNOHAND)
|
|
||||||
ret = -EINTR;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ooo, nasty. We need here to frob 32-bit unsigned longs to
|
|
||||||
* 64-bit unsigned longs.
|
|
||||||
*/
|
|
||||||
static
|
|
||||||
int compat_get_fd_set(unsigned long nr, compat_ulong_t __user *ufdset,
|
|
||||||
unsigned long *fdset)
|
|
||||||
{
|
|
||||||
nr = DIV_ROUND_UP(nr, __COMPAT_NFDBITS);
|
|
||||||
if (ufdset) {
|
|
||||||
unsigned long odd;
|
|
||||||
|
|
||||||
if (!access_ok(VERIFY_WRITE, ufdset, nr*sizeof(compat_ulong_t)))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
odd = nr & 1UL;
|
|
||||||
nr &= ~1UL;
|
|
||||||
while (nr) {
|
|
||||||
unsigned long h, l;
|
|
||||||
if (__get_user(l, ufdset) || __get_user(h, ufdset+1))
|
|
||||||
return -EFAULT;
|
|
||||||
ufdset += 2;
|
|
||||||
*fdset++ = h << 32 | l;
|
|
||||||
nr -= 2;
|
|
||||||
}
|
|
||||||
if (odd && __get_user(*fdset, ufdset))
|
|
||||||
return -EFAULT;
|
|
||||||
} else {
|
|
||||||
/* Tricky, must clear full unsigned long in the
|
|
||||||
* kernel fdset at the end, this makes sure that
|
|
||||||
* actually happens.
|
|
||||||
*/
|
|
||||||
memset(fdset, 0, ((nr + 1) & ~1)*sizeof(compat_ulong_t));
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
int compat_set_fd_set(unsigned long nr, compat_ulong_t __user *ufdset,
|
|
||||||
unsigned long *fdset)
|
|
||||||
{
|
|
||||||
unsigned long odd;
|
|
||||||
nr = DIV_ROUND_UP(nr, __COMPAT_NFDBITS);
|
|
||||||
|
|
||||||
if (!ufdset)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
odd = nr & 1UL;
|
|
||||||
nr &= ~1UL;
|
|
||||||
while (nr) {
|
|
||||||
unsigned long h, l;
|
|
||||||
l = *fdset++;
|
|
||||||
h = l >> 32;
|
|
||||||
if (__put_user(l, ufdset) || __put_user(h, ufdset+1))
|
|
||||||
return -EFAULT;
|
|
||||||
ufdset += 2;
|
|
||||||
nr -= 2;
|
|
||||||
}
|
|
||||||
if (odd && __put_user(*fdset, ufdset))
|
|
||||||
return -EFAULT;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is a virtual copy of sys_select from fs/select.c and probably
|
|
||||||
* should be compared to it from time to time
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We can actually return ERESTARTSYS instead of EINTR, but I'd
|
|
||||||
* like to be certain this leads to no problems. So I return
|
|
||||||
* EINTR just for safety.
|
|
||||||
*
|
|
||||||
* Update: ERESTARTSYS breaks at least the xview clock binary, so
|
|
||||||
* I'm trying ERESTARTNOHAND which restart only when you want to.
|
|
||||||
*/
|
|
||||||
int compat_core_sys_select(int n, compat_ulong_t __user *inp,
|
|
||||||
compat_ulong_t __user *outp, compat_ulong_t __user *exp,
|
|
||||||
struct timespec *end_time)
|
|
||||||
{
|
|
||||||
fd_set_bits fds;
|
|
||||||
void *bits;
|
|
||||||
int size, max_fds, ret = -EINVAL;
|
|
||||||
struct fdtable *fdt;
|
|
||||||
long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];
|
|
||||||
|
|
||||||
if (n < 0)
|
|
||||||
goto out_nofds;
|
|
||||||
|
|
||||||
/* max_fds can increase, so grab it once to avoid race */
|
|
||||||
rcu_read_lock();
|
|
||||||
fdt = files_fdtable(current->files);
|
|
||||||
max_fds = fdt->max_fds;
|
|
||||||
rcu_read_unlock();
|
|
||||||
if (n > max_fds)
|
|
||||||
n = max_fds;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We need 6 bitmaps (in/out/ex for both incoming and outgoing),
|
|
||||||
* since we used fdset we need to allocate memory in units of
|
|
||||||
* long-words.
|
|
||||||
*/
|
|
||||||
size = FDS_BYTES(n);
|
|
||||||
bits = stack_fds;
|
|
||||||
if (size > sizeof(stack_fds) / 6) {
|
|
||||||
bits = kmalloc(6 * size, GFP_KERNEL);
|
|
||||||
ret = -ENOMEM;
|
|
||||||
if (!bits)
|
|
||||||
goto out_nofds;
|
|
||||||
}
|
|
||||||
fds.in = (unsigned long *) bits;
|
|
||||||
fds.out = (unsigned long *) (bits + size);
|
|
||||||
fds.ex = (unsigned long *) (bits + 2*size);
|
|
||||||
fds.res_in = (unsigned long *) (bits + 3*size);
|
|
||||||
fds.res_out = (unsigned long *) (bits + 4*size);
|
|
||||||
fds.res_ex = (unsigned long *) (bits + 5*size);
|
|
||||||
|
|
||||||
if ((ret = compat_get_fd_set(n, inp, fds.in)) ||
|
|
||||||
(ret = compat_get_fd_set(n, outp, fds.out)) ||
|
|
||||||
(ret = compat_get_fd_set(n, exp, fds.ex)))
|
|
||||||
goto out;
|
|
||||||
zero_fd_set(n, fds.res_in);
|
|
||||||
zero_fd_set(n, fds.res_out);
|
|
||||||
zero_fd_set(n, fds.res_ex);
|
|
||||||
|
|
||||||
ret = do_select(n, &fds, end_time);
|
|
||||||
|
|
||||||
if (ret < 0)
|
|
||||||
goto out;
|
|
||||||
if (!ret) {
|
|
||||||
ret = -ERESTARTNOHAND;
|
|
||||||
if (signal_pending(current))
|
|
||||||
goto out;
|
|
||||||
ret = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compat_set_fd_set(n, inp, fds.res_in) ||
|
|
||||||
compat_set_fd_set(n, outp, fds.res_out) ||
|
|
||||||
compat_set_fd_set(n, exp, fds.res_ex))
|
|
||||||
ret = -EFAULT;
|
|
||||||
out:
|
|
||||||
if (bits != stack_fds)
|
|
||||||
kfree(bits);
|
|
||||||
out_nofds:
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
COMPAT_SYSCALL_DEFINE5(select, int, n, compat_ulong_t __user *, inp,
|
|
||||||
compat_ulong_t __user *, outp, compat_ulong_t __user *, exp,
|
|
||||||
struct compat_timeval __user *, tvp)
|
|
||||||
{
|
|
||||||
struct timespec end_time, *to = NULL;
|
|
||||||
struct compat_timeval tv;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (tvp) {
|
|
||||||
if (copy_from_user(&tv, tvp, sizeof(tv)))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
to = &end_time;
|
|
||||||
if (poll_select_set_timeout(to,
|
|
||||||
tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
|
|
||||||
(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = compat_core_sys_select(n, inp, outp, exp, to);
|
|
||||||
ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct compat_sel_arg_struct {
|
|
||||||
compat_ulong_t n;
|
|
||||||
compat_uptr_t inp;
|
|
||||||
compat_uptr_t outp;
|
|
||||||
compat_uptr_t exp;
|
|
||||||
compat_uptr_t tvp;
|
|
||||||
};
|
|
||||||
|
|
||||||
COMPAT_SYSCALL_DEFINE1(old_select, struct compat_sel_arg_struct __user *, arg)
|
|
||||||
{
|
|
||||||
struct compat_sel_arg_struct a;
|
|
||||||
|
|
||||||
if (copy_from_user(&a, arg, sizeof(a)))
|
|
||||||
return -EFAULT;
|
|
||||||
return compat_sys_select(a.n, compat_ptr(a.inp), compat_ptr(a.outp),
|
|
||||||
compat_ptr(a.exp), compat_ptr(a.tvp));
|
|
||||||
}
|
|
||||||
|
|
||||||
static long do_compat_pselect(int n, compat_ulong_t __user *inp,
|
|
||||||
compat_ulong_t __user *outp, compat_ulong_t __user *exp,
|
|
||||||
struct compat_timespec __user *tsp, compat_sigset_t __user *sigmask,
|
|
||||||
compat_size_t sigsetsize)
|
|
||||||
{
|
|
||||||
compat_sigset_t ss32;
|
|
||||||
sigset_t ksigmask, sigsaved;
|
|
||||||
struct compat_timespec ts;
|
|
||||||
struct timespec end_time, *to = NULL;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (tsp) {
|
|
||||||
if (copy_from_user(&ts, tsp, sizeof(ts)))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
to = &end_time;
|
|
||||||
if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec))
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sigmask) {
|
|
||||||
if (sigsetsize != sizeof(compat_sigset_t))
|
|
||||||
return -EINVAL;
|
|
||||||
if (copy_from_user(&ss32, sigmask, sizeof(ss32)))
|
|
||||||
return -EFAULT;
|
|
||||||
sigset_from_compat(&ksigmask, &ss32);
|
|
||||||
|
|
||||||
sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
|
|
||||||
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = compat_core_sys_select(n, inp, outp, exp, to);
|
|
||||||
ret = poll_select_copy_remaining(&end_time, tsp, 0, ret);
|
|
||||||
|
|
||||||
if (ret == -ERESTARTNOHAND) {
|
|
||||||
/*
|
|
||||||
* Don't restore the signal mask yet. Let do_signal() deliver
|
|
||||||
* the signal on the way back to userspace, before the signal
|
|
||||||
* mask is restored.
|
|
||||||
*/
|
|
||||||
if (sigmask) {
|
|
||||||
memcpy(¤t->saved_sigmask, &sigsaved,
|
|
||||||
sizeof(sigsaved));
|
|
||||||
set_restore_sigmask();
|
|
||||||
}
|
|
||||||
} else if (sigmask)
|
|
||||||
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
COMPAT_SYSCALL_DEFINE6(pselect6, int, n, compat_ulong_t __user *, inp,
|
|
||||||
compat_ulong_t __user *, outp, compat_ulong_t __user *, exp,
|
|
||||||
struct compat_timespec __user *, tsp, void __user *, sig)
|
|
||||||
{
|
|
||||||
compat_size_t sigsetsize = 0;
|
|
||||||
compat_uptr_t up = 0;
|
|
||||||
|
|
||||||
if (sig) {
|
|
||||||
if (!access_ok(VERIFY_READ, sig,
|
|
||||||
sizeof(compat_uptr_t)+sizeof(compat_size_t)) ||
|
|
||||||
__get_user(up, (compat_uptr_t __user *)sig) ||
|
|
||||||
__get_user(sigsetsize,
|
|
||||||
(compat_size_t __user *)(sig+sizeof(up))))
|
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
return do_compat_pselect(n, inp, outp, exp, tsp, compat_ptr(up),
|
|
||||||
sigsetsize);
|
|
||||||
}
|
|
||||||
|
|
||||||
COMPAT_SYSCALL_DEFINE5(ppoll, struct pollfd __user *, ufds,
|
|
||||||
unsigned int, nfds, struct compat_timespec __user *, tsp,
|
|
||||||
const compat_sigset_t __user *, sigmask, compat_size_t, sigsetsize)
|
|
||||||
{
|
|
||||||
compat_sigset_t ss32;
|
|
||||||
sigset_t ksigmask, sigsaved;
|
|
||||||
struct compat_timespec ts;
|
|
||||||
struct timespec end_time, *to = NULL;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (tsp) {
|
|
||||||
if (copy_from_user(&ts, tsp, sizeof(ts)))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
to = &end_time;
|
|
||||||
if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec))
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sigmask) {
|
|
||||||
if (sigsetsize != sizeof(compat_sigset_t))
|
|
||||||
return -EINVAL;
|
|
||||||
if (copy_from_user(&ss32, sigmask, sizeof(ss32)))
|
|
||||||
return -EFAULT;
|
|
||||||
sigset_from_compat(&ksigmask, &ss32);
|
|
||||||
|
|
||||||
sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
|
|
||||||
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = do_sys_poll(ufds, nfds, to);
|
|
||||||
|
|
||||||
/* We can restart this syscall, usually */
|
|
||||||
if (ret == -EINTR) {
|
|
||||||
/*
|
|
||||||
* Don't restore the signal mask yet. Let do_signal() deliver
|
|
||||||
* the signal on the way back to userspace, before the signal
|
|
||||||
* mask is restored.
|
|
||||||
*/
|
|
||||||
if (sigmask) {
|
|
||||||
memcpy(¤t->saved_sigmask, &sigsaved,
|
|
||||||
sizeof(sigsaved));
|
|
||||||
set_restore_sigmask();
|
|
||||||
}
|
|
||||||
ret = -ERESTARTNOHAND;
|
|
||||||
} else if (sigmask)
|
|
||||||
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
|
|
||||||
|
|
||||||
ret = poll_select_copy_remaining(&end_time, tsp, 0, ret);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_FHANDLE
|
#ifdef CONFIG_FHANDLE
|
||||||
/*
|
/*
|
||||||
* Exactly like fs/open.c:sys_open_by_handle_at(), except that it
|
* Exactly like fs/open.c:sys_open_by_handle_at(), except that it
|
||||||
|
|||||||
421
fs/select.c
421
fs/select.c
@@ -338,6 +338,53 @@ static int poll_select_copy_remaining(struct timespec64 *end_time,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scalable version of the fd_set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned long *in, *out, *ex;
|
||||||
|
unsigned long *res_in, *res_out, *res_ex;
|
||||||
|
} fd_set_bits;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* How many longwords for "nr" bits?
|
||||||
|
*/
|
||||||
|
#define FDS_BITPERLONG (8*sizeof(long))
|
||||||
|
#define FDS_LONGS(nr) (((nr)+FDS_BITPERLONG-1)/FDS_BITPERLONG)
|
||||||
|
#define FDS_BYTES(nr) (FDS_LONGS(nr)*sizeof(long))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We do a VERIFY_WRITE here even though we are only reading this time:
|
||||||
|
* we'll write to it eventually..
|
||||||
|
*
|
||||||
|
* Use "unsigned long" accesses to let user-mode fd_set's be long-aligned.
|
||||||
|
*/
|
||||||
|
static inline
|
||||||
|
int get_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
|
||||||
|
{
|
||||||
|
nr = FDS_BYTES(nr);
|
||||||
|
if (ufdset)
|
||||||
|
return copy_from_user(fdset, ufdset, nr) ? -EFAULT : 0;
|
||||||
|
|
||||||
|
memset(fdset, 0, nr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline unsigned long __must_check
|
||||||
|
set_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
|
||||||
|
{
|
||||||
|
if (ufdset)
|
||||||
|
return __copy_to_user(ufdset, fdset, FDS_BYTES(nr));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline
|
||||||
|
void zero_fd_set(unsigned long nr, unsigned long *fdset)
|
||||||
|
{
|
||||||
|
memset(fdset, 0, FDS_BYTES(nr));
|
||||||
|
}
|
||||||
|
|
||||||
#define FDS_IN(fds, n) (fds->in + n)
|
#define FDS_IN(fds, n) (fds->in + n)
|
||||||
#define FDS_OUT(fds, n) (fds->out + n)
|
#define FDS_OUT(fds, n) (fds->out + n)
|
||||||
#define FDS_EX(fds, n) (fds->ex + n)
|
#define FDS_EX(fds, n) (fds->ex + n)
|
||||||
@@ -401,7 +448,7 @@ static inline void wait_key_set(poll_table *wait, unsigned long in,
|
|||||||
wait->_key |= POLLOUT_SET;
|
wait->_key |= POLLOUT_SET;
|
||||||
}
|
}
|
||||||
|
|
||||||
int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
|
static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
|
||||||
{
|
{
|
||||||
ktime_t expire, *to = NULL;
|
ktime_t expire, *to = NULL;
|
||||||
struct poll_wqueues table;
|
struct poll_wqueues table;
|
||||||
@@ -881,7 +928,7 @@ static int do_poll(struct poll_list *list, struct poll_wqueues *wait,
|
|||||||
#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / \
|
#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / \
|
||||||
sizeof(struct pollfd))
|
sizeof(struct pollfd))
|
||||||
|
|
||||||
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
|
static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
|
||||||
struct timespec64 *end_time)
|
struct timespec64 *end_time)
|
||||||
{
|
{
|
||||||
struct poll_wqueues table;
|
struct poll_wqueues table;
|
||||||
@@ -1053,3 +1100,373 @@ SYSCALL_DEFINE5(ppoll, struct pollfd __user *, ufds, unsigned int, nfds,
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_COMPAT
|
||||||
|
#define __COMPAT_NFDBITS (8 * sizeof(compat_ulong_t))
|
||||||
|
|
||||||
|
static
|
||||||
|
int compat_poll_select_copy_remaining(struct timespec *end_time, void __user *p,
|
||||||
|
int timeval, int ret)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
if (!p)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (current->personality & STICKY_TIMEOUTS)
|
||||||
|
goto sticky;
|
||||||
|
|
||||||
|
/* No update for zero timeout */
|
||||||
|
if (!end_time->tv_sec && !end_time->tv_nsec)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ktime_get_ts(&ts);
|
||||||
|
ts = timespec_sub(*end_time, ts);
|
||||||
|
if (ts.tv_sec < 0)
|
||||||
|
ts.tv_sec = ts.tv_nsec = 0;
|
||||||
|
|
||||||
|
if (timeval) {
|
||||||
|
struct compat_timeval rtv;
|
||||||
|
|
||||||
|
rtv.tv_sec = ts.tv_sec;
|
||||||
|
rtv.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
|
||||||
|
|
||||||
|
if (!copy_to_user(p, &rtv, sizeof(rtv)))
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
struct compat_timespec rts;
|
||||||
|
|
||||||
|
rts.tv_sec = ts.tv_sec;
|
||||||
|
rts.tv_nsec = ts.tv_nsec;
|
||||||
|
|
||||||
|
if (!copy_to_user(p, &rts, sizeof(rts)))
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* If an application puts its timeval in read-only memory, we
|
||||||
|
* don't want the Linux-specific update to the timeval to
|
||||||
|
* cause a fault after the select has completed
|
||||||
|
* successfully. However, because we're not updating the
|
||||||
|
* timeval, we can't restart the system call.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sticky:
|
||||||
|
if (ret == -ERESTARTNOHAND)
|
||||||
|
ret = -EINTR;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ooo, nasty. We need here to frob 32-bit unsigned longs to
|
||||||
|
* 64-bit unsigned longs.
|
||||||
|
*/
|
||||||
|
static
|
||||||
|
int compat_get_fd_set(unsigned long nr, compat_ulong_t __user *ufdset,
|
||||||
|
unsigned long *fdset)
|
||||||
|
{
|
||||||
|
nr = DIV_ROUND_UP(nr, __COMPAT_NFDBITS);
|
||||||
|
if (ufdset) {
|
||||||
|
unsigned long odd;
|
||||||
|
|
||||||
|
if (!access_ok(VERIFY_WRITE, ufdset, nr*sizeof(compat_ulong_t)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
odd = nr & 1UL;
|
||||||
|
nr &= ~1UL;
|
||||||
|
while (nr) {
|
||||||
|
unsigned long h, l;
|
||||||
|
if (__get_user(l, ufdset) || __get_user(h, ufdset+1))
|
||||||
|
return -EFAULT;
|
||||||
|
ufdset += 2;
|
||||||
|
*fdset++ = h << 32 | l;
|
||||||
|
nr -= 2;
|
||||||
|
}
|
||||||
|
if (odd && __get_user(*fdset, ufdset))
|
||||||
|
return -EFAULT;
|
||||||
|
} else {
|
||||||
|
/* Tricky, must clear full unsigned long in the
|
||||||
|
* kernel fdset at the end, this makes sure that
|
||||||
|
* actually happens.
|
||||||
|
*/
|
||||||
|
memset(fdset, 0, ((nr + 1) & ~1)*sizeof(compat_ulong_t));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int compat_set_fd_set(unsigned long nr, compat_ulong_t __user *ufdset,
|
||||||
|
unsigned long *fdset)
|
||||||
|
{
|
||||||
|
unsigned long odd;
|
||||||
|
nr = DIV_ROUND_UP(nr, __COMPAT_NFDBITS);
|
||||||
|
|
||||||
|
if (!ufdset)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
odd = nr & 1UL;
|
||||||
|
nr &= ~1UL;
|
||||||
|
while (nr) {
|
||||||
|
unsigned long h, l;
|
||||||
|
l = *fdset++;
|
||||||
|
h = l >> 32;
|
||||||
|
if (__put_user(l, ufdset) || __put_user(h, ufdset+1))
|
||||||
|
return -EFAULT;
|
||||||
|
ufdset += 2;
|
||||||
|
nr -= 2;
|
||||||
|
}
|
||||||
|
if (odd && __put_user(*fdset, ufdset))
|
||||||
|
return -EFAULT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a virtual copy of sys_select from fs/select.c and probably
|
||||||
|
* should be compared to it from time to time
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We can actually return ERESTARTSYS instead of EINTR, but I'd
|
||||||
|
* like to be certain this leads to no problems. So I return
|
||||||
|
* EINTR just for safety.
|
||||||
|
*
|
||||||
|
* Update: ERESTARTSYS breaks at least the xview clock binary, so
|
||||||
|
* I'm trying ERESTARTNOHAND which restart only when you want to.
|
||||||
|
*/
|
||||||
|
static int compat_core_sys_select(int n, compat_ulong_t __user *inp,
|
||||||
|
compat_ulong_t __user *outp, compat_ulong_t __user *exp,
|
||||||
|
struct timespec *end_time)
|
||||||
|
{
|
||||||
|
fd_set_bits fds;
|
||||||
|
void *bits;
|
||||||
|
int size, max_fds, ret = -EINVAL;
|
||||||
|
struct fdtable *fdt;
|
||||||
|
long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];
|
||||||
|
|
||||||
|
if (n < 0)
|
||||||
|
goto out_nofds;
|
||||||
|
|
||||||
|
/* max_fds can increase, so grab it once to avoid race */
|
||||||
|
rcu_read_lock();
|
||||||
|
fdt = files_fdtable(current->files);
|
||||||
|
max_fds = fdt->max_fds;
|
||||||
|
rcu_read_unlock();
|
||||||
|
if (n > max_fds)
|
||||||
|
n = max_fds;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need 6 bitmaps (in/out/ex for both incoming and outgoing),
|
||||||
|
* since we used fdset we need to allocate memory in units of
|
||||||
|
* long-words.
|
||||||
|
*/
|
||||||
|
size = FDS_BYTES(n);
|
||||||
|
bits = stack_fds;
|
||||||
|
if (size > sizeof(stack_fds) / 6) {
|
||||||
|
bits = kmalloc(6 * size, GFP_KERNEL);
|
||||||
|
ret = -ENOMEM;
|
||||||
|
if (!bits)
|
||||||
|
goto out_nofds;
|
||||||
|
}
|
||||||
|
fds.in = (unsigned long *) bits;
|
||||||
|
fds.out = (unsigned long *) (bits + size);
|
||||||
|
fds.ex = (unsigned long *) (bits + 2*size);
|
||||||
|
fds.res_in = (unsigned long *) (bits + 3*size);
|
||||||
|
fds.res_out = (unsigned long *) (bits + 4*size);
|
||||||
|
fds.res_ex = (unsigned long *) (bits + 5*size);
|
||||||
|
|
||||||
|
if ((ret = compat_get_fd_set(n, inp, fds.in)) ||
|
||||||
|
(ret = compat_get_fd_set(n, outp, fds.out)) ||
|
||||||
|
(ret = compat_get_fd_set(n, exp, fds.ex)))
|
||||||
|
goto out;
|
||||||
|
zero_fd_set(n, fds.res_in);
|
||||||
|
zero_fd_set(n, fds.res_out);
|
||||||
|
zero_fd_set(n, fds.res_ex);
|
||||||
|
|
||||||
|
ret = do_select(n, &fds, end_time);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
if (!ret) {
|
||||||
|
ret = -ERESTARTNOHAND;
|
||||||
|
if (signal_pending(current))
|
||||||
|
goto out;
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compat_set_fd_set(n, inp, fds.res_in) ||
|
||||||
|
compat_set_fd_set(n, outp, fds.res_out) ||
|
||||||
|
compat_set_fd_set(n, exp, fds.res_ex))
|
||||||
|
ret = -EFAULT;
|
||||||
|
out:
|
||||||
|
if (bits != stack_fds)
|
||||||
|
kfree(bits);
|
||||||
|
out_nofds:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPAT_SYSCALL_DEFINE5(select, int, n, compat_ulong_t __user *, inp,
|
||||||
|
compat_ulong_t __user *, outp, compat_ulong_t __user *, exp,
|
||||||
|
struct compat_timeval __user *, tvp)
|
||||||
|
{
|
||||||
|
struct timespec end_time, *to = NULL;
|
||||||
|
struct compat_timeval tv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (tvp) {
|
||||||
|
if (copy_from_user(&tv, tvp, sizeof(tv)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
to = &end_time;
|
||||||
|
if (poll_select_set_timeout(to,
|
||||||
|
tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
|
||||||
|
(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = compat_core_sys_select(n, inp, outp, exp, to);
|
||||||
|
ret = compat_poll_select_copy_remaining(&end_time, tvp, 1, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct compat_sel_arg_struct {
|
||||||
|
compat_ulong_t n;
|
||||||
|
compat_uptr_t inp;
|
||||||
|
compat_uptr_t outp;
|
||||||
|
compat_uptr_t exp;
|
||||||
|
compat_uptr_t tvp;
|
||||||
|
};
|
||||||
|
|
||||||
|
COMPAT_SYSCALL_DEFINE1(old_select, struct compat_sel_arg_struct __user *, arg)
|
||||||
|
{
|
||||||
|
struct compat_sel_arg_struct a;
|
||||||
|
|
||||||
|
if (copy_from_user(&a, arg, sizeof(a)))
|
||||||
|
return -EFAULT;
|
||||||
|
return compat_sys_select(a.n, compat_ptr(a.inp), compat_ptr(a.outp),
|
||||||
|
compat_ptr(a.exp), compat_ptr(a.tvp));
|
||||||
|
}
|
||||||
|
|
||||||
|
static long do_compat_pselect(int n, compat_ulong_t __user *inp,
|
||||||
|
compat_ulong_t __user *outp, compat_ulong_t __user *exp,
|
||||||
|
struct compat_timespec __user *tsp, compat_sigset_t __user *sigmask,
|
||||||
|
compat_size_t sigsetsize)
|
||||||
|
{
|
||||||
|
compat_sigset_t ss32;
|
||||||
|
sigset_t ksigmask, sigsaved;
|
||||||
|
struct compat_timespec ts;
|
||||||
|
struct timespec end_time, *to = NULL;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (tsp) {
|
||||||
|
if (copy_from_user(&ts, tsp, sizeof(ts)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
to = &end_time;
|
||||||
|
if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec))
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sigmask) {
|
||||||
|
if (sigsetsize != sizeof(compat_sigset_t))
|
||||||
|
return -EINVAL;
|
||||||
|
if (copy_from_user(&ss32, sigmask, sizeof(ss32)))
|
||||||
|
return -EFAULT;
|
||||||
|
sigset_from_compat(&ksigmask, &ss32);
|
||||||
|
|
||||||
|
sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
|
||||||
|
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = compat_core_sys_select(n, inp, outp, exp, to);
|
||||||
|
ret = compat_poll_select_copy_remaining(&end_time, tsp, 0, ret);
|
||||||
|
|
||||||
|
if (ret == -ERESTARTNOHAND) {
|
||||||
|
/*
|
||||||
|
* Don't restore the signal mask yet. Let do_signal() deliver
|
||||||
|
* the signal on the way back to userspace, before the signal
|
||||||
|
* mask is restored.
|
||||||
|
*/
|
||||||
|
if (sigmask) {
|
||||||
|
memcpy(¤t->saved_sigmask, &sigsaved,
|
||||||
|
sizeof(sigsaved));
|
||||||
|
set_restore_sigmask();
|
||||||
|
}
|
||||||
|
} else if (sigmask)
|
||||||
|
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPAT_SYSCALL_DEFINE6(pselect6, int, n, compat_ulong_t __user *, inp,
|
||||||
|
compat_ulong_t __user *, outp, compat_ulong_t __user *, exp,
|
||||||
|
struct compat_timespec __user *, tsp, void __user *, sig)
|
||||||
|
{
|
||||||
|
compat_size_t sigsetsize = 0;
|
||||||
|
compat_uptr_t up = 0;
|
||||||
|
|
||||||
|
if (sig) {
|
||||||
|
if (!access_ok(VERIFY_READ, sig,
|
||||||
|
sizeof(compat_uptr_t)+sizeof(compat_size_t)) ||
|
||||||
|
__get_user(up, (compat_uptr_t __user *)sig) ||
|
||||||
|
__get_user(sigsetsize,
|
||||||
|
(compat_size_t __user *)(sig+sizeof(up))))
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
return do_compat_pselect(n, inp, outp, exp, tsp, compat_ptr(up),
|
||||||
|
sigsetsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPAT_SYSCALL_DEFINE5(ppoll, struct pollfd __user *, ufds,
|
||||||
|
unsigned int, nfds, struct compat_timespec __user *, tsp,
|
||||||
|
const compat_sigset_t __user *, sigmask, compat_size_t, sigsetsize)
|
||||||
|
{
|
||||||
|
compat_sigset_t ss32;
|
||||||
|
sigset_t ksigmask, sigsaved;
|
||||||
|
struct compat_timespec ts;
|
||||||
|
struct timespec end_time, *to = NULL;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (tsp) {
|
||||||
|
if (copy_from_user(&ts, tsp, sizeof(ts)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
to = &end_time;
|
||||||
|
if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec))
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sigmask) {
|
||||||
|
if (sigsetsize != sizeof(compat_sigset_t))
|
||||||
|
return -EINVAL;
|
||||||
|
if (copy_from_user(&ss32, sigmask, sizeof(ss32)))
|
||||||
|
return -EFAULT;
|
||||||
|
sigset_from_compat(&ksigmask, &ss32);
|
||||||
|
|
||||||
|
sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
|
||||||
|
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = do_sys_poll(ufds, nfds, to);
|
||||||
|
|
||||||
|
/* We can restart this syscall, usually */
|
||||||
|
if (ret == -EINTR) {
|
||||||
|
/*
|
||||||
|
* Don't restore the signal mask yet. Let do_signal() deliver
|
||||||
|
* the signal on the way back to userspace, before the signal
|
||||||
|
* mask is restored.
|
||||||
|
*/
|
||||||
|
if (sigmask) {
|
||||||
|
memcpy(¤t->saved_sigmask, &sigsaved,
|
||||||
|
sizeof(sigsaved));
|
||||||
|
set_restore_sigmask();
|
||||||
|
}
|
||||||
|
ret = -ERESTARTNOHAND;
|
||||||
|
} else if (sigmask)
|
||||||
|
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
|
||||||
|
|
||||||
|
ret = compat_poll_select_copy_remaining(&end_time, tsp, 0, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -98,64 +98,8 @@ extern int poll_schedule_timeout(struct poll_wqueues *pwq, int state,
|
|||||||
ktime_t *expires, unsigned long slack);
|
ktime_t *expires, unsigned long slack);
|
||||||
extern u64 select_estimate_accuracy(struct timespec64 *tv);
|
extern u64 select_estimate_accuracy(struct timespec64 *tv);
|
||||||
|
|
||||||
|
|
||||||
static inline int poll_schedule(struct poll_wqueues *pwq, int state)
|
|
||||||
{
|
|
||||||
return poll_schedule_timeout(pwq, state, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Scalable version of the fd_set.
|
|
||||||
*/
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
unsigned long *in, *out, *ex;
|
|
||||||
unsigned long *res_in, *res_out, *res_ex;
|
|
||||||
} fd_set_bits;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* How many longwords for "nr" bits?
|
|
||||||
*/
|
|
||||||
#define FDS_BITPERLONG (8*sizeof(long))
|
|
||||||
#define FDS_LONGS(nr) (((nr)+FDS_BITPERLONG-1)/FDS_BITPERLONG)
|
|
||||||
#define FDS_BYTES(nr) (FDS_LONGS(nr)*sizeof(long))
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We do a VERIFY_WRITE here even though we are only reading this time:
|
|
||||||
* we'll write to it eventually..
|
|
||||||
*
|
|
||||||
* Use "unsigned long" accesses to let user-mode fd_set's be long-aligned.
|
|
||||||
*/
|
|
||||||
static inline
|
|
||||||
int get_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
|
|
||||||
{
|
|
||||||
nr = FDS_BYTES(nr);
|
|
||||||
if (ufdset)
|
|
||||||
return copy_from_user(fdset, ufdset, nr) ? -EFAULT : 0;
|
|
||||||
|
|
||||||
memset(fdset, 0, nr);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline unsigned long __must_check
|
|
||||||
set_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
|
|
||||||
{
|
|
||||||
if (ufdset)
|
|
||||||
return __copy_to_user(ufdset, fdset, FDS_BYTES(nr));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline
|
|
||||||
void zero_fd_set(unsigned long nr, unsigned long *fdset)
|
|
||||||
{
|
|
||||||
memset(fdset, 0, FDS_BYTES(nr));
|
|
||||||
}
|
|
||||||
|
|
||||||
#define MAX_INT64_SECONDS (((s64)(~((u64)0)>>1)/HZ)-1)
|
#define MAX_INT64_SECONDS (((s64)(~((u64)0)>>1)/HZ)-1)
|
||||||
|
|
||||||
extern int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time);
|
|
||||||
extern int do_sys_poll(struct pollfd __user * ufds, unsigned int nfds,
|
|
||||||
struct timespec64 *end_time);
|
|
||||||
extern int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
|
extern int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
|
||||||
fd_set __user *exp, struct timespec64 *end_time);
|
fd_set __user *exp, struct timespec64 *end_time);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user