2020-07-12 04:46:31

by Gabriel Krisman Bertazi

[permalink] [raw]
Subject: [PATCH v3 0/2] Syscall user redirection

Hi,

This is the v3 of the syscall user redirection patch, applying the
suggestions from Matthew and Kees. In particular, it modifies the ABI
to allow passing a range of allowed addresses and introduces kselftests
for the feature.

RFC/v1: https://lkml.org/lkml/2020/7/8/96
v2: https://lkml.org/lkml/2020/7/9/17

Gabriel Krisman Bertazi (2):
kernel: Implement selective syscall userspace redirection
selftests: Add kselftest for syscall user dispatch

arch/Kconfig | 20 ++
arch/x86/Kconfig | 1 +
arch/x86/entry/common.c | 5 +
arch/x86/include/asm/thread_info.h | 4 +-
arch/x86/kernel/signal_compat.c | 2 +-
fs/exec.c | 2 +
include/linux/sched.h | 3 +
include/linux/syscall_user_dispatch.h | 50 ++++
include/uapi/asm-generic/siginfo.h | 3 +-
include/uapi/linux/prctl.h | 5 +
kernel/Makefile | 1 +
kernel/fork.c | 1 +
kernel/sys.c | 5 +
kernel/syscall_user_dispatch.c | 92 +++++++
tools/testing/selftests/Makefile | 1 +
.../syscall_user_dispatch/.gitignore | 1 +
.../selftests/syscall_user_dispatch/Makefile | 5 +
.../selftests/syscall_user_dispatch/config | 1 +
.../syscall_user_dispatch.c | 259 ++++++++++++++++++
19 files changed, 458 insertions(+), 3 deletions(-)
create mode 100644 include/linux/syscall_user_dispatch.h
create mode 100644 kernel/syscall_user_dispatch.c
create mode 100644 tools/testing/selftests/syscall_user_dispatch/.gitignore
create mode 100644 tools/testing/selftests/syscall_user_dispatch/Makefile
create mode 100644 tools/testing/selftests/syscall_user_dispatch/config
create mode 100644 tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c

--
2.27.0


2020-07-12 04:49:20

by Gabriel Krisman Bertazi

[permalink] [raw]
Subject: [PATCH v3 2/2] selftests: Add kselftest for syscall user dispatch

Implement functionality tests for syscall user dispatch. In order to
make the test portable, refrain from open coding syscall dispatchers and
calculating glibc memory ranges.

Signed-off-by: Gabriel Krisman Bertazi <[email protected]>
---
tools/testing/selftests/Makefile | 1 +
.../syscall_user_dispatch/.gitignore | 1 +
.../selftests/syscall_user_dispatch/Makefile | 5 +
.../selftests/syscall_user_dispatch/config | 1 +
.../syscall_user_dispatch.c | 259 ++++++++++++++++++
5 files changed, 267 insertions(+)
create mode 100644 tools/testing/selftests/syscall_user_dispatch/.gitignore
create mode 100644 tools/testing/selftests/syscall_user_dispatch/Makefile
create mode 100644 tools/testing/selftests/syscall_user_dispatch/config
create mode 100644 tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 1195bd85af38..31b07dd774a6 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -47,6 +47,7 @@ TARGETS += openat2
TARGETS += rseq
TARGETS += rtc
TARGETS += seccomp
+TARGETS += syscall_user_dispatch
TARGETS += sigaltstack
TARGETS += size
TARGETS += sparc64
diff --git a/tools/testing/selftests/syscall_user_dispatch/.gitignore b/tools/testing/selftests/syscall_user_dispatch/.gitignore
new file mode 100644
index 000000000000..fadfb304c539
--- /dev/null
+++ b/tools/testing/selftests/syscall_user_dispatch/.gitignore
@@ -0,0 +1 @@
+syscall_user_dispatch
diff --git a/tools/testing/selftests/syscall_user_dispatch/Makefile b/tools/testing/selftests/syscall_user_dispatch/Makefile
new file mode 100644
index 000000000000..4785c98d4714
--- /dev/null
+++ b/tools/testing/selftests/syscall_user_dispatch/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -Wall
+
+TEST_GEN_PROGS := syscall_user_dispatch
+include ../lib.mk
diff --git a/tools/testing/selftests/syscall_user_dispatch/config b/tools/testing/selftests/syscall_user_dispatch/config
new file mode 100644
index 000000000000..22c4dfe167ca
--- /dev/null
+++ b/tools/testing/selftests/syscall_user_dispatch/config
@@ -0,0 +1 @@
+CONFIG_SYSCALL_USER_DISPATCH=y
diff --git a/tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c b/tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c
new file mode 100644
index 000000000000..d713147863ef
--- /dev/null
+++ b/tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 Collabora Ltd.
+ *
+ * Test code for syscall user dispatch
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <sys/sysinfo.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "../kselftest_harness.h"
+
+#ifndef PR_SET_SYSCALL_USER_DISPATCH
+# define PR_SET_SYSCALL_USER_DISPATCH 59
+#endif
+
+#ifndef PR_SYS_DISPATCH_OFF
+# define PR_SYS_DISPATCH_OFF 0
+#endif
+
+#ifndef PR_SYS_DISPATCH_ON
+# define PR_SYS_DISPATCH_ON 1
+#endif
+
+#ifndef SYS_USER_DISPATCH
+# define SYS_USER_DISPATCH 2
+#endif
+
+#define SYSCALL_DISPATCH_ON(x) ((x) = 1)
+#define SYSCALL_DISPATCH_OFF(x) ((x) = 0)
+
+/* Test Summary:
+ *
+ * - dispatch_trigger_sigsys: Verify if PR_SET_SYSCALL_USER_DISPATCH is
+ * able to trigger SIGSYS on a syscall.
+ *
+ * - bad_selector: Test that a bad selector value triggers SIGSEGV.
+ *
+ * - bad_prctl_param: Test that the API correctly rejects invalid
+ * parameters on prctl
+ *
+ * - dispatch_and_return: Test that a syscall is selectively dispatched
+ * to userspace depending on the value of selector.
+ *
+ * - disable_dispatch: Test that the PR_SYS_DISPATCH_OFF correctly
+ * disables the dispatcher
+ *
+ * - direct_dispatch_range: Test that a syscall within the allowed range
+ * can bypass the dispatcher.
+ */
+
+TEST_SIGNAL(dispatch_trigger_sigsys, SIGSYS)
+{
+ char sel = 0;
+ struct sysinfo info;
+ int ret;
+
+ ret = sysinfo(&info);
+ ASSERT_EQ(0, ret);
+
+ ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, 0, &sel);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
+ }
+
+ SYSCALL_DISPATCH_ON(sel);
+
+ sysinfo(&info);
+
+ EXPECT_FALSE(true) {
+ TH_LOG("Unreachable!");
+ }
+}
+
+TEST_SIGNAL(bad_selector, SIGSEGV)
+{
+ char sel = -1;
+ long ret;
+
+ ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, 0, &sel);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
+ }
+ EXPECT_FALSE(true) {
+ TH_LOG("Unreachable!");
+ }
+}
+
+TEST(bad_prctl_param)
+{
+ char sel = 0;
+ int op;
+
+ /* Invalid op */
+ op = -1;
+ prctl(PR_SET_SYSCALL_USER_DISPATCH, op, 0, 0, &sel);
+ ASSERT_EQ(EINVAL, errno);
+
+ /* PR_SYS_DISPATCH_OFF */
+ op = PR_SYS_DISPATCH_OFF;
+
+ /* start_addr != 0 */
+ prctl(PR_SET_SYSCALL_USER_DISPATCH, op, 0x1, 0xff, 0);
+ EXPECT_EQ(EINVAL, errno);
+
+ /* end_addr != 0 */
+ prctl(PR_SET_SYSCALL_USER_DISPATCH, op, 0x0, 0xff, 0);
+ EXPECT_EQ(EINVAL, errno);
+
+ /* sel != NULL */
+ prctl(PR_SET_SYSCALL_USER_DISPATCH, op, 0x0, 0x0, &sel);
+ EXPECT_EQ(EINVAL, errno);
+
+ /* Valid parameter */
+ errno = 0;
+ syscall(SYS_prctl, PR_SET_SYSCALL_USER_DISPATCH, op, 0x0, 0x0, 0x0);
+ EXPECT_EQ(0, errno);
+
+ /* PR_SYS_DISPATCH_ON */
+ op = PR_SYS_DISPATCH_ON;
+
+ /* start_addr > end_addr */
+ syscall(SYS_prctl, PR_SET_SYSCALL_USER_DISPATCH, op, 0x1, 0x0, &sel);
+ EXPECT_EQ(EINVAL, errno);
+
+ /* Invalid selector */
+ syscall(SYS_prctl, PR_SET_SYSCALL_USER_DISPATCH, op, 0x0, 0x1, (void *) -1);
+ ASSERT_EQ(EFAULT, errno);
+}
+
+#define MAGIC_SYSCALL_1 0xff00 /* Bad Linux syscall number */
+
+/*
+ * Use global selector for handle_sigsys tests, to avoid passing
+ * selector to signal handler
+ */
+char glob_sel;
+int nr_syscalls_emulated;
+int si_code;
+
+static void handle_sigsys(int sig, siginfo_t *info, void *ucontext)
+{
+ si_code = info->si_code;
+
+ if (info->si_syscall == MAGIC_SYSCALL_1)
+ nr_syscalls_emulated++;
+
+ /* In preparation for sigreturn. */
+ SYSCALL_DISPATCH_OFF(glob_sel);
+}
+
+TEST(dispatch_and_return)
+{
+ long ret;
+ struct sigaction act;
+ sigset_t mask;
+
+ glob_sel = 0;
+ nr_syscalls_emulated = 0;
+ si_code = 0;
+
+ memset(&act, 0, sizeof(act));
+ sigemptyset(&mask);
+
+ act.sa_sigaction = handle_sigsys;
+ act.sa_flags = SA_SIGINFO;
+ act.sa_mask = mask;
+
+ ret = sigaction(SIGSYS, &act, NULL);
+ ASSERT_EQ(0, ret);
+
+ /* Make sure selector is good prior to prctl. */
+ SYSCALL_DISPATCH_OFF(glob_sel);
+
+ ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, 0, &glob_sel);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
+ }
+
+ /* MAGIC_SYSCALL_1 doesn't exist. */
+ SYSCALL_DISPATCH_OFF(glob_sel);
+ ret = syscall(MAGIC_SYSCALL_1);
+ EXPECT_EQ(-1, ret) {
+ TH_LOG("Dispatch triggered unexpectedly");
+ }
+
+ /* MAGIC_SYSCALL_1 should be emulated. */
+ nr_syscalls_emulated = 0;
+ SYSCALL_DISPATCH_ON(glob_sel);
+
+ ret = syscall(MAGIC_SYSCALL_1);
+ EXPECT_EQ(MAGIC_SYSCALL_1, ret) {
+ TH_LOG("Failed to intercept syscall");
+ }
+ EXPECT_EQ(1, nr_syscalls_emulated) {
+ TH_LOG("Failed to emulate syscall");
+ }
+ ASSERT_EQ(SYS_USER_DISPATCH, si_code) {
+ TH_LOG("Bad si_code in SIGSYS");
+ }
+}
+
+TEST(disable_dispatch)
+{
+ int ret;
+ struct sysinfo info;
+ char sel = 0;
+
+ ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, 0, &sel);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
+ }
+
+ /* MAGIC_SYSCALL_1 doesn't exist. */
+ SYSCALL_DISPATCH_OFF(glob_sel);
+
+ ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_OFF, 0, 0, 0);
+ EXPECT_EQ(0, ret) {
+ TH_LOG("Failed to unset syscall user dispatch");
+ }
+
+ /* Shouldn't have any effect... */
+ SYSCALL_DISPATCH_ON(glob_sel);
+
+ ret = syscall(SYS_sysinfo, &info);
+ EXPECT_EQ(0, ret) {
+ TH_LOG("Dispatch triggered unexpectedly");
+ }
+}
+
+TEST(direct_dispatch_range)
+{
+ int ret = 0;
+ struct sysinfo info;
+ char sel = 0;
+
+ /*
+ * Instead of calculating libc addresses; allow the entire
+ * memory map and lock the selector.
+ */
+ ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, -1L, &sel);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
+ }
+
+ SYSCALL_DISPATCH_ON(sel);
+
+ ret = syscall(SYS_sysinfo, &info);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Dispatch triggered unexpectedly");
+ }
+}
+
+TEST_HARNESS_MAIN
--
2.27.0

2020-07-12 04:50:24

by Gabriel Krisman Bertazi

[permalink] [raw]
Subject: [PATCH v3 1/2] kernel: Implement selective syscall userspace redirection

Introduce a mechanism to quickly disable/enable syscall handling for a
specific process and redirect to userspace via SIGSYS. This is useful
for processes with parts that require syscall redirection and parts that
don't, but who need to perform this boundary crossing really fast,
without paying the cost of a system call to reconfigure syscall handling
on each boundary transition. This is particularly important for Windows
games running over Wine.

The proposed interface looks like this:

prctl(PR_SET_SYSCALL_USER_DISPATCH, <op>, <start_addr>, <end_addr>, [selector])

The range [<start_addr>,<end_addr>] is a part of the process memory map
that is allowed to by-pass the redirection code and dispatch syscalls
directly, such that in fast paths a process doesn't need to disable the
trap nor the kernel has to check the selector. This is essential to
return from SIGSYS to a blocked area without triggering another SIGSYS
from rt_sigreturn.

selector is an optional pointer to a char-sized userspace memory region
that has a key switch for the mechanism. This key switch is set to
either PR_SYS_DISPATCH_ON, PR_SYS_DISPATCH_OFF to enable and disable the
redirection without calling the kernel.

The feature is meant to be set per-thread and it is disabled on
fork/clone/execv.

Internally, this doesn't add overhead to the syscall hot path, and it
requires very little per-architecture support. I avoided using seccomp,
even though it duplicates some functionality, due to previous feedback
that maybe it shouldn't mix with seccomp since it is not a security
mechanism. And obviously, this should never be considered a security
mechanism, since any part of the program can by-pass it by using the
syscall dispatcher.

For the sysinfo benchmark, which measures the overhead added to
executing a native syscall that doesn't require interception, the
overhead using only the direct dispatcher region to issue syscalls is
pretty much irrelevant. The overhead of using the selector goes around
40ns for a native (unredirected) syscall in my system, and it is (as
expected) dominated by the supervisor-mode user-address access. In
fact, with SMAP off, the overhead is consistently less than 5ns on my
test box.

Right now, it is only supported by x86_64 and x86, but it should be
easily enabled for other architectures.

An example code using this interface can be found at:
https://gitlab.collabora.com/krisman/syscall-disable-personality

Changes since v2:
(Matthew Wilcox suggestions)
- Drop __user on non-ptr type.
- Move #define closer to similar defs
- Allow a memory region that can dispatch directly
(Kees Cook suggestions)
- Improve kconfig summary line
- Move flag cleanup on execve to begin_new_exec
- Hint branch predictor in the syscall path
(Me)
- Convert selector to char

Changes since RFC:
(Kees Cook suggestions)
- Don't mention personality while explaining the feature
- Use syscall_get_nr
- Remove header guard on several places
- Convert WARN_ON to WARN_ON_ONCE
- Explicit check for state values
- Rename to syscall user dispatcher

Cc: Matthew Wilcox <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Cc: Paul Gofman <[email protected]>
Cc: Kees Cook <[email protected]>
Signed-off-by: Gabriel Krisman Bertazi <[email protected]>
---
arch/Kconfig | 20 ++++++
arch/x86/Kconfig | 1 +
arch/x86/entry/common.c | 5 ++
arch/x86/include/asm/thread_info.h | 4 +-
arch/x86/kernel/signal_compat.c | 2 +-
fs/exec.c | 2 +
include/linux/sched.h | 3 +
include/linux/syscall_user_dispatch.h | 50 +++++++++++++++
include/uapi/asm-generic/siginfo.h | 3 +-
include/uapi/linux/prctl.h | 5 ++
kernel/Makefile | 1 +
kernel/fork.c | 1 +
kernel/sys.c | 5 ++
kernel/syscall_user_dispatch.c | 92 +++++++++++++++++++++++++++
14 files changed, 191 insertions(+), 3 deletions(-)
create mode 100644 include/linux/syscall_user_dispatch.h
create mode 100644 kernel/syscall_user_dispatch.c

diff --git a/arch/Kconfig b/arch/Kconfig
index 8cc35dc556c7..0ebd971d0d8f 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -465,6 +465,26 @@ config SECCOMP_FILTER

See Documentation/userspace-api/seccomp_filter.rst for details.

+config HAVE_ARCH_SYSCALL_USER_DISPATCH
+ bool
+ help
+ An arch should select this symbol if it provides all of these things:
+ - TIF_SYSCALL_USER_DISPATCH
+ - syscall_get_arch
+ - syscall_rollback
+ - syscall_get_nr
+ - SIGSYS siginfo_t support
+
+config SYSCALL_USER_DISPATCH
+ bool "Support syscall redirection to userspace dispatcher"
+ depends on HAVE_ARCH_SYSCALL_USER_DISPATCH
+ help
+ Enable tasks to ask the kernel to redirect syscalls not
+ issued from a predefined dispatcher back to userspace,
+ depending on a userspace memory selector.
+
+ This option is useful to optimize games running over Wine.
+
config HAVE_ARCH_STACKLEAK
bool
help
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 883da0abf779..466a3a9c0708 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -149,6 +149,7 @@ config X86
select HAVE_ARCH_COMPAT_MMAP_BASES if MMU && COMPAT
select HAVE_ARCH_PREL32_RELOCATIONS
select HAVE_ARCH_SECCOMP_FILTER
+ select HAVE_ARCH_SYSCALL_USER_DISPATCH
select HAVE_ARCH_THREAD_STRUCT_WHITELIST
select HAVE_ARCH_STACKLEAK
select HAVE_ARCH_TRACEHOOK
diff --git a/arch/x86/entry/common.c b/arch/x86/entry/common.c
index bd3f14175193..6c1360a7f260 100644
--- a/arch/x86/entry/common.c
+++ b/arch/x86/entry/common.c
@@ -138,6 +138,11 @@ static long syscall_trace_enter(struct pt_regs *regs)
return -1L;
}

+ if (work & _TIF_SYSCALL_USER_DISPATCH) {
+ if (do_syscall_user_dispatch(regs))
+ return -1L;
+ }
+
#ifdef CONFIG_SECCOMP
/*
* Do seccomp after ptrace, to catch any tracer changes.
diff --git a/arch/x86/include/asm/thread_info.h b/arch/x86/include/asm/thread_info.h
index 8de8ceccb8bc..b26a9f2f0491 100644
--- a/arch/x86/include/asm/thread_info.h
+++ b/arch/x86/include/asm/thread_info.h
@@ -93,6 +93,7 @@ struct thread_info {
#define TIF_NOTSC 16 /* TSC is not accessible in userland */
#define TIF_IA32 17 /* IA32 compatibility process */
#define TIF_SLD 18 /* Restore split lock detection on context switch */
+#define TIF_SYSCALL_USER_DISPATCH 19 /* Redirect syscall for userspace handling */
#define TIF_MEMDIE 20 /* is terminating due to OOM killer */
#define TIF_POLLING_NRFLAG 21 /* idle is polling for TIF_NEED_RESCHED */
#define TIF_IO_BITMAP 22 /* uses I/O bitmap */
@@ -123,6 +124,7 @@ struct thread_info {
#define _TIF_NOTSC (1 << TIF_NOTSC)
#define _TIF_IA32 (1 << TIF_IA32)
#define _TIF_SLD (1 << TIF_SLD)
+#define _TIF_SYSCALL_USER_DISPATCH (1 << TIF_SYSCALL_USER_DISPATCH)
#define _TIF_POLLING_NRFLAG (1 << TIF_POLLING_NRFLAG)
#define _TIF_IO_BITMAP (1 << TIF_IO_BITMAP)
#define _TIF_FORCED_TF (1 << TIF_FORCED_TF)
@@ -136,7 +138,7 @@ struct thread_info {
/* Work to do before invoking the actual syscall. */
#define _TIF_WORK_SYSCALL_ENTRY \
(_TIF_SYSCALL_TRACE | _TIF_SYSCALL_EMU | _TIF_SYSCALL_AUDIT | \
- _TIF_SECCOMP | _TIF_SYSCALL_TRACEPOINT)
+ _TIF_SECCOMP | _TIF_SYSCALL_TRACEPOINT | _TIF_SYSCALL_USER_DISPATCH)

/* flags to check in __switch_to() */
#define _TIF_WORK_CTXSW_BASE \
diff --git a/arch/x86/kernel/signal_compat.c b/arch/x86/kernel/signal_compat.c
index 9ccbf0576cd0..210aecc6eab9 100644
--- a/arch/x86/kernel/signal_compat.c
+++ b/arch/x86/kernel/signal_compat.c
@@ -31,7 +31,7 @@ static inline void signal_compat_build_tests(void)
BUILD_BUG_ON(NSIGBUS != 5);
BUILD_BUG_ON(NSIGTRAP != 5);
BUILD_BUG_ON(NSIGCHLD != 6);
- BUILD_BUG_ON(NSIGSYS != 1);
+ BUILD_BUG_ON(NSIGSYS != 2);

/* This is part of the ABI and can never change in size: */
BUILD_BUG_ON(sizeof(compat_siginfo_t) != 128);
diff --git a/fs/exec.c b/fs/exec.c
index e6e8a9a70327..849f618ed790 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1386,6 +1386,8 @@ int begin_new_exec(struct linux_binprm * bprm)
flush_thread();
me->personality &= ~bprm->per_clear;

+ clear_tsk_syscall_user_dispatch(me);
+
/*
* We have to apply CLOEXEC before we change whether the process is
* dumpable (in setup_new_exec) to avoid a race with a process in userspace
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 692e327d7455..407b868146e1 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -32,6 +32,7 @@
#include <linux/posix-timers.h>
#include <linux/rseq.h>
#include <linux/kcsan.h>
+#include <linux/syscall_user_dispatch.h>

/* task_struct member predeclarations (sorted alphabetically): */
struct audit_context;
@@ -953,6 +954,8 @@ struct task_struct {
#endif
struct seccomp seccomp;

+ struct syscall_user_dispatch syscall_dispatch;
+
/* Thread group tracking: */
u64 parent_exec_id;
u64 self_exec_id;
diff --git a/include/linux/syscall_user_dispatch.h b/include/linux/syscall_user_dispatch.h
new file mode 100644
index 000000000000..a49e2de93705
--- /dev/null
+++ b/include/linux/syscall_user_dispatch.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 Collabora Ltd.
+ */
+#ifndef _SYSCALL_USER_DISPATCH_H
+#define _SYSCALL_USER_DISPATCH_H
+
+struct task_struct;
+static void clear_tsk_thread_flag(struct task_struct *tsk, int flag);
+
+#ifdef CONFIG_SYSCALL_USER_DISPATCH
+struct syscall_user_dispatch {
+ char __user *selector;
+ unsigned long dispatcher_start;
+ unsigned long dispatcher_end;
+};
+
+int do_syscall_user_dispatch(struct pt_regs *regs);
+int set_syscall_user_dispatch(int mode, unsigned long dispatcher_start,
+ unsigned long dispatcher_end,
+ char __user *selector);
+
+static inline void clear_tsk_syscall_user_dispatch(struct task_struct *tsk)
+{
+ clear_tsk_thread_flag(tsk, TIF_SYSCALL_USER_DISPATCH);
+}
+
+#else
+struct syscall_user_dispatch {};
+
+static inline int set_syscall_user_dispatch(int mode, unsigned long dispatcher_start,
+ unsigned long dispatcher_end,
+ char __user *selector)
+{
+ return -EINVAL;
+}
+
+static inline int do_syscall_user_dispatch(struct pt_regs *regs)
+{
+ WARN_ON_ONCE(1);
+ return 0;
+}
+
+static inline void clear_tsk_syscall_user_dispatch(struct task_struct *tsk)
+{
+}
+
+#endif /* CONFIG_SYSCALL_USER_DISPATCH */
+
+#endif /* _SYSCALL_USER_DISPATCH_H */
diff --git a/include/uapi/asm-generic/siginfo.h b/include/uapi/asm-generic/siginfo.h
index cb3d6c267181..37741908b846 100644
--- a/include/uapi/asm-generic/siginfo.h
+++ b/include/uapi/asm-generic/siginfo.h
@@ -284,7 +284,8 @@ typedef struct siginfo {
* SIGSYS si_codes
*/
#define SYS_SECCOMP 1 /* seccomp triggered */
-#define NSIGSYS 1
+#define SYS_USER_DISPATCH 2 /* syscall user dispatch triggered */
+#define NSIGSYS 2

/*
* SIGEMT si_codes
diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
index 07b4f8131e36..96265246383d 100644
--- a/include/uapi/linux/prctl.h
+++ b/include/uapi/linux/prctl.h
@@ -238,4 +238,9 @@ struct prctl_mm_map {
#define PR_SET_IO_FLUSHER 57
#define PR_GET_IO_FLUSHER 58

+/* Dispatch syscalls to a userspace handler */
+#define PR_SET_SYSCALL_USER_DISPATCH 59
+# define PR_SYS_DISPATCH_OFF 0
+# define PR_SYS_DISPATCH_ON 1
+
#endif /* _LINUX_PRCTL_H */
diff --git a/kernel/Makefile b/kernel/Makefile
index f3218bc5ec69..158b8c61592f 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o
obj-$(CONFIG_LOCKUP_DETECTOR) += watchdog.o
obj-$(CONFIG_HARDLOCKUP_DETECTOR_PERF) += watchdog_hld.o
obj-$(CONFIG_SECCOMP) += seccomp.o
+obj-$(CONFIG_SYSCALL_USER_DISPATCH) += syscall_user_dispatch.o
obj-$(CONFIG_RELAY) += relay.o
obj-$(CONFIG_SYSCTL) += utsname_sysctl.o
obj-$(CONFIG_TASK_DELAY_ACCT) += delayacct.o
diff --git a/kernel/fork.c b/kernel/fork.c
index 142b23645d82..c6b64a849fec 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -921,6 +921,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
clear_user_return_notifier(tsk);
clear_tsk_need_resched(tsk);
set_task_stack_end_magic(tsk);
+ clear_tsk_syscall_user_dispatch(tsk);

#ifdef CONFIG_STACKPROTECTOR
tsk->stack_canary = get_random_canary();
diff --git a/kernel/sys.c b/kernel/sys.c
index 00a96746e28a..d85880873c92 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -42,6 +42,7 @@
#include <linux/syscore_ops.h>
#include <linux/version.h>
#include <linux/ctype.h>
+#include <linux/syscall_user_dispatch.h>

#include <linux/compat.h>
#include <linux/syscalls.h>
@@ -2527,6 +2528,10 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,

error = (current->flags & PR_IO_FLUSHER) == PR_IO_FLUSHER;
break;
+ case PR_SET_SYSCALL_USER_DISPATCH:
+ error = set_syscall_user_dispatch((int) arg2, arg3, arg4,
+ (char __user *) arg5);
+ break;
default:
error = -EINVAL;
break;
diff --git a/kernel/syscall_user_dispatch.c b/kernel/syscall_user_dispatch.c
new file mode 100644
index 000000000000..39ee29c2b91f
--- /dev/null
+++ b/kernel/syscall_user_dispatch.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Collabora Ltd.
+ */
+#include <linux/sched.h>
+#include <linux/prctl.h>
+#include <linux/syscall_user_dispatch.h>
+#include <linux/uaccess.h>
+#include <linux/signal.h>
+
+#include <asm/syscall.h>
+
+#include <linux/sched/signal.h>
+#include <linux/sched/task_stack.h>
+
+static void trigger_sigsys(struct pt_regs *regs)
+{
+ struct kernel_siginfo info;
+
+ clear_siginfo(&info);
+ info.si_signo = SIGSYS;
+ info.si_code = SYS_USER_DISPATCH;
+ info.si_call_addr = (void __user *)KSTK_EIP(current);
+ info.si_errno = 0;
+ info.si_arch = syscall_get_arch(current);
+ info.si_syscall = syscall_get_nr(current, regs);
+
+ force_sig_info(&info);
+}
+
+int do_syscall_user_dispatch(struct pt_regs *regs)
+{
+ struct syscall_user_dispatch *sd = &current->syscall_dispatch;
+ unsigned long ip = instruction_pointer(regs);
+ char state;
+
+ if (likely(ip >= sd->dispatcher_start && ip <= sd->dispatcher_end))
+ return 0;
+
+ if (likely(sd->selector)) {
+ if (unlikely(__get_user(state, sd->selector)))
+ do_exit(SIGSEGV);
+
+ if (likely(state == 0))
+ return 0;
+
+ if (state != 1)
+ do_exit(SIGSEGV);
+ }
+
+ syscall_rollback(current, regs);
+ trigger_sigsys(regs);
+
+ return 1;
+}
+
+int set_syscall_user_dispatch(int mode, unsigned long dispatcher_start,
+ unsigned long dispatcher_end, char __user *selector)
+{
+ switch (mode) {
+ case PR_SYS_DISPATCH_OFF:
+ if (dispatcher_start || dispatcher_end || selector)
+ return -EINVAL;
+ break;
+ case PR_SYS_DISPATCH_ON:
+ /*
+ * Validate the direct dispatcher region just for basic
+ * sanity. If the user is able to submit a syscall from
+ * an address, that address is obviously valid.
+ */
+ if (dispatcher_end < dispatcher_start)
+ return -EINVAL;
+
+ if (selector && !access_ok(selector, 1))
+ return -EFAULT;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ current->syscall_dispatch.selector = selector;
+ current->syscall_dispatch.dispatcher_start = dispatcher_start;
+ current->syscall_dispatch.dispatcher_end = dispatcher_end;
+
+ if (mode == PR_SYS_DISPATCH_ON)
+ set_tsk_thread_flag(current, TIF_SYSCALL_USER_DISPATCH);
+ else
+ clear_tsk_thread_flag(current, TIF_SYSCALL_USER_DISPATCH);
+
+ return 0;
+}
--
2.27.0

2020-07-14 00:17:02

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v3 1/2] kernel: Implement selective syscall userspace redirection

On Sun, Jul 12, 2020 at 12:45:15AM -0400, Gabriel Krisman Bertazi wrote:
> Introduce a mechanism to quickly disable/enable syscall handling for a
> specific process and redirect to userspace via SIGSYS. This is useful
> for processes with parts that require syscall redirection and parts that
> don't, but who need to perform this boundary crossing really fast,
> without paying the cost of a system call to reconfigure syscall handling
> on each boundary transition. This is particularly important for Windows
> games running over Wine.
>
> The proposed interface looks like this:
>
> prctl(PR_SET_SYSCALL_USER_DISPATCH, <op>, <start_addr>, <end_addr>, [selector])
>
> The range [<start_addr>,<end_addr>] is a part of the process memory map
> that is allowed to by-pass the redirection code and dispatch syscalls
> directly, such that in fast paths a process doesn't need to disable the
> trap nor the kernel has to check the selector. This is essential to
> return from SIGSYS to a blocked area without triggering another SIGSYS
> from rt_sigreturn.
>
> selector is an optional pointer to a char-sized userspace memory region
> that has a key switch for the mechanism. This key switch is set to
> either PR_SYS_DISPATCH_ON, PR_SYS_DISPATCH_OFF to enable and disable the
> redirection without calling the kernel.
>
> The feature is meant to be set per-thread and it is disabled on
> fork/clone/execv.
>
> Internally, this doesn't add overhead to the syscall hot path, and it
> requires very little per-architecture support. I avoided using seccomp,
> even though it duplicates some functionality, due to previous feedback
> that maybe it shouldn't mix with seccomp since it is not a security
> mechanism. And obviously, this should never be considered a security
> mechanism, since any part of the program can by-pass it by using the
> syscall dispatcher.
>
> For the sysinfo benchmark, which measures the overhead added to
> executing a native syscall that doesn't require interception, the
> overhead using only the direct dispatcher region to issue syscalls is
> pretty much irrelevant. The overhead of using the selector goes around
> 40ns for a native (unredirected) syscall in my system, and it is (as
> expected) dominated by the supervisor-mode user-address access. In
> fact, with SMAP off, the overhead is consistently less than 5ns on my
> test box.
>
> Right now, it is only supported by x86_64 and x86, but it should be
> easily enabled for other architectures.
>
> An example code using this interface can be found at:
> https://gitlab.collabora.com/krisman/syscall-disable-personality
>
> Changes since v2:
> (Matthew Wilcox suggestions)
> - Drop __user on non-ptr type.
> - Move #define closer to similar defs
> - Allow a memory region that can dispatch directly
> (Kees Cook suggestions)
> - Improve kconfig summary line
> - Move flag cleanup on execve to begin_new_exec
> - Hint branch predictor in the syscall path
> (Me)
> - Convert selector to char
>
> Changes since RFC:
> (Kees Cook suggestions)
> - Don't mention personality while explaining the feature
> - Use syscall_get_nr
> - Remove header guard on several places
> - Convert WARN_ON to WARN_ON_ONCE
> - Explicit check for state values
> - Rename to syscall user dispatcher
>
> Cc: Matthew Wilcox <[email protected]>
> Cc: Andy Lutomirski <[email protected]>
> Cc: Paul Gofman <[email protected]>
> Cc: Kees Cook <[email protected]>
> Signed-off-by: Gabriel Krisman Bertazi <[email protected]>

I think this looks great. :)

Reviewed-by: Kees Cook <[email protected]>

Any other folks able to look through it?

--
Kees Cook

2020-07-14 00:24:29

by Kees Cook

[permalink] [raw]
Subject: Re: [PATCH v3 2/2] selftests: Add kselftest for syscall user dispatch

On Sun, Jul 12, 2020 at 12:45:16AM -0400, Gabriel Krisman Bertazi wrote:
> Implement functionality tests for syscall user dispatch. In order to
> make the test portable, refrain from open coding syscall dispatchers and
> calculating glibc memory ranges.
>
> Signed-off-by: Gabriel Krisman Bertazi <[email protected]>
> ---
> tools/testing/selftests/Makefile | 1 +
> .../syscall_user_dispatch/.gitignore | 1 +
> .../selftests/syscall_user_dispatch/Makefile | 5 +
> .../selftests/syscall_user_dispatch/config | 1 +
> .../syscall_user_dispatch.c | 259 ++++++++++++++++++
> 5 files changed, 267 insertions(+)
> create mode 100644 tools/testing/selftests/syscall_user_dispatch/.gitignore
> create mode 100644 tools/testing/selftests/syscall_user_dispatch/Makefile
> create mode 100644 tools/testing/selftests/syscall_user_dispatch/config
> create mode 100644 tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c
>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 1195bd85af38..31b07dd774a6 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -47,6 +47,7 @@ TARGETS += openat2
> TARGETS += rseq
> TARGETS += rtc
> TARGETS += seccomp
> +TARGETS += syscall_user_dispatch
> TARGETS += sigaltstack
> TARGETS += size
> TARGETS += sparc64

nit: moar alphabetical! :)

> diff --git a/tools/testing/selftests/syscall_user_dispatch/.gitignore b/tools/testing/selftests/syscall_user_dispatch/.gitignore
> new file mode 100644
> index 000000000000..fadfb304c539
> --- /dev/null
> +++ b/tools/testing/selftests/syscall_user_dispatch/.gitignore
> @@ -0,0 +1 @@
> +syscall_user_dispatch

nit: this needs as the first line:

# SPDX-License-Identifier: GPL-2.0-only

> diff --git a/tools/testing/selftests/syscall_user_dispatch/Makefile b/tools/testing/selftests/syscall_user_dispatch/Makefile
> new file mode 100644
> index 000000000000..4785c98d4714
> --- /dev/null
> +++ b/tools/testing/selftests/syscall_user_dispatch/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0
> +CFLAGS += -Wall
> +
> +TEST_GEN_PROGS := syscall_user_dispatch
> +include ../lib.mk
> diff --git a/tools/testing/selftests/syscall_user_dispatch/config b/tools/testing/selftests/syscall_user_dispatch/config
> new file mode 100644
> index 000000000000..22c4dfe167ca
> --- /dev/null
> +++ b/tools/testing/selftests/syscall_user_dispatch/config
> @@ -0,0 +1 @@
> +CONFIG_SYSCALL_USER_DISPATCH=y
> diff --git a/tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c b/tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c
> new file mode 100644
> index 000000000000..d713147863ef
> --- /dev/null
> +++ b/tools/testing/selftests/syscall_user_dispatch/syscall_user_dispatch.c
> @@ -0,0 +1,259 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2020 Collabora Ltd.
> + *
> + * Test code for syscall user dispatch
> + */
> +
> +#define _GNU_SOURCE
> +#include <sys/types.h>
> +#include <sys/prctl.h>
> +#include <sys/syscall.h>
> +#include <sys/sysinfo.h>
> +#include <signal.h>
> +#include <errno.h>
> +
> +#include "../kselftest_harness.h"
> +
> +#ifndef PR_SET_SYSCALL_USER_DISPATCH

style nit: I usually use the initial ifndef to wrap all those related to
it. i.e.:

#ifndef PR_SET_SYSCALL_USER_DISPATCH
# define PR_SET_SYSCALL_USER_DISPATCH 59
# define PR_SYS_DISPATCH_OFF 0
# define PR_SYS_DISPATCH_ON 1
...
#endif

But either way is fine.

> +# define PR_SET_SYSCALL_USER_DISPATCH 59
> +#endif
> +
> +#ifndef PR_SYS_DISPATCH_OFF
> +# define PR_SYS_DISPATCH_OFF 0
> +#endif
> +
> +#ifndef PR_SYS_DISPATCH_ON
> +# define PR_SYS_DISPATCH_ON 1
> +#endif
> +
> +#ifndef SYS_USER_DISPATCH
> +# define SYS_USER_DISPATCH 2
> +#endif
> +
> +#define SYSCALL_DISPATCH_ON(x) ((x) = 1)
> +#define SYSCALL_DISPATCH_OFF(x) ((x) = 0)
> +
> +/* Test Summary:
> + *
> + * - dispatch_trigger_sigsys: Verify if PR_SET_SYSCALL_USER_DISPATCH is
> + * able to trigger SIGSYS on a syscall.
> + *
> + * - bad_selector: Test that a bad selector value triggers SIGSEGV.
> + *
> + * - bad_prctl_param: Test that the API correctly rejects invalid
> + * parameters on prctl
> + *
> + * - dispatch_and_return: Test that a syscall is selectively dispatched
> + * to userspace depending on the value of selector.
> + *
> + * - disable_dispatch: Test that the PR_SYS_DISPATCH_OFF correctly
> + * disables the dispatcher
> + *
> + * - direct_dispatch_range: Test that a syscall within the allowed range
> + * can bypass the dispatcher.
> + */
> +
> +TEST_SIGNAL(dispatch_trigger_sigsys, SIGSYS)
> +{
> + char sel = 0;
> + struct sysinfo info;
> + int ret;
> +
> + ret = sysinfo(&info);
> + ASSERT_EQ(0, ret);
> +
> + ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, 0, &sel);
> + ASSERT_EQ(0, ret) {
> + TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
> + }
> +
> + SYSCALL_DISPATCH_ON(sel);
> +
> + sysinfo(&info);
> +
> + EXPECT_FALSE(true) {
> + TH_LOG("Unreachable!");
> + }
> +}
> +
> +TEST_SIGNAL(bad_selector, SIGSEGV)
> +{
> + char sel = -1;
> + long ret;
> +
> + ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, 0, &sel);
> + ASSERT_EQ(0, ret) {
> + TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
> + }
> + EXPECT_FALSE(true) {
> + TH_LOG("Unreachable!");
> + }
> +}
> +
> +TEST(bad_prctl_param)
> +{
> + char sel = 0;
> + int op;
> +
> + /* Invalid op */
> + op = -1;
> + prctl(PR_SET_SYSCALL_USER_DISPATCH, op, 0, 0, &sel);
> + ASSERT_EQ(EINVAL, errno);
> +
> + /* PR_SYS_DISPATCH_OFF */
> + op = PR_SYS_DISPATCH_OFF;
> +
> + /* start_addr != 0 */
> + prctl(PR_SET_SYSCALL_USER_DISPATCH, op, 0x1, 0xff, 0);
> + EXPECT_EQ(EINVAL, errno);
> +
> + /* end_addr != 0 */
> + prctl(PR_SET_SYSCALL_USER_DISPATCH, op, 0x0, 0xff, 0);
> + EXPECT_EQ(EINVAL, errno);
> +
> + /* sel != NULL */
> + prctl(PR_SET_SYSCALL_USER_DISPATCH, op, 0x0, 0x0, &sel);
> + EXPECT_EQ(EINVAL, errno);

I think here would be a good place to test PR_GET_... too?

> +
> + /* Valid parameter */
> + errno = 0;
> + syscall(SYS_prctl, PR_SET_SYSCALL_USER_DISPATCH, op, 0x0, 0x0, 0x0);
> + EXPECT_EQ(0, errno);
> +
> + /* PR_SYS_DISPATCH_ON */
> + op = PR_SYS_DISPATCH_ON;
> +
> + /* start_addr > end_addr */
> + syscall(SYS_prctl, PR_SET_SYSCALL_USER_DISPATCH, op, 0x1, 0x0, &sel);
> + EXPECT_EQ(EINVAL, errno);
> +
> + /* Invalid selector */
> + syscall(SYS_prctl, PR_SET_SYSCALL_USER_DISPATCH, op, 0x0, 0x1, (void *) -1);
> + ASSERT_EQ(EFAULT, errno);
> +}
> +
> +#define MAGIC_SYSCALL_1 0xff00 /* Bad Linux syscall number */

Some archs do weird things with syscalls. Maybe use __NR_syscalls + 1?
For x86, this should be fine, though.

> +
> +/*
> + * Use global selector for handle_sigsys tests, to avoid passing
> + * selector to signal handler
> + */
> +char glob_sel;
> +int nr_syscalls_emulated;
> +int si_code;
> +
> +static void handle_sigsys(int sig, siginfo_t *info, void *ucontext)
> +{
> + si_code = info->si_code;
> +
> + if (info->si_syscall == MAGIC_SYSCALL_1)
> + nr_syscalls_emulated++;
> +
> + /* In preparation for sigreturn. */
> + SYSCALL_DISPATCH_OFF(glob_sel);
> +}
> +
> +TEST(dispatch_and_return)
> +{
> + long ret;
> + struct sigaction act;
> + sigset_t mask;
> +
> + glob_sel = 0;
> + nr_syscalls_emulated = 0;
> + si_code = 0;
> +
> + memset(&act, 0, sizeof(act));
> + sigemptyset(&mask);
> +
> + act.sa_sigaction = handle_sigsys;
> + act.sa_flags = SA_SIGINFO;
> + act.sa_mask = mask;
> +
> + ret = sigaction(SIGSYS, &act, NULL);
> + ASSERT_EQ(0, ret);
> +
> + /* Make sure selector is good prior to prctl. */
> + SYSCALL_DISPATCH_OFF(glob_sel);
> +
> + ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, 0, &glob_sel);
> + ASSERT_EQ(0, ret) {
> + TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
> + }
> +
> + /* MAGIC_SYSCALL_1 doesn't exist. */
> + SYSCALL_DISPATCH_OFF(glob_sel);
> + ret = syscall(MAGIC_SYSCALL_1);
> + EXPECT_EQ(-1, ret) {
> + TH_LOG("Dispatch triggered unexpectedly");
> + }
> +
> + /* MAGIC_SYSCALL_1 should be emulated. */
> + nr_syscalls_emulated = 0;
> + SYSCALL_DISPATCH_ON(glob_sel);
> +
> + ret = syscall(MAGIC_SYSCALL_1);
> + EXPECT_EQ(MAGIC_SYSCALL_1, ret) {
> + TH_LOG("Failed to intercept syscall");
> + }
> + EXPECT_EQ(1, nr_syscalls_emulated) {
> + TH_LOG("Failed to emulate syscall");
> + }
> + ASSERT_EQ(SYS_USER_DISPATCH, si_code) {
> + TH_LOG("Bad si_code in SIGSYS");
> + }
> +}
> +
> +TEST(disable_dispatch)
> +{
> + int ret;
> + struct sysinfo info;
> + char sel = 0;
> +
> + ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, 0, &sel);
> + ASSERT_EQ(0, ret) {
> + TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
> + }
> +
> + /* MAGIC_SYSCALL_1 doesn't exist. */
> + SYSCALL_DISPATCH_OFF(glob_sel);
> +
> + ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_OFF, 0, 0, 0);
> + EXPECT_EQ(0, ret) {
> + TH_LOG("Failed to unset syscall user dispatch");
> + }
> +
> + /* Shouldn't have any effect... */
> + SYSCALL_DISPATCH_ON(glob_sel);
> +
> + ret = syscall(SYS_sysinfo, &info);
> + EXPECT_EQ(0, ret) {
> + TH_LOG("Dispatch triggered unexpectedly");
> + }
> +}
> +
> +TEST(direct_dispatch_range)
> +{
> + int ret = 0;
> + struct sysinfo info;
> + char sel = 0;
> +
> + /*
> + * Instead of calculating libc addresses; allow the entire
> + * memory map and lock the selector.
> + */
> + ret = prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON, 0, -1L, &sel);
> + ASSERT_EQ(0, ret) {
> + TH_LOG("Kernel does not support CONFIG_SYSCALL_USER_DISPATCH");
> + }
> +
> + SYSCALL_DISPATCH_ON(sel);
> +
> + ret = syscall(SYS_sysinfo, &info);
> + ASSERT_EQ(0, ret) {
> + TH_LOG("Dispatch triggered unexpectedly");
> + }
> +}
> +
> +TEST_HARNESS_MAIN

Yay tests! :) Thank you! :)

--
Kees Cook