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 <krisman@xxxxxxxxxxxxx> > --- > 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