[RFC PATCH] fs: compat select/pselect for x32 arch

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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(&current->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




[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux