The definition of fd_set in X32 ABI user-space uses a 32-bit base data type for the fd array while the kernel uses a 64-bit base data type. For applications using the glibc implementation of select(2)/pselect(2), the size of fd_set is an integer multiple of both base types, so there is no issue. For applications using fd_set sizes different from the glibc default size, we can have an overrun of the user-space fd_set buffer when the user-space buffer size is an odd multiple of 4 bytes (e.g. user-space can pass a 12-byte fd_set to the kernel and the kernel will copy 16 bytes to user-space before returning from select/pselect system calls). OpenSSH is one example of an application using fd_set sizes different from the default. Since these system calls need 32-bit fd_set definitions and 64-bit struct timeval, struct timespec, and sigset_t, we cannot use the existing compat functions. Add new x32-specific compat implementations for these system calls. Note that existing versions of glibc will continue to use the x86_64 select/pselect entry point, so backwards ABI compatibility should be preserved. Reproducer (compiled with gcc -mx32): int test(int nfds) { fd_set *fds; struct timeval tv = {0, 0}; printf("Calling select with nfds = %d\n", nfds); fds = calloc(howmany(nfds, __NFDBITS), sizeof(__fd_mask)); select(nfds, fds, NULL, NULL, &tv); free(fds); printf("Success\n"); return 0; } int main(int argc, char **argv) { mcheck_pedantic(NULL); test(64); test(65); return 0; } /* Expected output without this patch: * * Calling select with nfds = 64 * Success * Calling select with nfds = 65 * memory clobbered past end of allocated block * Aborted (core dumped) */ Signed-off-by: Lance Richardson <lance.richardson.net@xxxxxxxxx> --- Some questions: - Are compat_sys_x32_select/compat_sys_x32_pselect6 OK names? - This code is (afaik) only applicable to a single arch, is it appropriate for a common file? - Are there other lists/individuals this should be copied to? arch/x86/entry/syscalls/syscall_64.tbl | 6 ++- fs/select.c | 91 ++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl index 5aef183e2f85..de637336ed10 100644 --- a/arch/x86/entry/syscalls/syscall_64.tbl +++ b/arch/x86/entry/syscalls/syscall_64.tbl @@ -29,7 +29,7 @@ 20 64 writev sys_writev 21 common access sys_access 22 common pipe sys_pipe -23 common select sys_select +23 64 select sys_select 24 common sched_yield sys_sched_yield 25 common mremap sys_mremap 26 common msync sys_msync @@ -276,7 +276,7 @@ 267 common readlinkat sys_readlinkat 268 common fchmodat sys_fchmodat 269 common faccessat sys_faccessat -270 common pselect6 sys_pselect6 +270 64 pselect6 sys_pselect6 271 common ppoll sys_ppoll 272 common unshare sys_unshare 273 64 set_robust_list sys_set_robust_list @@ -380,3 +380,5 @@ 545 x32 execveat compat_sys_execveat/ptregs 546 x32 preadv2 compat_sys_preadv64v2 547 x32 pwritev2 compat_sys_pwritev64v2 +548 x32 select compat_sys_x32_select +549 x32 pselect6 compat_sys_x32_pselect6 diff --git a/fs/select.c b/fs/select.c index b6c36254028a..01b0a879835b 100644 --- a/fs/select.c +++ b/fs/select.c @@ -1284,6 +1284,30 @@ COMPAT_SYSCALL_DEFINE5(select, int, n, compat_ulong_t __user *, inp, return ret; } +COMPAT_SYSCALL_DEFINE5(x32_select, int, n, compat_ulong_t __user *, inp, + compat_ulong_t __user *, outp, compat_ulong_t __user *, exp, + struct timeval __user *, tvp) +{ + struct timespec64 end_time, *to = NULL; + struct 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; @@ -1369,6 +1393,73 @@ COMPAT_SYSCALL_DEFINE6(pselect6, int, n, compat_ulong_t __user *, inp, sigsetsize); } +static long do_compat_x32_pselect(int n, compat_ulong_t __user *inp, + compat_ulong_t __user *outp, compat_ulong_t __user *exp, + struct timespec __user *tsp, sigset_t __user *sigmask, + size_t sigsetsize) +{ + sigset_t ksigmask, sigsaved; + struct timespec64 ts, end_time, *to = NULL; + int ret; + + if (tsp) { + if (get_timespec64(&ts, tsp)) + return -EFAULT; + + to = &end_time; + if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec)) + return -EINVAL; + } + + if (sigmask) { + /* XXX: Don't preclude handling different sized sigset_t's. */ + if (sigsetsize != sizeof(sigset_t)) + return -EINVAL; + if (copy_from_user(&ksigmask, sigmask, sizeof(ksigmask))) + return -EFAULT; + + 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(x32_pselect6, int, n, compat_ulong_t __user *, inp, + compat_ulong_t __user *, outp, compat_ulong_t __user *, exp, + struct timespec __user *, tsp, void __user *, sig) +{ + size_t sigsetsize = 0; + sigset_t __user *up = NULL; + + if (sig) { + if (!access_ok(VERIFY_READ, sig, sizeof(void *)+sizeof(size_t)) + || __get_user(up, (sigset_t __user * __user *)sig) + || __get_user(sigsetsize, + (size_t __user *)(sig+sizeof(void *)))) + return -EFAULT; + } + return do_compat_x32_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) -- 2.14.1