All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] selftests/x86: Add test_vsyscall
@ 2018-01-12  1:16 Andy Lutomirski
  2018-01-12  4:12 ` Kees Cook
                   ` (5 more replies)
  0 siblings, 6 replies; 12+ messages in thread
From: Andy Lutomirski @ 2018-01-12  1:16 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, Borislav Petkov, Kees Cook, Peter Zijlstra,
	Greg Kroah-Hartman, Hugh Dickins, Andy Lutomirski, stable

This tests that the vsyscall entries do what they're expected to do.
It also confirms that attempts to read the vsyscall page behave as
expected.

If changes are made to the vsyscall code or its memory map handling,
running this test in all three of vsyscall=none, vsyscall=emulate,
and vsyscall=native are helpful.

(Because it's easy, this also compares the vsyscall results to their
 vDSO equivalents.)

Cc: stable@vger.kernel.org
Signed-off-by: Andy Lutomirski <luto@kernel.org>
---

Note to KAISER backporters: please test this under all three
vsyscall modes.  Also, in the emulate and native modes, make sure
that test_vsyscall_64 agrees with the command line or config
option as to which mode you're in.  It's quite easy to mess up
the kernel such that native mode accidentally emulates
or vice versa.

Greg, etc: please backport this to all your Meltdown-patched
kernels.  It'll help make sure the patches didn't regress
vsyscalls.

Changes from RFC version:
 - Doesn't warn on 32-bit
 - Detects native vs emulate

tools/testing/selftests/x86/Makefile        |   2 +-
 tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
 2 files changed, 501 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/x86/test_vsyscall.c

diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 939a337128db..5d4f10ac2af2 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -7,7 +7,7 @@ include ../lib.mk
 
 TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \
 			check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test ioperm \
-			protection_keys test_vdso
+			protection_keys test_vdso test_vsyscall
 TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
 			test_FCMOV test_FCOMI test_FISTTP \
 			vdso_restorer
diff --git a/tools/testing/selftests/x86/test_vsyscall.c b/tools/testing/selftests/x86/test_vsyscall.c
new file mode 100644
index 000000000000..7a744fa7b786
--- /dev/null
+++ b/tools/testing/selftests/x86/test_vsyscall.c
@@ -0,0 +1,500 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <sys/ucontext.h>
+#include <errno.h>
+#include <err.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <setjmp.h>
+
+#ifdef __x86_64__
+# define VSYS(x) (x)
+#else
+# define VSYS(x) 0
+#endif
+
+#ifndef SYS_getcpu
+# ifdef __x86_64__
+#  define SYS_getcpu 309
+# else
+#  define SYS_getcpu 318
+# endif
+#endif
+
+static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
+		       int flags)
+{
+	struct sigaction sa;
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_sigaction = handler;
+	sa.sa_flags = SA_SIGINFO | flags;
+	sigemptyset(&sa.sa_mask);
+	if (sigaction(sig, &sa, 0))
+		err(1, "sigaction");
+}
+
+/* vsyscalls and vDSO */
+bool should_read_vsyscall = false;
+
+typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
+gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000);
+gtod_t vdso_gtod;
+
+typedef int (*vgettime_t)(clockid_t, struct timespec *);
+vgettime_t vdso_gettime;
+
+typedef long (*time_func_t)(time_t *t);
+time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400);
+time_func_t vdso_time;
+
+typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
+getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800);
+getcpu_t vdso_getcpu;
+
+static void init_vdso(void)
+{
+	void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+	if (!vdso)
+		vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+	if (!vdso) {
+		printf("[WARN]\tfailed to find vDSO\n");
+		return;
+	}
+
+	vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday");
+	if (!vdso_gtod)
+		printf("[WARN]\tfailed to find gettimeofday in vDSO\n");
+
+	vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
+	if (!vdso_gettime)
+		printf("[WARN]\tfailed to find clock_gettime in vDSO\n");
+
+	vdso_time = (time_func_t)dlsym(vdso, "__vdso_time");
+	if (!vdso_time)
+		printf("[WARN]\tfailed to find time in vDSO\n");
+
+	vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
+	if (!vdso_getcpu) {
+		/* getcpu() was never wired up in the 32-bit vDSO. */
+		printf("[%s]\tfailed to find getcpu in vDSO\n",
+		       sizeof(long) == 8 ? "WARN" : "NOTE");
+	}
+}
+
+static int init_vsys(void)
+{
+#ifdef __x86_64__
+	int nerrs = 0;
+	FILE *maps;
+	char line[128];
+	bool found = false;
+
+	maps = fopen("/proc/self/maps", "r");
+	if (!maps) {
+		printf("[WARN]\tCould not open /proc/self/maps -- assuming vsyscall is r-x\n");
+		should_read_vsyscall = true;
+		return 0;
+	}
+
+	while (fgets(line, sizeof(line), maps)) {
+		char r, x;
+		void *start, *end;
+		char name[128];
+		if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
+			   &start, &end, &r, &x, name) != 5)
+			continue;
+
+		if (strcmp(name, "[vsyscall]"))
+			continue;
+
+		printf("\tvsyscall map: %s", line);
+
+		if (start != (void *)0xffffffffff600000 ||
+		    end != (void *)0xffffffffff601000) {
+			printf("[FAIL]\taddress range is nonsense\n");
+			nerrs++;
+		}
+
+		printf("\tvsyscall permissions are %c-%c\n", r, x);
+		should_read_vsyscall = (r == 'r');
+		if (x != 'x') {
+			vgtod = NULL;
+			vtime = NULL;
+			vgetcpu = NULL;
+		}
+
+		found = true;
+		break;
+	}
+
+	fclose(maps);
+
+	if (!found) {
+		printf("\tno vsyscall map in /proc/self/maps\n");
+		should_read_vsyscall = false;
+		vgtod = NULL;
+		vtime = NULL;
+		vgetcpu = NULL;
+	}
+
+	return nerrs;
+#else
+	return 0;
+#endif
+}
+
+/* syscalls */
+static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
+{
+	return syscall(SYS_gettimeofday, tv, tz);
+}
+
+static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
+{
+	return syscall(SYS_clock_gettime, id, ts);
+}
+
+static inline long sys_time(time_t *t)
+{
+	return syscall(SYS_time, t);
+}
+
+static inline long sys_getcpu(unsigned * cpu, unsigned * node,
+			      void* cache)
+{
+	return syscall(SYS_getcpu, cpu, node, cache);
+}
+
+static jmp_buf jmpbuf;
+
+static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
+{
+	siglongjmp(jmpbuf, 1);
+}
+
+static double tv_diff(const struct timeval *a, const struct timeval *b)
+{
+	return (double)(a->tv_sec - b->tv_sec) +
+		(double)((int)a->tv_usec - (int)b->tv_usec) * 1e-6;
+}
+
+static int check_gtod(const struct timeval *tv_sys1,
+		      const struct timeval *tv_sys2,
+		      const struct timezone *tz_sys,
+		      const char *which,
+		      const struct timeval *tv_other,
+		      const struct timezone *tz_other)
+{
+	int nerrs = 0;
+	double d1, d2;
+
+	if (tz_other && (tz_sys->tz_minuteswest != tz_other->tz_minuteswest || tz_sys->tz_dsttime != tz_other->tz_dsttime)) {
+		printf("[FAIL] %s tz mismatch\n", which);
+		nerrs++;
+	}
+
+	d1 = tv_diff(tv_other, tv_sys1);
+	d2 = tv_diff(tv_sys2, tv_other); 
+	printf("\t%s time offsets: %lf %lf\n", which, d1, d2);
+
+	if (d1 < 0 || d2 < 0) {
+		printf("[FAIL]\t%s time was inconsistent with the syscall\n", which);
+		nerrs++;
+	} else {
+		printf("[OK]\t%s gettimeofday()'s timeval was okay\n", which);
+	}
+
+	return nerrs;
+}
+
+static int test_gtod(void)
+{
+	struct timeval tv_sys1, tv_sys2, tv_vdso, tv_vsys;
+	struct timezone tz_sys, tz_vdso, tz_vsys;
+	long ret_vdso = -1;
+	long ret_vsys = -1;
+	int nerrs = 0;
+
+	printf("[RUN]\ttest gettimeofday()\n");
+
+	if (sys_gtod(&tv_sys1, &tz_sys) != 0)
+		err(1, "syscall gettimeofday");
+	if (vdso_gtod)
+		ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso);
+	if (vgtod)
+		ret_vsys = vgtod(&tv_vsys, &tz_vsys);
+	if (sys_gtod(&tv_sys2, &tz_sys) != 0)
+		err(1, "syscall gettimeofday");
+
+	if (vdso_gtod) {
+		if (ret_vdso == 0) {
+			nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vDSO", &tv_vdso, &tz_vdso);
+		} else {
+			printf("[FAIL]\tvDSO gettimeofday() failed: %ld\n", ret_vdso);
+			nerrs++;
+		}
+	}
+
+	if (vgtod) {
+		if (ret_vsys == 0) {
+			nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys);
+		} else {
+			printf("[FAIL]\tvsys gettimeofday() failed: %ld\n", ret_vsys);
+			nerrs++;
+		}
+	}
+
+	return nerrs;
+}
+
+static int test_time(void) {
+	int nerrs = 0;
+
+	printf("[RUN]\ttest time()\n");
+	long t_sys1, t_sys2, t_vdso = 0, t_vsys = 0;
+	long t2_sys1 = -1, t2_sys2 = -1, t2_vdso = -1, t2_vsys = -1;
+	t_sys1 = sys_time(&t2_sys1);
+	if (vdso_time)
+		t_vdso = vdso_time(&t2_vdso);
+	if (vtime)
+		t_vsys = vtime(&t2_vsys);
+	t_sys2 = sys_time(&t2_sys2);
+	if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) {
+		printf("[FAIL]\tsyscall failed (ret1:%ld output1:%ld ret2:%ld output2:%ld)\n", t_sys1, t2_sys1, t_sys2, t2_sys2);
+		nerrs++;
+		return nerrs;
+	}
+
+	if (vdso_time) {
+		if (t_vdso < 0 || t_vdso != t2_vdso) {
+			printf("[FAIL]\tvDSO failed (ret:%ld output:%ld)\n", t_vdso, t2_vdso);
+			nerrs++;
+		} else if (t_vdso < t_sys1 || t_vdso > t_sys2) {
+			printf("[FAIL]\tvDSO returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vdso, t_sys2);
+			nerrs++;
+		} else {
+			printf("[OK]\tvDSO time() is okay\n");
+		}
+	}
+
+	if (vtime) {
+		if (t_vsys < 0 || t_vsys != t2_vsys) {
+			printf("[FAIL]\tvsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys);
+			nerrs++;
+		} else if (t_vsys < t_sys1 || t_vsys > t_sys2) {
+			printf("[FAIL]\tvsyscall returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vsys, t_sys2);
+			nerrs++;
+		} else {
+			printf("[OK]\tvsyscall time() is okay\n");
+		}
+	}
+
+	return nerrs;
+}
+
+static int test_getcpu(int cpu)
+{
+	int nerrs = 0;
+	long ret_sys, ret_vdso = -1, ret_vsys = -1;
+
+	printf("[RUN]\tgetcpu() on CPU %d\n", cpu);
+
+	cpu_set_t cpuset;
+	CPU_ZERO(&cpuset);
+	CPU_SET(cpu, &cpuset);
+	if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
+		printf("[SKIP]\tfailed to force CPU %d\n", cpu);
+		return nerrs;
+	}
+
+	unsigned cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys;
+	unsigned node = 0;
+	bool have_node = false;
+	ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
+	if (vdso_getcpu)
+		ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
+	if (vgetcpu)
+		ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
+
+	if (ret_sys == 0) {
+		if (cpu_sys != cpu) {
+			printf("[FAIL]\tsyscall reported CPU %hu but should be %d\n", cpu_sys, cpu);
+			nerrs++;
+		}
+
+		have_node = true;
+		node = node_sys;
+	}
+
+	if (vdso_getcpu) {
+		if (ret_vdso) {
+			printf("[FAIL]\tvDSO getcpu() failed\n");
+			nerrs++;
+		} else {
+			if (!have_node) {
+				have_node = true;
+				node = node_vdso;
+			}
+
+			if (cpu_vdso != cpu) {
+				printf("[FAIL]\tvDSO reported CPU %hu but should be %d\n", cpu_vdso, cpu);
+				nerrs++;
+			} else {
+				printf("[OK]\tvDSO reported correct CPU\n");
+			}
+
+			if (node_vdso != node) {
+				printf("[FAIL]\tvDSO reported node %hu but should be %hu\n", node_vdso, node);
+				nerrs++;
+			} else {
+				printf("[OK]\tvDSO reported correct node\n");
+			}
+		}
+	}
+
+	if (vgetcpu) {
+		if (ret_vsys) {
+			printf("[FAIL]\tvsyscall getcpu() failed\n");
+			nerrs++;
+		} else {
+			if (!have_node) {
+				have_node = true;
+				node = node_vsys;
+			}
+
+			if (cpu_vsys != cpu) {
+				printf("[FAIL]\tvsyscall reported CPU %hu but should be %d\n", cpu_vsys, cpu);
+				nerrs++;
+			} else {
+				printf("[OK]\tvsyscall reported correct CPU\n");
+			}
+
+			if (node_vsys != node) {
+				printf("[FAIL]\tvsyscall reported node %hu but should be %hu\n", node_vsys, node);
+				nerrs++;
+			} else {
+				printf("[OK]\tvsyscall reported correct node\n");
+			}
+		}
+	}
+
+	return nerrs;
+}
+
+static int test_vsys_r(void)
+{
+#ifdef __x86_64__
+	printf("[RUN]\tChecking read access to the vsyscall page\n");
+	bool can_read;
+	if (sigsetjmp(jmpbuf, 1) == 0) {
+		*(volatile int *)0xffffffffff600000;
+		can_read = true;
+	} else {
+		can_read = false;
+	}
+
+	if (can_read && !should_read_vsyscall) {
+		printf("[FAIL]\tWe have read access, but we shouldn't\n");
+		return 1;
+	} else if (!can_read && should_read_vsyscall) {
+		printf("[FAIL]\tWe don't have read access, but we should\n");
+		return 1;
+	} else {
+		printf("[OK]\tgot expected result\n");
+	}
+#endif
+
+	return 0;
+}
+
+
+#ifdef __x86_64__
+#define X86_EFLAGS_TF (1UL << 8)
+static volatile sig_atomic_t num_vsyscall_traps;
+
+static unsigned long get_eflags(void)
+{
+	unsigned long eflags;
+	asm volatile ("pushfq\n\tpopq %0" : "=rm" (eflags));
+	return eflags;
+}
+
+static void set_eflags(unsigned long eflags)
+{
+	asm volatile ("pushq %0\n\tpopfq" : : "rm" (eflags) : "flags");
+}
+
+static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
+{
+	ucontext_t *ctx = (ucontext_t *)ctx_void;
+	unsigned long ip = ctx->uc_mcontext.gregs[REG_RIP];
+
+	if (((ip ^ 0xffffffffff600000UL) & ~0xfffUL) == 0)
+		num_vsyscall_traps++;
+}
+
+static int test_native_vsyscall(void)
+{
+	time_t tmp;
+	bool is_native;
+
+	if (!vtime)
+		return 0;
+
+	printf("[RUN]\tchecking for native vsyscall\n");
+	sethandler(SIGTRAP, sigtrap, 0);
+	set_eflags(get_eflags() | X86_EFLAGS_TF);
+	vtime(&tmp);
+	set_eflags(get_eflags() & ~X86_EFLAGS_TF);
+
+	/*
+	 * If vsyscalls are emulated, we expect a single trap in the
+	 * vsyscall page -- the call instruction will trap with RIP
+	 * pointing to the entry point before emulation takes over.
+	 * In native mode, we expect two traps, since whatever code
+	 * the vsyscall page contains will be more than just a ret
+	 * instruction.
+	 */
+	is_native = (num_vsyscall_traps > 1);
+
+	printf("\tvsyscalls are %s (%d instructions in vsyscall page)\n",
+	       (is_native ? "native" : "emulated"),
+	       (int)num_vsyscall_traps);
+
+	return 0;
+}
+#endif
+
+int main(int argc, char **argv)
+{
+	int nerrs = 0;
+
+	init_vdso();
+	nerrs += init_vsys();
+
+	nerrs += test_gtod();
+	nerrs += test_time();
+	nerrs += test_getcpu(0);
+	nerrs += test_getcpu(1);
+
+	sethandler(SIGSEGV, sigsegv, 0);
+	nerrs += test_vsys_r();
+
+#ifdef __x86_64__
+	nerrs += test_native_vsyscall();
+#endif
+
+	return nerrs ? 1 : 0;
+}
-- 
2.13.6

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH] selftests/x86: Add test_vsyscall
  2018-01-12  1:16 [PATCH] selftests/x86: Add test_vsyscall Andy Lutomirski
@ 2018-01-12  4:12 ` Kees Cook
  2018-01-12  7:32 ` Greg Kroah-Hartman
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Kees Cook @ 2018-01-12  4:12 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: X86 ML, LKML, Borislav Petkov, Peter Zijlstra,
	Greg Kroah-Hartman, Hugh Dickins, # 3.4.x

On Thu, Jan 11, 2018 at 5:16 PM, Andy Lutomirski <luto@kernel.org> wrote:
> This tests that the vsyscall entries do what they're expected to do.
> It also confirms that attempts to read the vsyscall page behave as
> expected.
>
> If changes are made to the vsyscall code or its memory map handling,
> running this test in all three of vsyscall=none, vsyscall=emulate,
> and vsyscall=native are helpful.
>
> (Because it's easy, this also compares the vsyscall results to their
>  vDSO equivalents.)
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Andy Lutomirski <luto@kernel.org>

Acked-by: Kees Cook <keescook@chromium.org>
Tested-by: Kees Cook <keescook@chromium.org>

(random note: if you're crazy and built with CONFIG_COMPAT_VDSO, this
doesn't actually fail, it just kind of limps along with warnings and
"RUN" but no "OK".)

-Kees

> ---
>
> Note to KAISER backporters: please test this under all three
> vsyscall modes.  Also, in the emulate and native modes, make sure
> that test_vsyscall_64 agrees with the command line or config
> option as to which mode you're in.  It's quite easy to mess up
> the kernel such that native mode accidentally emulates
> or vice versa.
>
> Greg, etc: please backport this to all your Meltdown-patched
> kernels.  It'll help make sure the patches didn't regress
> vsyscalls.
>
> Changes from RFC version:
>  - Doesn't warn on 32-bit
>  - Detects native vs emulate
>
> tools/testing/selftests/x86/Makefile        |   2 +-
>  tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
>  2 files changed, 501 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/x86/test_vsyscall.c
>
> diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
> index 939a337128db..5d4f10ac2af2 100644
> --- a/tools/testing/selftests/x86/Makefile
> +++ b/tools/testing/selftests/x86/Makefile
> @@ -7,7 +7,7 @@ include ../lib.mk
>
>  TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \
>                         check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test ioperm \
> -                       protection_keys test_vdso
> +                       protection_keys test_vdso test_vsyscall
>  TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
>                         test_FCMOV test_FCOMI test_FISTTP \
>                         vdso_restorer
> diff --git a/tools/testing/selftests/x86/test_vsyscall.c b/tools/testing/selftests/x86/test_vsyscall.c
> new file mode 100644
> index 000000000000..7a744fa7b786
> --- /dev/null
> +++ b/tools/testing/selftests/x86/test_vsyscall.c
> @@ -0,0 +1,500 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdio.h>
> +#include <sys/time.h>
> +#include <time.h>
> +#include <stdlib.h>
> +#include <sys/syscall.h>
> +#include <unistd.h>
> +#include <dlfcn.h>
> +#include <string.h>
> +#include <inttypes.h>
> +#include <signal.h>
> +#include <sys/ucontext.h>
> +#include <errno.h>
> +#include <err.h>
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <setjmp.h>
> +
> +#ifdef __x86_64__
> +# define VSYS(x) (x)
> +#else
> +# define VSYS(x) 0
> +#endif
> +
> +#ifndef SYS_getcpu
> +# ifdef __x86_64__
> +#  define SYS_getcpu 309
> +# else
> +#  define SYS_getcpu 318
> +# endif
> +#endif
> +
> +static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
> +                      int flags)
> +{
> +       struct sigaction sa;
> +       memset(&sa, 0, sizeof(sa));
> +       sa.sa_sigaction = handler;
> +       sa.sa_flags = SA_SIGINFO | flags;
> +       sigemptyset(&sa.sa_mask);
> +       if (sigaction(sig, &sa, 0))
> +               err(1, "sigaction");
> +}
> +
> +/* vsyscalls and vDSO */
> +bool should_read_vsyscall = false;
> +
> +typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
> +gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000);
> +gtod_t vdso_gtod;
> +
> +typedef int (*vgettime_t)(clockid_t, struct timespec *);
> +vgettime_t vdso_gettime;
> +
> +typedef long (*time_func_t)(time_t *t);
> +time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400);
> +time_func_t vdso_time;
> +
> +typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
> +getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800);
> +getcpu_t vdso_getcpu;
> +
> +static void init_vdso(void)
> +{
> +       void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso)
> +               vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso) {
> +               printf("[WARN]\tfailed to find vDSO\n");
> +               return;
> +       }
> +
> +       vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday");
> +       if (!vdso_gtod)
> +               printf("[WARN]\tfailed to find gettimeofday in vDSO\n");
> +
> +       vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
> +       if (!vdso_gettime)
> +               printf("[WARN]\tfailed to find clock_gettime in vDSO\n");
> +
> +       vdso_time = (time_func_t)dlsym(vdso, "__vdso_time");
> +       if (!vdso_time)
> +               printf("[WARN]\tfailed to find time in vDSO\n");
> +
> +       vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
> +       if (!vdso_getcpu) {
> +               /* getcpu() was never wired up in the 32-bit vDSO. */
> +               printf("[%s]\tfailed to find getcpu in vDSO\n",
> +                      sizeof(long) == 8 ? "WARN" : "NOTE");
> +       }
> +}
> +
> +static int init_vsys(void)
> +{
> +#ifdef __x86_64__
> +       int nerrs = 0;
> +       FILE *maps;
> +       char line[128];
> +       bool found = false;
> +
> +       maps = fopen("/proc/self/maps", "r");
> +       if (!maps) {
> +               printf("[WARN]\tCould not open /proc/self/maps -- assuming vsyscall is r-x\n");
> +               should_read_vsyscall = true;
> +               return 0;
> +       }
> +
> +       while (fgets(line, sizeof(line), maps)) {
> +               char r, x;
> +               void *start, *end;
> +               char name[128];
> +               if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
> +                          &start, &end, &r, &x, name) != 5)
> +                       continue;
> +
> +               if (strcmp(name, "[vsyscall]"))
> +                       continue;
> +
> +               printf("\tvsyscall map: %s", line);
> +
> +               if (start != (void *)0xffffffffff600000 ||
> +                   end != (void *)0xffffffffff601000) {
> +                       printf("[FAIL]\taddress range is nonsense\n");
> +                       nerrs++;
> +               }
> +
> +               printf("\tvsyscall permissions are %c-%c\n", r, x);
> +               should_read_vsyscall = (r == 'r');
> +               if (x != 'x') {
> +                       vgtod = NULL;
> +                       vtime = NULL;
> +                       vgetcpu = NULL;
> +               }
> +
> +               found = true;
> +               break;
> +       }
> +
> +       fclose(maps);
> +
> +       if (!found) {
> +               printf("\tno vsyscall map in /proc/self/maps\n");
> +               should_read_vsyscall = false;
> +               vgtod = NULL;
> +               vtime = NULL;
> +               vgetcpu = NULL;
> +       }
> +
> +       return nerrs;
> +#else
> +       return 0;
> +#endif
> +}
> +
> +/* syscalls */
> +static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
> +{
> +       return syscall(SYS_gettimeofday, tv, tz);
> +}
> +
> +static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
> +{
> +       return syscall(SYS_clock_gettime, id, ts);
> +}
> +
> +static inline long sys_time(time_t *t)
> +{
> +       return syscall(SYS_time, t);
> +}
> +
> +static inline long sys_getcpu(unsigned * cpu, unsigned * node,
> +                             void* cache)
> +{
> +       return syscall(SYS_getcpu, cpu, node, cache);
> +}
> +
> +static jmp_buf jmpbuf;
> +
> +static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       siglongjmp(jmpbuf, 1);
> +}
> +
> +static double tv_diff(const struct timeval *a, const struct timeval *b)
> +{
> +       return (double)(a->tv_sec - b->tv_sec) +
> +               (double)((int)a->tv_usec - (int)b->tv_usec) * 1e-6;
> +}
> +
> +static int check_gtod(const struct timeval *tv_sys1,
> +                     const struct timeval *tv_sys2,
> +                     const struct timezone *tz_sys,
> +                     const char *which,
> +                     const struct timeval *tv_other,
> +                     const struct timezone *tz_other)
> +{
> +       int nerrs = 0;
> +       double d1, d2;
> +
> +       if (tz_other && (tz_sys->tz_minuteswest != tz_other->tz_minuteswest || tz_sys->tz_dsttime != tz_other->tz_dsttime)) {
> +               printf("[FAIL] %s tz mismatch\n", which);
> +               nerrs++;
> +       }
> +
> +       d1 = tv_diff(tv_other, tv_sys1);
> +       d2 = tv_diff(tv_sys2, tv_other);
> +       printf("\t%s time offsets: %lf %lf\n", which, d1, d2);
> +
> +       if (d1 < 0 || d2 < 0) {
> +               printf("[FAIL]\t%s time was inconsistent with the syscall\n", which);
> +               nerrs++;
> +       } else {
> +               printf("[OK]\t%s gettimeofday()'s timeval was okay\n", which);
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_gtod(void)
> +{
> +       struct timeval tv_sys1, tv_sys2, tv_vdso, tv_vsys;
> +       struct timezone tz_sys, tz_vdso, tz_vsys;
> +       long ret_vdso = -1;
> +       long ret_vsys = -1;
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest gettimeofday()\n");
> +
> +       if (sys_gtod(&tv_sys1, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +       if (vdso_gtod)
> +               ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso);
> +       if (vgtod)
> +               ret_vsys = vgtod(&tv_vsys, &tz_vsys);
> +       if (sys_gtod(&tv_sys2, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +
> +       if (vdso_gtod) {
> +               if (ret_vdso == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vDSO", &tv_vdso, &tz_vdso);
> +               } else {
> +                       printf("[FAIL]\tvDSO gettimeofday() failed: %ld\n", ret_vdso);
> +                       nerrs++;
> +               }
> +       }
> +
> +       if (vgtod) {
> +               if (ret_vsys == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys);
> +               } else {
> +                       printf("[FAIL]\tvsys gettimeofday() failed: %ld\n", ret_vsys);
> +                       nerrs++;
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_time(void) {
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest time()\n");
> +       long t_sys1, t_sys2, t_vdso = 0, t_vsys = 0;
> +       long t2_sys1 = -1, t2_sys2 = -1, t2_vdso = -1, t2_vsys = -1;
> +       t_sys1 = sys_time(&t2_sys1);
> +       if (vdso_time)
> +               t_vdso = vdso_time(&t2_vdso);
> +       if (vtime)
> +               t_vsys = vtime(&t2_vsys);
> +       t_sys2 = sys_time(&t2_sys2);
> +       if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) {
> +               printf("[FAIL]\tsyscall failed (ret1:%ld output1:%ld ret2:%ld output2:%ld)\n", t_sys1, t2_sys1, t_sys2, t2_sys2);
> +               nerrs++;
> +               return nerrs;
> +       }
> +
> +       if (vdso_time) {
> +               if (t_vdso < 0 || t_vdso != t2_vdso) {
> +                       printf("[FAIL]\tvDSO failed (ret:%ld output:%ld)\n", t_vdso, t2_vdso);
> +                       nerrs++;
> +               } else if (t_vdso < t_sys1 || t_vdso > t_sys2) {
> +                       printf("[FAIL]\tvDSO returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vdso, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvDSO time() is okay\n");
> +               }
> +       }
> +
> +       if (vtime) {
> +               if (t_vsys < 0 || t_vsys != t2_vsys) {
> +                       printf("[FAIL]\tvsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys);
> +                       nerrs++;
> +               } else if (t_vsys < t_sys1 || t_vsys > t_sys2) {
> +                       printf("[FAIL]\tvsyscall returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vsys, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvsyscall time() is okay\n");
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_getcpu(int cpu)
> +{
> +       int nerrs = 0;
> +       long ret_sys, ret_vdso = -1, ret_vsys = -1;
> +
> +       printf("[RUN]\tgetcpu() on CPU %d\n", cpu);
> +
> +       cpu_set_t cpuset;
> +       CPU_ZERO(&cpuset);
> +       CPU_SET(cpu, &cpuset);
> +       if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
> +               printf("[SKIP]\tfailed to force CPU %d\n", cpu);
> +               return nerrs;
> +       }
> +
> +       unsigned cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys;
> +       unsigned node = 0;
> +       bool have_node = false;
> +       ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
> +       if (vdso_getcpu)
> +               ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
> +       if (vgetcpu)
> +               ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
> +
> +       if (ret_sys == 0) {
> +               if (cpu_sys != cpu) {
> +                       printf("[FAIL]\tsyscall reported CPU %hu but should be %d\n", cpu_sys, cpu);
> +                       nerrs++;
> +               }
> +
> +               have_node = true;
> +               node = node_sys;
> +       }
> +
> +       if (vdso_getcpu) {
> +               if (ret_vdso) {
> +                       printf("[FAIL]\tvDSO getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vdso;
> +                       }
> +
> +                       if (cpu_vdso != cpu) {
> +                               printf("[FAIL]\tvDSO reported CPU %hu but should be %d\n", cpu_vdso, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct CPU\n");
> +                       }
> +
> +                       if (node_vdso != node) {
> +                               printf("[FAIL]\tvDSO reported node %hu but should be %hu\n", node_vdso, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       if (vgetcpu) {
> +               if (ret_vsys) {
> +                       printf("[FAIL]\tvsyscall getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vsys;
> +                       }
> +
> +                       if (cpu_vsys != cpu) {
> +                               printf("[FAIL]\tvsyscall reported CPU %hu but should be %d\n", cpu_vsys, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct CPU\n");
> +                       }
> +
> +                       if (node_vsys != node) {
> +                               printf("[FAIL]\tvsyscall reported node %hu but should be %hu\n", node_vsys, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_vsys_r(void)
> +{
> +#ifdef __x86_64__
> +       printf("[RUN]\tChecking read access to the vsyscall page\n");
> +       bool can_read;
> +       if (sigsetjmp(jmpbuf, 1) == 0) {
> +               *(volatile int *)0xffffffffff600000;
> +               can_read = true;
> +       } else {
> +               can_read = false;
> +       }
> +
> +       if (can_read && !should_read_vsyscall) {
> +               printf("[FAIL]\tWe have read access, but we shouldn't\n");
> +               return 1;
> +       } else if (!can_read && should_read_vsyscall) {
> +               printf("[FAIL]\tWe don't have read access, but we should\n");
> +               return 1;
> +       } else {
> +               printf("[OK]\tgot expected result\n");
> +       }
> +#endif
> +
> +       return 0;
> +}
> +
> +
> +#ifdef __x86_64__
> +#define X86_EFLAGS_TF (1UL << 8)
> +static volatile sig_atomic_t num_vsyscall_traps;
> +
> +static unsigned long get_eflags(void)
> +{
> +       unsigned long eflags;
> +       asm volatile ("pushfq\n\tpopq %0" : "=rm" (eflags));
> +       return eflags;
> +}
> +
> +static void set_eflags(unsigned long eflags)
> +{
> +       asm volatile ("pushq %0\n\tpopfq" : : "rm" (eflags) : "flags");
> +}
> +
> +static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       ucontext_t *ctx = (ucontext_t *)ctx_void;
> +       unsigned long ip = ctx->uc_mcontext.gregs[REG_RIP];
> +
> +       if (((ip ^ 0xffffffffff600000UL) & ~0xfffUL) == 0)
> +               num_vsyscall_traps++;
> +}
> +
> +static int test_native_vsyscall(void)
> +{
> +       time_t tmp;
> +       bool is_native;
> +
> +       if (!vtime)
> +               return 0;
> +
> +       printf("[RUN]\tchecking for native vsyscall\n");
> +       sethandler(SIGTRAP, sigtrap, 0);
> +       set_eflags(get_eflags() | X86_EFLAGS_TF);
> +       vtime(&tmp);
> +       set_eflags(get_eflags() & ~X86_EFLAGS_TF);
> +
> +       /*
> +        * If vsyscalls are emulated, we expect a single trap in the
> +        * vsyscall page -- the call instruction will trap with RIP
> +        * pointing to the entry point before emulation takes over.
> +        * In native mode, we expect two traps, since whatever code
> +        * the vsyscall page contains will be more than just a ret
> +        * instruction.
> +        */
> +       is_native = (num_vsyscall_traps > 1);
> +
> +       printf("\tvsyscalls are %s (%d instructions in vsyscall page)\n",
> +              (is_native ? "native" : "emulated"),
> +              (int)num_vsyscall_traps);
> +
> +       return 0;
> +}
> +#endif
> +
> +int main(int argc, char **argv)
> +{
> +       int nerrs = 0;
> +
> +       init_vdso();
> +       nerrs += init_vsys();
> +
> +       nerrs += test_gtod();
> +       nerrs += test_time();
> +       nerrs += test_getcpu(0);
> +       nerrs += test_getcpu(1);
> +
> +       sethandler(SIGSEGV, sigsegv, 0);
> +       nerrs += test_vsys_r();
> +
> +#ifdef __x86_64__
> +       nerrs += test_native_vsyscall();
> +#endif
> +
> +       return nerrs ? 1 : 0;
> +}
> --
> 2.13.6
>



-- 
Kees Cook
Pixel Security

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] selftests/x86: Add test_vsyscall
  2018-01-12  1:16 [PATCH] selftests/x86: Add test_vsyscall Andy Lutomirski
  2018-01-12  4:12 ` Kees Cook
@ 2018-01-12  7:32 ` Greg Kroah-Hartman
  2018-01-12 12:45 ` Ingo Molnar
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Greg Kroah-Hartman @ 2018-01-12  7:32 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: x86, linux-kernel, Borislav Petkov, Kees Cook, Peter Zijlstra,
	Hugh Dickins, stable

On Thu, Jan 11, 2018 at 05:16:51PM -0800, Andy Lutomirski wrote:
> This tests that the vsyscall entries do what they're expected to do.
> It also confirms that attempts to read the vsyscall page behave as
> expected.
> 
> If changes are made to the vsyscall code or its memory map handling,
> running this test in all three of vsyscall=none, vsyscall=emulate,
> and vsyscall=native are helpful.
> 
> (Because it's easy, this also compares the vsyscall results to their
>  vDSO equivalents.)
> 
> Cc: stable@vger.kernel.org
> Signed-off-by: Andy Lutomirski <luto@kernel.org>
> ---

Tested-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

> Note to KAISER backporters: please test this under all three
> vsyscall modes.  Also, in the emulate and native modes, make sure
> that test_vsyscall_64 agrees with the command line or config
> option as to which mode you're in.  It's quite easy to mess up
> the kernel such that native mode accidentally emulates
> or vice versa.
> 
> Greg, etc: please backport this to all your Meltdown-patched
> kernels.  It'll help make sure the patches didn't regress
> vsyscalls.

I will gladly do so, thanks so much for this test.

greg k-h

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] selftests/x86: Add test_vsyscall
  2018-01-12  1:16 [PATCH] selftests/x86: Add test_vsyscall Andy Lutomirski
  2018-01-12  4:12 ` Kees Cook
  2018-01-12  7:32 ` Greg Kroah-Hartman
@ 2018-01-12 12:45 ` Ingo Molnar
  2018-01-12 12:57 ` Ingo Molnar
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Ingo Molnar @ 2018-01-12 12:45 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: x86, linux-kernel, Borislav Petkov, Kees Cook, Peter Zijlstra,
	Greg Kroah-Hartman, Hugh Dickins, stable


* Andy Lutomirski <luto@kernel.org> wrote:

> This tests that the vsyscall entries do what they're expected to do.
> It also confirms that attempts to read the vsyscall page behave as
> expected.
> 
> If changes are made to the vsyscall code or its memory map handling,
> running this test in all three of vsyscall=none, vsyscall=emulate,
> and vsyscall=native are helpful.
> 
> (Because it's easy, this also compares the vsyscall results to their
>  vDSO equivalents.)
> 
> Cc: stable@vger.kernel.org
> Signed-off-by: Andy Lutomirski <luto@kernel.org>
> ---
> 
> Note to KAISER backporters: please test this under all three
> vsyscall modes.  Also, in the emulate and native modes, make sure
> that test_vsyscall_64 agrees with the command line or config
> option as to which mode you're in.  It's quite easy to mess up
> the kernel such that native mode accidentally emulates
> or vice versa.
> 
> Greg, etc: please backport this to all your Meltdown-patched
> kernels.  It'll help make sure the patches didn't regress
> vsyscalls.

I've added this to tip:x86/pti, that way it will end up in the linear backport 
range of commits.

Thanks,

	Ingo

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] selftests/x86: Add test_vsyscall
  2018-01-12  1:16 [PATCH] selftests/x86: Add test_vsyscall Andy Lutomirski
                   ` (2 preceding siblings ...)
  2018-01-12 12:45 ` Ingo Molnar
@ 2018-01-12 12:57 ` Ingo Molnar
  2018-01-13  6:29   ` Andy Lutomirski
  2018-01-13  6:22   ` Andy Lutomirski
  2018-01-13 10:28 ` [tip:x86/pti] " tip-bot for Andy Lutomirski
  5 siblings, 1 reply; 12+ messages in thread
From: Ingo Molnar @ 2018-01-12 12:57 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: x86, linux-kernel, Borislav Petkov, Kees Cook, Peter Zijlstra,
	Greg Kroah-Hartman, Hugh Dickins, stable


* Andy Lutomirski <luto@kernel.org> wrote:

> tools/testing/selftests/x86/Makefile        |   2 +-
>  tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
>  2 files changed, 501 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/x86/test_vsyscall.c

Hm, it doesn't even build here, on Ubuntu latest (Artful):

triton:~/tip/tools/testing/selftests/x86> make test_vsyscall
gcc -O2 -g -std=gnu99 -pthread -Wall -no-pie    test_vsyscall.c   -o test_vsyscall
/tmp/ccKyelfb.o: In function `init_vdso':
/home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:68: undefined reference to `dlopen'
/home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:76: undefined reference to `dlsym'
/home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:80: undefined reference to `dlsym'
/home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:84: undefined reference to `dlsym'
/home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:88: undefined reference to `dlsym'
/home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:70: undefined reference to `dlopen'
collect2: error: ld returned 1 exit status
<builtin>: recipe for target 'test_vsyscall' failed
make: *** [test_vsyscall] Error 1

Thanks,

	Ingo

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] selftests/x86: Add test_vsyscall
  2018-01-12  1:16 [PATCH] selftests/x86: Add test_vsyscall Andy Lutomirski
  2018-01-12  4:12 ` Kees Cook
  2018-01-12  7:32 ` Greg Kroah-Hartman
@ 2018-01-13  6:22   ` Andy Lutomirski
  2018-01-12 12:57 ` Ingo Molnar
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Andy Lutomirski @ 2018-01-13  6:22 UTC (permalink / raw)
  To: Andy Lutomirski, Shuah Khan, open list:KERNEL SELFTEST FRAMEWORK
  Cc: X86 ML, LKML, Borislav Petkov, Kees Cook, Peter Zijlstra,
	Greg Kroah-Hartman, Hugh Dickins, stable

Add missing cc's.  Duh

On Thu, Jan 11, 2018 at 5:16 PM, Andy Lutomirski <luto@kernel.org> wrote:
> This tests that the vsyscall entries do what they're expected to do.
> It also confirms that attempts to read the vsyscall page behave as
> expected.
>
> If changes are made to the vsyscall code or its memory map handling,
> running this test in all three of vsyscall=none, vsyscall=emulate,
> and vsyscall=native are helpful.
>
> (Because it's easy, this also compares the vsyscall results to their
>  vDSO equivalents.)
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Andy Lutomirski <luto@kernel.org>
> ---
>
> Note to KAISER backporters: please test this under all three
> vsyscall modes.  Also, in the emulate and native modes, make sure
> that test_vsyscall_64 agrees with the command line or config
> option as to which mode you're in.  It's quite easy to mess up
> the kernel such that native mode accidentally emulates
> or vice versa.
>
> Greg, etc: please backport this to all your Meltdown-patched
> kernels.  It'll help make sure the patches didn't regress
> vsyscalls.
>
> Changes from RFC version:
>  - Doesn't warn on 32-bit
>  - Detects native vs emulate
>
> tools/testing/selftests/x86/Makefile        |   2 +-
>  tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
>  2 files changed, 501 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/x86/test_vsyscall.c
>
> diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
> index 939a337128db..5d4f10ac2af2 100644
> --- a/tools/testing/selftests/x86/Makefile
> +++ b/tools/testing/selftests/x86/Makefile
> @@ -7,7 +7,7 @@ include ../lib.mk
>
>  TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \
>                         check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test ioperm \
> -                       protection_keys test_vdso
> +                       protection_keys test_vdso test_vsyscall
>  TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
>                         test_FCMOV test_FCOMI test_FISTTP \
>                         vdso_restorer
> diff --git a/tools/testing/selftests/x86/test_vsyscall.c b/tools/testing/selftests/x86/test_vsyscall.c
> new file mode 100644
> index 000000000000..7a744fa7b786
> --- /dev/null
> +++ b/tools/testing/selftests/x86/test_vsyscall.c
> @@ -0,0 +1,500 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdio.h>
> +#include <sys/time.h>
> +#include <time.h>
> +#include <stdlib.h>
> +#include <sys/syscall.h>
> +#include <unistd.h>
> +#include <dlfcn.h>
> +#include <string.h>
> +#include <inttypes.h>
> +#include <signal.h>
> +#include <sys/ucontext.h>
> +#include <errno.h>
> +#include <err.h>
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <setjmp.h>
> +
> +#ifdef __x86_64__
> +# define VSYS(x) (x)
> +#else
> +# define VSYS(x) 0
> +#endif
> +
> +#ifndef SYS_getcpu
> +# ifdef __x86_64__
> +#  define SYS_getcpu 309
> +# else
> +#  define SYS_getcpu 318
> +# endif
> +#endif
> +
> +static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
> +                      int flags)
> +{
> +       struct sigaction sa;
> +       memset(&sa, 0, sizeof(sa));
> +       sa.sa_sigaction = handler;
> +       sa.sa_flags = SA_SIGINFO | flags;
> +       sigemptyset(&sa.sa_mask);
> +       if (sigaction(sig, &sa, 0))
> +               err(1, "sigaction");
> +}
> +
> +/* vsyscalls and vDSO */
> +bool should_read_vsyscall = false;
> +
> +typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
> +gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000);
> +gtod_t vdso_gtod;
> +
> +typedef int (*vgettime_t)(clockid_t, struct timespec *);
> +vgettime_t vdso_gettime;
> +
> +typedef long (*time_func_t)(time_t *t);
> +time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400);
> +time_func_t vdso_time;
> +
> +typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
> +getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800);
> +getcpu_t vdso_getcpu;
> +
> +static void init_vdso(void)
> +{
> +       void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso)
> +               vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso) {
> +               printf("[WARN]\tfailed to find vDSO\n");
> +               return;
> +       }
> +
> +       vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday");
> +       if (!vdso_gtod)
> +               printf("[WARN]\tfailed to find gettimeofday in vDSO\n");
> +
> +       vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
> +       if (!vdso_gettime)
> +               printf("[WARN]\tfailed to find clock_gettime in vDSO\n");
> +
> +       vdso_time = (time_func_t)dlsym(vdso, "__vdso_time");
> +       if (!vdso_time)
> +               printf("[WARN]\tfailed to find time in vDSO\n");
> +
> +       vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
> +       if (!vdso_getcpu) {
> +               /* getcpu() was never wired up in the 32-bit vDSO. */
> +               printf("[%s]\tfailed to find getcpu in vDSO\n",
> +                      sizeof(long) == 8 ? "WARN" : "NOTE");
> +       }
> +}
> +
> +static int init_vsys(void)
> +{
> +#ifdef __x86_64__
> +       int nerrs = 0;
> +       FILE *maps;
> +       char line[128];
> +       bool found = false;
> +
> +       maps = fopen("/proc/self/maps", "r");
> +       if (!maps) {
> +               printf("[WARN]\tCould not open /proc/self/maps -- assuming vsyscall is r-x\n");
> +               should_read_vsyscall = true;
> +               return 0;
> +       }
> +
> +       while (fgets(line, sizeof(line), maps)) {
> +               char r, x;
> +               void *start, *end;
> +               char name[128];
> +               if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
> +                          &start, &end, &r, &x, name) != 5)
> +                       continue;
> +
> +               if (strcmp(name, "[vsyscall]"))
> +                       continue;
> +
> +               printf("\tvsyscall map: %s", line);
> +
> +               if (start != (void *)0xffffffffff600000 ||
> +                   end != (void *)0xffffffffff601000) {
> +                       printf("[FAIL]\taddress range is nonsense\n");
> +                       nerrs++;
> +               }
> +
> +               printf("\tvsyscall permissions are %c-%c\n", r, x);
> +               should_read_vsyscall = (r == 'r');
> +               if (x != 'x') {
> +                       vgtod = NULL;
> +                       vtime = NULL;
> +                       vgetcpu = NULL;
> +               }
> +
> +               found = true;
> +               break;
> +       }
> +
> +       fclose(maps);
> +
> +       if (!found) {
> +               printf("\tno vsyscall map in /proc/self/maps\n");
> +               should_read_vsyscall = false;
> +               vgtod = NULL;
> +               vtime = NULL;
> +               vgetcpu = NULL;
> +       }
> +
> +       return nerrs;
> +#else
> +       return 0;
> +#endif
> +}
> +
> +/* syscalls */
> +static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
> +{
> +       return syscall(SYS_gettimeofday, tv, tz);
> +}
> +
> +static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
> +{
> +       return syscall(SYS_clock_gettime, id, ts);
> +}
> +
> +static inline long sys_time(time_t *t)
> +{
> +       return syscall(SYS_time, t);
> +}
> +
> +static inline long sys_getcpu(unsigned * cpu, unsigned * node,
> +                             void* cache)
> +{
> +       return syscall(SYS_getcpu, cpu, node, cache);
> +}
> +
> +static jmp_buf jmpbuf;
> +
> +static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       siglongjmp(jmpbuf, 1);
> +}
> +
> +static double tv_diff(const struct timeval *a, const struct timeval *b)
> +{
> +       return (double)(a->tv_sec - b->tv_sec) +
> +               (double)((int)a->tv_usec - (int)b->tv_usec) * 1e-6;
> +}
> +
> +static int check_gtod(const struct timeval *tv_sys1,
> +                     const struct timeval *tv_sys2,
> +                     const struct timezone *tz_sys,
> +                     const char *which,
> +                     const struct timeval *tv_other,
> +                     const struct timezone *tz_other)
> +{
> +       int nerrs = 0;
> +       double d1, d2;
> +
> +       if (tz_other && (tz_sys->tz_minuteswest != tz_other->tz_minuteswest || tz_sys->tz_dsttime != tz_other->tz_dsttime)) {
> +               printf("[FAIL] %s tz mismatch\n", which);
> +               nerrs++;
> +       }
> +
> +       d1 = tv_diff(tv_other, tv_sys1);
> +       d2 = tv_diff(tv_sys2, tv_other);
> +       printf("\t%s time offsets: %lf %lf\n", which, d1, d2);
> +
> +       if (d1 < 0 || d2 < 0) {
> +               printf("[FAIL]\t%s time was inconsistent with the syscall\n", which);
> +               nerrs++;
> +       } else {
> +               printf("[OK]\t%s gettimeofday()'s timeval was okay\n", which);
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_gtod(void)
> +{
> +       struct timeval tv_sys1, tv_sys2, tv_vdso, tv_vsys;
> +       struct timezone tz_sys, tz_vdso, tz_vsys;
> +       long ret_vdso = -1;
> +       long ret_vsys = -1;
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest gettimeofday()\n");
> +
> +       if (sys_gtod(&tv_sys1, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +       if (vdso_gtod)
> +               ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso);
> +       if (vgtod)
> +               ret_vsys = vgtod(&tv_vsys, &tz_vsys);
> +       if (sys_gtod(&tv_sys2, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +
> +       if (vdso_gtod) {
> +               if (ret_vdso == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vDSO", &tv_vdso, &tz_vdso);
> +               } else {
> +                       printf("[FAIL]\tvDSO gettimeofday() failed: %ld\n", ret_vdso);
> +                       nerrs++;
> +               }
> +       }
> +
> +       if (vgtod) {
> +               if (ret_vsys == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys);
> +               } else {
> +                       printf("[FAIL]\tvsys gettimeofday() failed: %ld\n", ret_vsys);
> +                       nerrs++;
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_time(void) {
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest time()\n");
> +       long t_sys1, t_sys2, t_vdso = 0, t_vsys = 0;
> +       long t2_sys1 = -1, t2_sys2 = -1, t2_vdso = -1, t2_vsys = -1;
> +       t_sys1 = sys_time(&t2_sys1);
> +       if (vdso_time)
> +               t_vdso = vdso_time(&t2_vdso);
> +       if (vtime)
> +               t_vsys = vtime(&t2_vsys);
> +       t_sys2 = sys_time(&t2_sys2);
> +       if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) {
> +               printf("[FAIL]\tsyscall failed (ret1:%ld output1:%ld ret2:%ld output2:%ld)\n", t_sys1, t2_sys1, t_sys2, t2_sys2);
> +               nerrs++;
> +               return nerrs;
> +       }
> +
> +       if (vdso_time) {
> +               if (t_vdso < 0 || t_vdso != t2_vdso) {
> +                       printf("[FAIL]\tvDSO failed (ret:%ld output:%ld)\n", t_vdso, t2_vdso);
> +                       nerrs++;
> +               } else if (t_vdso < t_sys1 || t_vdso > t_sys2) {
> +                       printf("[FAIL]\tvDSO returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vdso, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvDSO time() is okay\n");
> +               }
> +       }
> +
> +       if (vtime) {
> +               if (t_vsys < 0 || t_vsys != t2_vsys) {
> +                       printf("[FAIL]\tvsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys);
> +                       nerrs++;
> +               } else if (t_vsys < t_sys1 || t_vsys > t_sys2) {
> +                       printf("[FAIL]\tvsyscall returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vsys, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvsyscall time() is okay\n");
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_getcpu(int cpu)
> +{
> +       int nerrs = 0;
> +       long ret_sys, ret_vdso = -1, ret_vsys = -1;
> +
> +       printf("[RUN]\tgetcpu() on CPU %d\n", cpu);
> +
> +       cpu_set_t cpuset;
> +       CPU_ZERO(&cpuset);
> +       CPU_SET(cpu, &cpuset);
> +       if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
> +               printf("[SKIP]\tfailed to force CPU %d\n", cpu);
> +               return nerrs;
> +       }
> +
> +       unsigned cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys;
> +       unsigned node = 0;
> +       bool have_node = false;
> +       ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
> +       if (vdso_getcpu)
> +               ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
> +       if (vgetcpu)
> +               ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
> +
> +       if (ret_sys == 0) {
> +               if (cpu_sys != cpu) {
> +                       printf("[FAIL]\tsyscall reported CPU %hu but should be %d\n", cpu_sys, cpu);
> +                       nerrs++;
> +               }
> +
> +               have_node = true;
> +               node = node_sys;
> +       }
> +
> +       if (vdso_getcpu) {
> +               if (ret_vdso) {
> +                       printf("[FAIL]\tvDSO getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vdso;
> +                       }
> +
> +                       if (cpu_vdso != cpu) {
> +                               printf("[FAIL]\tvDSO reported CPU %hu but should be %d\n", cpu_vdso, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct CPU\n");
> +                       }
> +
> +                       if (node_vdso != node) {
> +                               printf("[FAIL]\tvDSO reported node %hu but should be %hu\n", node_vdso, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       if (vgetcpu) {
> +               if (ret_vsys) {
> +                       printf("[FAIL]\tvsyscall getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vsys;
> +                       }
> +
> +                       if (cpu_vsys != cpu) {
> +                               printf("[FAIL]\tvsyscall reported CPU %hu but should be %d\n", cpu_vsys, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct CPU\n");
> +                       }
> +
> +                       if (node_vsys != node) {
> +                               printf("[FAIL]\tvsyscall reported node %hu but should be %hu\n", node_vsys, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_vsys_r(void)
> +{
> +#ifdef __x86_64__
> +       printf("[RUN]\tChecking read access to the vsyscall page\n");
> +       bool can_read;
> +       if (sigsetjmp(jmpbuf, 1) == 0) {
> +               *(volatile int *)0xffffffffff600000;
> +               can_read = true;
> +       } else {
> +               can_read = false;
> +       }
> +
> +       if (can_read && !should_read_vsyscall) {
> +               printf("[FAIL]\tWe have read access, but we shouldn't\n");
> +               return 1;
> +       } else if (!can_read && should_read_vsyscall) {
> +               printf("[FAIL]\tWe don't have read access, but we should\n");
> +               return 1;
> +       } else {
> +               printf("[OK]\tgot expected result\n");
> +       }
> +#endif
> +
> +       return 0;
> +}
> +
> +
> +#ifdef __x86_64__
> +#define X86_EFLAGS_TF (1UL << 8)
> +static volatile sig_atomic_t num_vsyscall_traps;
> +
> +static unsigned long get_eflags(void)
> +{
> +       unsigned long eflags;
> +       asm volatile ("pushfq\n\tpopq %0" : "=rm" (eflags));
> +       return eflags;
> +}
> +
> +static void set_eflags(unsigned long eflags)
> +{
> +       asm volatile ("pushq %0\n\tpopfq" : : "rm" (eflags) : "flags");
> +}
> +
> +static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       ucontext_t *ctx = (ucontext_t *)ctx_void;
> +       unsigned long ip = ctx->uc_mcontext.gregs[REG_RIP];
> +
> +       if (((ip ^ 0xffffffffff600000UL) & ~0xfffUL) == 0)
> +               num_vsyscall_traps++;
> +}
> +
> +static int test_native_vsyscall(void)
> +{
> +       time_t tmp;
> +       bool is_native;
> +
> +       if (!vtime)
> +               return 0;
> +
> +       printf("[RUN]\tchecking for native vsyscall\n");
> +       sethandler(SIGTRAP, sigtrap, 0);
> +       set_eflags(get_eflags() | X86_EFLAGS_TF);
> +       vtime(&tmp);
> +       set_eflags(get_eflags() & ~X86_EFLAGS_TF);
> +
> +       /*
> +        * If vsyscalls are emulated, we expect a single trap in the
> +        * vsyscall page -- the call instruction will trap with RIP
> +        * pointing to the entry point before emulation takes over.
> +        * In native mode, we expect two traps, since whatever code
> +        * the vsyscall page contains will be more than just a ret
> +        * instruction.
> +        */
> +       is_native = (num_vsyscall_traps > 1);
> +
> +       printf("\tvsyscalls are %s (%d instructions in vsyscall page)\n",
> +              (is_native ? "native" : "emulated"),
> +              (int)num_vsyscall_traps);
> +
> +       return 0;
> +}
> +#endif
> +
> +int main(int argc, char **argv)
> +{
> +       int nerrs = 0;
> +
> +       init_vdso();
> +       nerrs += init_vsys();
> +
> +       nerrs += test_gtod();
> +       nerrs += test_time();
> +       nerrs += test_getcpu(0);
> +       nerrs += test_getcpu(1);
> +
> +       sethandler(SIGSEGV, sigsegv, 0);
> +       nerrs += test_vsys_r();
> +
> +#ifdef __x86_64__
> +       nerrs += test_native_vsyscall();
> +#endif
> +
> +       return nerrs ? 1 : 0;
> +}
> --
> 2.13.6
>

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] selftests/x86: Add test_vsyscall
@ 2018-01-13  6:22   ` Andy Lutomirski
  0 siblings, 0 replies; 12+ messages in thread
From: Andy Lutomirski @ 2018-01-13  6:22 UTC (permalink / raw)
  To: Andy Lutomirski, Shuah Khan, open list:KERNEL SELFTEST FRAMEWORK
  Cc: X86 ML, LKML, Borislav Petkov, Kees Cook, Peter Zijlstra,
	Greg Kroah-Hartman, Hugh Dickins, stable

Add missing cc's.  Duh

On Thu, Jan 11, 2018 at 5:16 PM, Andy Lutomirski <luto@kernel.org> wrote:
> This tests that the vsyscall entries do what they're expected to do.
> It also confirms that attempts to read the vsyscall page behave as
> expected.
>
> If changes are made to the vsyscall code or its memory map handling,
> running this test in all three of vsyscall=none, vsyscall=emulate,
> and vsyscall=native are helpful.
>
> (Because it's easy, this also compares the vsyscall results to their
>  vDSO equivalents.)
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Andy Lutomirski <luto@kernel.org>
> ---
>
> Note to KAISER backporters: please test this under all three
> vsyscall modes.  Also, in the emulate and native modes, make sure
> that test_vsyscall_64 agrees with the command line or config
> option as to which mode you're in.  It's quite easy to mess up
> the kernel such that native mode accidentally emulates
> or vice versa.
>
> Greg, etc: please backport this to all your Meltdown-patched
> kernels.  It'll help make sure the patches didn't regress
> vsyscalls.
>
> Changes from RFC version:
>  - Doesn't warn on 32-bit
>  - Detects native vs emulate
>
> tools/testing/selftests/x86/Makefile        |   2 +-
>  tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
>  2 files changed, 501 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/x86/test_vsyscall.c
>
> diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
> index 939a337128db..5d4f10ac2af2 100644
> --- a/tools/testing/selftests/x86/Makefile
> +++ b/tools/testing/selftests/x86/Makefile
> @@ -7,7 +7,7 @@ include ../lib.mk
>
>  TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \
>                         check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test ioperm \
> -                       protection_keys test_vdso
> +                       protection_keys test_vdso test_vsyscall
>  TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
>                         test_FCMOV test_FCOMI test_FISTTP \
>                         vdso_restorer
> diff --git a/tools/testing/selftests/x86/test_vsyscall.c b/tools/testing/selftests/x86/test_vsyscall.c
> new file mode 100644
> index 000000000000..7a744fa7b786
> --- /dev/null
> +++ b/tools/testing/selftests/x86/test_vsyscall.c
> @@ -0,0 +1,500 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdio.h>
> +#include <sys/time.h>
> +#include <time.h>
> +#include <stdlib.h>
> +#include <sys/syscall.h>
> +#include <unistd.h>
> +#include <dlfcn.h>
> +#include <string.h>
> +#include <inttypes.h>
> +#include <signal.h>
> +#include <sys/ucontext.h>
> +#include <errno.h>
> +#include <err.h>
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <setjmp.h>
> +
> +#ifdef __x86_64__
> +# define VSYS(x) (x)
> +#else
> +# define VSYS(x) 0
> +#endif
> +
> +#ifndef SYS_getcpu
> +# ifdef __x86_64__
> +#  define SYS_getcpu 309
> +# else
> +#  define SYS_getcpu 318
> +# endif
> +#endif
> +
> +static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
> +                      int flags)
> +{
> +       struct sigaction sa;
> +       memset(&sa, 0, sizeof(sa));
> +       sa.sa_sigaction = handler;
> +       sa.sa_flags = SA_SIGINFO | flags;
> +       sigemptyset(&sa.sa_mask);
> +       if (sigaction(sig, &sa, 0))
> +               err(1, "sigaction");
> +}
> +
> +/* vsyscalls and vDSO */
> +bool should_read_vsyscall = false;
> +
> +typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
> +gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000);
> +gtod_t vdso_gtod;
> +
> +typedef int (*vgettime_t)(clockid_t, struct timespec *);
> +vgettime_t vdso_gettime;
> +
> +typedef long (*time_func_t)(time_t *t);
> +time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400);
> +time_func_t vdso_time;
> +
> +typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
> +getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800);
> +getcpu_t vdso_getcpu;
> +
> +static void init_vdso(void)
> +{
> +       void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso)
> +               vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso) {
> +               printf("[WARN]\tfailed to find vDSO\n");
> +               return;
> +       }
> +
> +       vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday");
> +       if (!vdso_gtod)
> +               printf("[WARN]\tfailed to find gettimeofday in vDSO\n");
> +
> +       vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
> +       if (!vdso_gettime)
> +               printf("[WARN]\tfailed to find clock_gettime in vDSO\n");
> +
> +       vdso_time = (time_func_t)dlsym(vdso, "__vdso_time");
> +       if (!vdso_time)
> +               printf("[WARN]\tfailed to find time in vDSO\n");
> +
> +       vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
> +       if (!vdso_getcpu) {
> +               /* getcpu() was never wired up in the 32-bit vDSO. */
> +               printf("[%s]\tfailed to find getcpu in vDSO\n",
> +                      sizeof(long) == 8 ? "WARN" : "NOTE");
> +       }
> +}
> +
> +static int init_vsys(void)
> +{
> +#ifdef __x86_64__
> +       int nerrs = 0;
> +       FILE *maps;
> +       char line[128];
> +       bool found = false;
> +
> +       maps = fopen("/proc/self/maps", "r");
> +       if (!maps) {
> +               printf("[WARN]\tCould not open /proc/self/maps -- assuming vsyscall is r-x\n");
> +               should_read_vsyscall = true;
> +               return 0;
> +       }
> +
> +       while (fgets(line, sizeof(line), maps)) {
> +               char r, x;
> +               void *start, *end;
> +               char name[128];
> +               if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
> +                          &start, &end, &r, &x, name) != 5)
> +                       continue;
> +
> +               if (strcmp(name, "[vsyscall]"))
> +                       continue;
> +
> +               printf("\tvsyscall map: %s", line);
> +
> +               if (start != (void *)0xffffffffff600000 ||
> +                   end != (void *)0xffffffffff601000) {
> +                       printf("[FAIL]\taddress range is nonsense\n");
> +                       nerrs++;
> +               }
> +
> +               printf("\tvsyscall permissions are %c-%c\n", r, x);
> +               should_read_vsyscall = (r == 'r');
> +               if (x != 'x') {
> +                       vgtod = NULL;
> +                       vtime = NULL;
> +                       vgetcpu = NULL;
> +               }
> +
> +               found = true;
> +               break;
> +       }
> +
> +       fclose(maps);
> +
> +       if (!found) {
> +               printf("\tno vsyscall map in /proc/self/maps\n");
> +               should_read_vsyscall = false;
> +               vgtod = NULL;
> +               vtime = NULL;
> +               vgetcpu = NULL;
> +       }
> +
> +       return nerrs;
> +#else
> +       return 0;
> +#endif
> +}
> +
> +/* syscalls */
> +static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
> +{
> +       return syscall(SYS_gettimeofday, tv, tz);
> +}
> +
> +static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
> +{
> +       return syscall(SYS_clock_gettime, id, ts);
> +}
> +
> +static inline long sys_time(time_t *t)
> +{
> +       return syscall(SYS_time, t);
> +}
> +
> +static inline long sys_getcpu(unsigned * cpu, unsigned * node,
> +                             void* cache)
> +{
> +       return syscall(SYS_getcpu, cpu, node, cache);
> +}
> +
> +static jmp_buf jmpbuf;
> +
> +static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       siglongjmp(jmpbuf, 1);
> +}
> +
> +static double tv_diff(const struct timeval *a, const struct timeval *b)
> +{
> +       return (double)(a->tv_sec - b->tv_sec) +
> +               (double)((int)a->tv_usec - (int)b->tv_usec) * 1e-6;
> +}
> +
> +static int check_gtod(const struct timeval *tv_sys1,
> +                     const struct timeval *tv_sys2,
> +                     const struct timezone *tz_sys,
> +                     const char *which,
> +                     const struct timeval *tv_other,
> +                     const struct timezone *tz_other)
> +{
> +       int nerrs = 0;
> +       double d1, d2;
> +
> +       if (tz_other && (tz_sys->tz_minuteswest != tz_other->tz_minuteswest || tz_sys->tz_dsttime != tz_other->tz_dsttime)) {
> +               printf("[FAIL] %s tz mismatch\n", which);
> +               nerrs++;
> +       }
> +
> +       d1 = tv_diff(tv_other, tv_sys1);
> +       d2 = tv_diff(tv_sys2, tv_other);
> +       printf("\t%s time offsets: %lf %lf\n", which, d1, d2);
> +
> +       if (d1 < 0 || d2 < 0) {
> +               printf("[FAIL]\t%s time was inconsistent with the syscall\n", which);
> +               nerrs++;
> +       } else {
> +               printf("[OK]\t%s gettimeofday()'s timeval was okay\n", which);
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_gtod(void)
> +{
> +       struct timeval tv_sys1, tv_sys2, tv_vdso, tv_vsys;
> +       struct timezone tz_sys, tz_vdso, tz_vsys;
> +       long ret_vdso = -1;
> +       long ret_vsys = -1;
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest gettimeofday()\n");
> +
> +       if (sys_gtod(&tv_sys1, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +       if (vdso_gtod)
> +               ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso);
> +       if (vgtod)
> +               ret_vsys = vgtod(&tv_vsys, &tz_vsys);
> +       if (sys_gtod(&tv_sys2, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +
> +       if (vdso_gtod) {
> +               if (ret_vdso == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vDSO", &tv_vdso, &tz_vdso);
> +               } else {
> +                       printf("[FAIL]\tvDSO gettimeofday() failed: %ld\n", ret_vdso);
> +                       nerrs++;
> +               }
> +       }
> +
> +       if (vgtod) {
> +               if (ret_vsys == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys);
> +               } else {
> +                       printf("[FAIL]\tvsys gettimeofday() failed: %ld\n", ret_vsys);
> +                       nerrs++;
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_time(void) {
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest time()\n");
> +       long t_sys1, t_sys2, t_vdso = 0, t_vsys = 0;
> +       long t2_sys1 = -1, t2_sys2 = -1, t2_vdso = -1, t2_vsys = -1;
> +       t_sys1 = sys_time(&t2_sys1);
> +       if (vdso_time)
> +               t_vdso = vdso_time(&t2_vdso);
> +       if (vtime)
> +               t_vsys = vtime(&t2_vsys);
> +       t_sys2 = sys_time(&t2_sys2);
> +       if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) {
> +               printf("[FAIL]\tsyscall failed (ret1:%ld output1:%ld ret2:%ld output2:%ld)\n", t_sys1, t2_sys1, t_sys2, t2_sys2);
> +               nerrs++;
> +               return nerrs;
> +       }
> +
> +       if (vdso_time) {
> +               if (t_vdso < 0 || t_vdso != t2_vdso) {
> +                       printf("[FAIL]\tvDSO failed (ret:%ld output:%ld)\n", t_vdso, t2_vdso);
> +                       nerrs++;
> +               } else if (t_vdso < t_sys1 || t_vdso > t_sys2) {
> +                       printf("[FAIL]\tvDSO returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vdso, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvDSO time() is okay\n");
> +               }
> +       }
> +
> +       if (vtime) {
> +               if (t_vsys < 0 || t_vsys != t2_vsys) {
> +                       printf("[FAIL]\tvsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys);
> +                       nerrs++;
> +               } else if (t_vsys < t_sys1 || t_vsys > t_sys2) {
> +                       printf("[FAIL]\tvsyscall returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vsys, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvsyscall time() is okay\n");
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_getcpu(int cpu)
> +{
> +       int nerrs = 0;
> +       long ret_sys, ret_vdso = -1, ret_vsys = -1;
> +
> +       printf("[RUN]\tgetcpu() on CPU %d\n", cpu);
> +
> +       cpu_set_t cpuset;
> +       CPU_ZERO(&cpuset);
> +       CPU_SET(cpu, &cpuset);
> +       if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
> +               printf("[SKIP]\tfailed to force CPU %d\n", cpu);
> +               return nerrs;
> +       }
> +
> +       unsigned cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys;
> +       unsigned node = 0;
> +       bool have_node = false;
> +       ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
> +       if (vdso_getcpu)
> +               ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
> +       if (vgetcpu)
> +               ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
> +
> +       if (ret_sys == 0) {
> +               if (cpu_sys != cpu) {
> +                       printf("[FAIL]\tsyscall reported CPU %hu but should be %d\n", cpu_sys, cpu);
> +                       nerrs++;
> +               }
> +
> +               have_node = true;
> +               node = node_sys;
> +       }
> +
> +       if (vdso_getcpu) {
> +               if (ret_vdso) {
> +                       printf("[FAIL]\tvDSO getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vdso;
> +                       }
> +
> +                       if (cpu_vdso != cpu) {
> +                               printf("[FAIL]\tvDSO reported CPU %hu but should be %d\n", cpu_vdso, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct CPU\n");
> +                       }
> +
> +                       if (node_vdso != node) {
> +                               printf("[FAIL]\tvDSO reported node %hu but should be %hu\n", node_vdso, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       if (vgetcpu) {
> +               if (ret_vsys) {
> +                       printf("[FAIL]\tvsyscall getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vsys;
> +                       }
> +
> +                       if (cpu_vsys != cpu) {
> +                               printf("[FAIL]\tvsyscall reported CPU %hu but should be %d\n", cpu_vsys, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct CPU\n");
> +                       }
> +
> +                       if (node_vsys != node) {
> +                               printf("[FAIL]\tvsyscall reported node %hu but should be %hu\n", node_vsys, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_vsys_r(void)
> +{
> +#ifdef __x86_64__
> +       printf("[RUN]\tChecking read access to the vsyscall page\n");
> +       bool can_read;
> +       if (sigsetjmp(jmpbuf, 1) == 0) {
> +               *(volatile int *)0xffffffffff600000;
> +               can_read = true;
> +       } else {
> +               can_read = false;
> +       }
> +
> +       if (can_read && !should_read_vsyscall) {
> +               printf("[FAIL]\tWe have read access, but we shouldn't\n");
> +               return 1;
> +       } else if (!can_read && should_read_vsyscall) {
> +               printf("[FAIL]\tWe don't have read access, but we should\n");
> +               return 1;
> +       } else {
> +               printf("[OK]\tgot expected result\n");
> +       }
> +#endif
> +
> +       return 0;
> +}
> +
> +
> +#ifdef __x86_64__
> +#define X86_EFLAGS_TF (1UL << 8)
> +static volatile sig_atomic_t num_vsyscall_traps;
> +
> +static unsigned long get_eflags(void)
> +{
> +       unsigned long eflags;
> +       asm volatile ("pushfq\n\tpopq %0" : "=rm" (eflags));
> +       return eflags;
> +}
> +
> +static void set_eflags(unsigned long eflags)
> +{
> +       asm volatile ("pushq %0\n\tpopfq" : : "rm" (eflags) : "flags");
> +}
> +
> +static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       ucontext_t *ctx = (ucontext_t *)ctx_void;
> +       unsigned long ip = ctx->uc_mcontext.gregs[REG_RIP];
> +
> +       if (((ip ^ 0xffffffffff600000UL) & ~0xfffUL) == 0)
> +               num_vsyscall_traps++;
> +}
> +
> +static int test_native_vsyscall(void)
> +{
> +       time_t tmp;
> +       bool is_native;
> +
> +       if (!vtime)
> +               return 0;
> +
> +       printf("[RUN]\tchecking for native vsyscall\n");
> +       sethandler(SIGTRAP, sigtrap, 0);
> +       set_eflags(get_eflags() | X86_EFLAGS_TF);
> +       vtime(&tmp);
> +       set_eflags(get_eflags() & ~X86_EFLAGS_TF);
> +
> +       /*
> +        * If vsyscalls are emulated, we expect a single trap in the
> +        * vsyscall page -- the call instruction will trap with RIP
> +        * pointing to the entry point before emulation takes over.
> +        * In native mode, we expect two traps, since whatever code
> +        * the vsyscall page contains will be more than just a ret
> +        * instruction.
> +        */
> +       is_native = (num_vsyscall_traps > 1);
> +
> +       printf("\tvsyscalls are %s (%d instructions in vsyscall page)\n",
> +              (is_native ? "native" : "emulated"),
> +              (int)num_vsyscall_traps);
> +
> +       return 0;
> +}
> +#endif
> +
> +int main(int argc, char **argv)
> +{
> +       int nerrs = 0;
> +
> +       init_vdso();
> +       nerrs += init_vsys();
> +
> +       nerrs += test_gtod();
> +       nerrs += test_time();
> +       nerrs += test_getcpu(0);
> +       nerrs += test_getcpu(1);
> +
> +       sethandler(SIGSEGV, sigsegv, 0);
> +       nerrs += test_vsys_r();
> +
> +#ifdef __x86_64__
> +       nerrs += test_native_vsyscall();
> +#endif
> +
> +       return nerrs ? 1 : 0;
> +}
> --
> 2.13.6
>

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [Linux-kselftest-mirror] [PATCH] selftests/x86: Add test_vsyscall
@ 2018-01-13  6:22   ` Andy Lutomirski
  0 siblings, 0 replies; 12+ messages in thread
From: luto @ 2018-01-13  6:22 UTC (permalink / raw)


Add missing cc's.  Duh

On Thu, Jan 11, 2018 at 5:16 PM, Andy Lutomirski <luto at kernel.org> wrote:
> This tests that the vsyscall entries do what they're expected to do.
> It also confirms that attempts to read the vsyscall page behave as
> expected.
>
> If changes are made to the vsyscall code or its memory map handling,
> running this test in all three of vsyscall=none, vsyscall=emulate,
> and vsyscall=native are helpful.
>
> (Because it's easy, this also compares the vsyscall results to their
>  vDSO equivalents.)
>
> Cc: stable at vger.kernel.org
> Signed-off-by: Andy Lutomirski <luto at kernel.org>
> ---
>
> Note to KAISER backporters: please test this under all three
> vsyscall modes.  Also, in the emulate and native modes, make sure
> that test_vsyscall_64 agrees with the command line or config
> option as to which mode you're in.  It's quite easy to mess up
> the kernel such that native mode accidentally emulates
> or vice versa.
>
> Greg, etc: please backport this to all your Meltdown-patched
> kernels.  It'll help make sure the patches didn't regress
> vsyscalls.
>
> Changes from RFC version:
>  - Doesn't warn on 32-bit
>  - Detects native vs emulate
>
> tools/testing/selftests/x86/Makefile        |   2 +-
>  tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
>  2 files changed, 501 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/x86/test_vsyscall.c
>
> diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
> index 939a337128db..5d4f10ac2af2 100644
> --- a/tools/testing/selftests/x86/Makefile
> +++ b/tools/testing/selftests/x86/Makefile
> @@ -7,7 +7,7 @@ include ../lib.mk
>
>  TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \
>                         check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test ioperm \
> -                       protection_keys test_vdso
> +                       protection_keys test_vdso test_vsyscall
>  TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
>                         test_FCMOV test_FCOMI test_FISTTP \
>                         vdso_restorer
> diff --git a/tools/testing/selftests/x86/test_vsyscall.c b/tools/testing/selftests/x86/test_vsyscall.c
> new file mode 100644
> index 000000000000..7a744fa7b786
> --- /dev/null
> +++ b/tools/testing/selftests/x86/test_vsyscall.c
> @@ -0,0 +1,500 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdio.h>
> +#include <sys/time.h>
> +#include <time.h>
> +#include <stdlib.h>
> +#include <sys/syscall.h>
> +#include <unistd.h>
> +#include <dlfcn.h>
> +#include <string.h>
> +#include <inttypes.h>
> +#include <signal.h>
> +#include <sys/ucontext.h>
> +#include <errno.h>
> +#include <err.h>
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <setjmp.h>
> +
> +#ifdef __x86_64__
> +# define VSYS(x) (x)
> +#else
> +# define VSYS(x) 0
> +#endif
> +
> +#ifndef SYS_getcpu
> +# ifdef __x86_64__
> +#  define SYS_getcpu 309
> +# else
> +#  define SYS_getcpu 318
> +# endif
> +#endif
> +
> +static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
> +                      int flags)
> +{
> +       struct sigaction sa;
> +       memset(&sa, 0, sizeof(sa));
> +       sa.sa_sigaction = handler;
> +       sa.sa_flags = SA_SIGINFO | flags;
> +       sigemptyset(&sa.sa_mask);
> +       if (sigaction(sig, &sa, 0))
> +               err(1, "sigaction");
> +}
> +
> +/* vsyscalls and vDSO */
> +bool should_read_vsyscall = false;
> +
> +typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
> +gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000);
> +gtod_t vdso_gtod;
> +
> +typedef int (*vgettime_t)(clockid_t, struct timespec *);
> +vgettime_t vdso_gettime;
> +
> +typedef long (*time_func_t)(time_t *t);
> +time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400);
> +time_func_t vdso_time;
> +
> +typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
> +getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800);
> +getcpu_t vdso_getcpu;
> +
> +static void init_vdso(void)
> +{
> +       void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso)
> +               vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso) {
> +               printf("[WARN]\tfailed to find vDSO\n");
> +               return;
> +       }
> +
> +       vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday");
> +       if (!vdso_gtod)
> +               printf("[WARN]\tfailed to find gettimeofday in vDSO\n");
> +
> +       vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
> +       if (!vdso_gettime)
> +               printf("[WARN]\tfailed to find clock_gettime in vDSO\n");
> +
> +       vdso_time = (time_func_t)dlsym(vdso, "__vdso_time");
> +       if (!vdso_time)
> +               printf("[WARN]\tfailed to find time in vDSO\n");
> +
> +       vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
> +       if (!vdso_getcpu) {
> +               /* getcpu() was never wired up in the 32-bit vDSO. */
> +               printf("[%s]\tfailed to find getcpu in vDSO\n",
> +                      sizeof(long) == 8 ? "WARN" : "NOTE");
> +       }
> +}
> +
> +static int init_vsys(void)
> +{
> +#ifdef __x86_64__
> +       int nerrs = 0;
> +       FILE *maps;
> +       char line[128];
> +       bool found = false;
> +
> +       maps = fopen("/proc/self/maps", "r");
> +       if (!maps) {
> +               printf("[WARN]\tCould not open /proc/self/maps -- assuming vsyscall is r-x\n");
> +               should_read_vsyscall = true;
> +               return 0;
> +       }
> +
> +       while (fgets(line, sizeof(line), maps)) {
> +               char r, x;
> +               void *start, *end;
> +               char name[128];
> +               if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
> +                          &start, &end, &r, &x, name) != 5)
> +                       continue;
> +
> +               if (strcmp(name, "[vsyscall]"))
> +                       continue;
> +
> +               printf("\tvsyscall map: %s", line);
> +
> +               if (start != (void *)0xffffffffff600000 ||
> +                   end != (void *)0xffffffffff601000) {
> +                       printf("[FAIL]\taddress range is nonsense\n");
> +                       nerrs++;
> +               }
> +
> +               printf("\tvsyscall permissions are %c-%c\n", r, x);
> +               should_read_vsyscall = (r == 'r');
> +               if (x != 'x') {
> +                       vgtod = NULL;
> +                       vtime = NULL;
> +                       vgetcpu = NULL;
> +               }
> +
> +               found = true;
> +               break;
> +       }
> +
> +       fclose(maps);
> +
> +       if (!found) {
> +               printf("\tno vsyscall map in /proc/self/maps\n");
> +               should_read_vsyscall = false;
> +               vgtod = NULL;
> +               vtime = NULL;
> +               vgetcpu = NULL;
> +       }
> +
> +       return nerrs;
> +#else
> +       return 0;
> +#endif
> +}
> +
> +/* syscalls */
> +static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
> +{
> +       return syscall(SYS_gettimeofday, tv, tz);
> +}
> +
> +static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
> +{
> +       return syscall(SYS_clock_gettime, id, ts);
> +}
> +
> +static inline long sys_time(time_t *t)
> +{
> +       return syscall(SYS_time, t);
> +}
> +
> +static inline long sys_getcpu(unsigned * cpu, unsigned * node,
> +                             void* cache)
> +{
> +       return syscall(SYS_getcpu, cpu, node, cache);
> +}
> +
> +static jmp_buf jmpbuf;
> +
> +static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       siglongjmp(jmpbuf, 1);
> +}
> +
> +static double tv_diff(const struct timeval *a, const struct timeval *b)
> +{
> +       return (double)(a->tv_sec - b->tv_sec) +
> +               (double)((int)a->tv_usec - (int)b->tv_usec) * 1e-6;
> +}
> +
> +static int check_gtod(const struct timeval *tv_sys1,
> +                     const struct timeval *tv_sys2,
> +                     const struct timezone *tz_sys,
> +                     const char *which,
> +                     const struct timeval *tv_other,
> +                     const struct timezone *tz_other)
> +{
> +       int nerrs = 0;
> +       double d1, d2;
> +
> +       if (tz_other && (tz_sys->tz_minuteswest != tz_other->tz_minuteswest || tz_sys->tz_dsttime != tz_other->tz_dsttime)) {
> +               printf("[FAIL] %s tz mismatch\n", which);
> +               nerrs++;
> +       }
> +
> +       d1 = tv_diff(tv_other, tv_sys1);
> +       d2 = tv_diff(tv_sys2, tv_other);
> +       printf("\t%s time offsets: %lf %lf\n", which, d1, d2);
> +
> +       if (d1 < 0 || d2 < 0) {
> +               printf("[FAIL]\t%s time was inconsistent with the syscall\n", which);
> +               nerrs++;
> +       } else {
> +               printf("[OK]\t%s gettimeofday()'s timeval was okay\n", which);
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_gtod(void)
> +{
> +       struct timeval tv_sys1, tv_sys2, tv_vdso, tv_vsys;
> +       struct timezone tz_sys, tz_vdso, tz_vsys;
> +       long ret_vdso = -1;
> +       long ret_vsys = -1;
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest gettimeofday()\n");
> +
> +       if (sys_gtod(&tv_sys1, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +       if (vdso_gtod)
> +               ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso);
> +       if (vgtod)
> +               ret_vsys = vgtod(&tv_vsys, &tz_vsys);
> +       if (sys_gtod(&tv_sys2, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +
> +       if (vdso_gtod) {
> +               if (ret_vdso == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vDSO", &tv_vdso, &tz_vdso);
> +               } else {
> +                       printf("[FAIL]\tvDSO gettimeofday() failed: %ld\n", ret_vdso);
> +                       nerrs++;
> +               }
> +       }
> +
> +       if (vgtod) {
> +               if (ret_vsys == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys);
> +               } else {
> +                       printf("[FAIL]\tvsys gettimeofday() failed: %ld\n", ret_vsys);
> +                       nerrs++;
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_time(void) {
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest time()\n");
> +       long t_sys1, t_sys2, t_vdso = 0, t_vsys = 0;
> +       long t2_sys1 = -1, t2_sys2 = -1, t2_vdso = -1, t2_vsys = -1;
> +       t_sys1 = sys_time(&t2_sys1);
> +       if (vdso_time)
> +               t_vdso = vdso_time(&t2_vdso);
> +       if (vtime)
> +               t_vsys = vtime(&t2_vsys);
> +       t_sys2 = sys_time(&t2_sys2);
> +       if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) {
> +               printf("[FAIL]\tsyscall failed (ret1:%ld output1:%ld ret2:%ld output2:%ld)\n", t_sys1, t2_sys1, t_sys2, t2_sys2);
> +               nerrs++;
> +               return nerrs;
> +       }
> +
> +       if (vdso_time) {
> +               if (t_vdso < 0 || t_vdso != t2_vdso) {
> +                       printf("[FAIL]\tvDSO failed (ret:%ld output:%ld)\n", t_vdso, t2_vdso);
> +                       nerrs++;
> +               } else if (t_vdso < t_sys1 || t_vdso > t_sys2) {
> +                       printf("[FAIL]\tvDSO returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vdso, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvDSO time() is okay\n");
> +               }
> +       }
> +
> +       if (vtime) {
> +               if (t_vsys < 0 || t_vsys != t2_vsys) {
> +                       printf("[FAIL]\tvsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys);
> +                       nerrs++;
> +               } else if (t_vsys < t_sys1 || t_vsys > t_sys2) {
> +                       printf("[FAIL]\tvsyscall returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vsys, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvsyscall time() is okay\n");
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_getcpu(int cpu)
> +{
> +       int nerrs = 0;
> +       long ret_sys, ret_vdso = -1, ret_vsys = -1;
> +
> +       printf("[RUN]\tgetcpu() on CPU %d\n", cpu);
> +
> +       cpu_set_t cpuset;
> +       CPU_ZERO(&cpuset);
> +       CPU_SET(cpu, &cpuset);
> +       if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
> +               printf("[SKIP]\tfailed to force CPU %d\n", cpu);
> +               return nerrs;
> +       }
> +
> +       unsigned cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys;
> +       unsigned node = 0;
> +       bool have_node = false;
> +       ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
> +       if (vdso_getcpu)
> +               ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
> +       if (vgetcpu)
> +               ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
> +
> +       if (ret_sys == 0) {
> +               if (cpu_sys != cpu) {
> +                       printf("[FAIL]\tsyscall reported CPU %hu but should be %d\n", cpu_sys, cpu);
> +                       nerrs++;
> +               }
> +
> +               have_node = true;
> +               node = node_sys;
> +       }
> +
> +       if (vdso_getcpu) {
> +               if (ret_vdso) {
> +                       printf("[FAIL]\tvDSO getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vdso;
> +                       }
> +
> +                       if (cpu_vdso != cpu) {
> +                               printf("[FAIL]\tvDSO reported CPU %hu but should be %d\n", cpu_vdso, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct CPU\n");
> +                       }
> +
> +                       if (node_vdso != node) {
> +                               printf("[FAIL]\tvDSO reported node %hu but should be %hu\n", node_vdso, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       if (vgetcpu) {
> +               if (ret_vsys) {
> +                       printf("[FAIL]\tvsyscall getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vsys;
> +                       }
> +
> +                       if (cpu_vsys != cpu) {
> +                               printf("[FAIL]\tvsyscall reported CPU %hu but should be %d\n", cpu_vsys, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct CPU\n");
> +                       }
> +
> +                       if (node_vsys != node) {
> +                               printf("[FAIL]\tvsyscall reported node %hu but should be %hu\n", node_vsys, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_vsys_r(void)
> +{
> +#ifdef __x86_64__
> +       printf("[RUN]\tChecking read access to the vsyscall page\n");
> +       bool can_read;
> +       if (sigsetjmp(jmpbuf, 1) == 0) {
> +               *(volatile int *)0xffffffffff600000;
> +               can_read = true;
> +       } else {
> +               can_read = false;
> +       }
> +
> +       if (can_read && !should_read_vsyscall) {
> +               printf("[FAIL]\tWe have read access, but we shouldn't\n");
> +               return 1;
> +       } else if (!can_read && should_read_vsyscall) {
> +               printf("[FAIL]\tWe don't have read access, but we should\n");
> +               return 1;
> +       } else {
> +               printf("[OK]\tgot expected result\n");
> +       }
> +#endif
> +
> +       return 0;
> +}
> +
> +
> +#ifdef __x86_64__
> +#define X86_EFLAGS_TF (1UL << 8)
> +static volatile sig_atomic_t num_vsyscall_traps;
> +
> +static unsigned long get_eflags(void)
> +{
> +       unsigned long eflags;
> +       asm volatile ("pushfq\n\tpopq %0" : "=rm" (eflags));
> +       return eflags;
> +}
> +
> +static void set_eflags(unsigned long eflags)
> +{
> +       asm volatile ("pushq %0\n\tpopfq" : : "rm" (eflags) : "flags");
> +}
> +
> +static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       ucontext_t *ctx = (ucontext_t *)ctx_void;
> +       unsigned long ip = ctx->uc_mcontext.gregs[REG_RIP];
> +
> +       if (((ip ^ 0xffffffffff600000UL) & ~0xfffUL) == 0)
> +               num_vsyscall_traps++;
> +}
> +
> +static int test_native_vsyscall(void)
> +{
> +       time_t tmp;
> +       bool is_native;
> +
> +       if (!vtime)
> +               return 0;
> +
> +       printf("[RUN]\tchecking for native vsyscall\n");
> +       sethandler(SIGTRAP, sigtrap, 0);
> +       set_eflags(get_eflags() | X86_EFLAGS_TF);
> +       vtime(&tmp);
> +       set_eflags(get_eflags() & ~X86_EFLAGS_TF);
> +
> +       /*
> +        * If vsyscalls are emulated, we expect a single trap in the
> +        * vsyscall page -- the call instruction will trap with RIP
> +        * pointing to the entry point before emulation takes over.
> +        * In native mode, we expect two traps, since whatever code
> +        * the vsyscall page contains will be more than just a ret
> +        * instruction.
> +        */
> +       is_native = (num_vsyscall_traps > 1);
> +
> +       printf("\tvsyscalls are %s (%d instructions in vsyscall page)\n",
> +              (is_native ? "native" : "emulated"),
> +              (int)num_vsyscall_traps);
> +
> +       return 0;
> +}
> +#endif
> +
> +int main(int argc, char **argv)
> +{
> +       int nerrs = 0;
> +
> +       init_vdso();
> +       nerrs += init_vsys();
> +
> +       nerrs += test_gtod();
> +       nerrs += test_time();
> +       nerrs += test_getcpu(0);
> +       nerrs += test_getcpu(1);
> +
> +       sethandler(SIGSEGV, sigsegv, 0);
> +       nerrs += test_vsys_r();
> +
> +#ifdef __x86_64__
> +       nerrs += test_native_vsyscall();
> +#endif
> +
> +       return nerrs ? 1 : 0;
> +}
> --
> 2.13.6
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kselftest" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [Linux-kselftest-mirror] [PATCH] selftests/x86: Add test_vsyscall
@ 2018-01-13  6:22   ` Andy Lutomirski
  0 siblings, 0 replies; 12+ messages in thread
From: Andy Lutomirski @ 2018-01-13  6:22 UTC (permalink / raw)


Add missing cc's.  Duh

On Thu, Jan 11, 2018@5:16 PM, Andy Lutomirski <luto@kernel.org> wrote:
> This tests that the vsyscall entries do what they're expected to do.
> It also confirms that attempts to read the vsyscall page behave as
> expected.
>
> If changes are made to the vsyscall code or its memory map handling,
> running this test in all three of vsyscall=none, vsyscall=emulate,
> and vsyscall=native are helpful.
>
> (Because it's easy, this also compares the vsyscall results to their
>  vDSO equivalents.)
>
> Cc: stable at vger.kernel.org
> Signed-off-by: Andy Lutomirski <luto at kernel.org>
> ---
>
> Note to KAISER backporters: please test this under all three
> vsyscall modes.  Also, in the emulate and native modes, make sure
> that test_vsyscall_64 agrees with the command line or config
> option as to which mode you're in.  It's quite easy to mess up
> the kernel such that native mode accidentally emulates
> or vice versa.
>
> Greg, etc: please backport this to all your Meltdown-patched
> kernels.  It'll help make sure the patches didn't regress
> vsyscalls.
>
> Changes from RFC version:
>  - Doesn't warn on 32-bit
>  - Detects native vs emulate
>
> tools/testing/selftests/x86/Makefile        |   2 +-
>  tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
>  2 files changed, 501 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/x86/test_vsyscall.c
>
> diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
> index 939a337128db..5d4f10ac2af2 100644
> --- a/tools/testing/selftests/x86/Makefile
> +++ b/tools/testing/selftests/x86/Makefile
> @@ -7,7 +7,7 @@ include ../lib.mk
>
>  TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \
>                         check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test ioperm \
> -                       protection_keys test_vdso
> +                       protection_keys test_vdso test_vsyscall
>  TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
>                         test_FCMOV test_FCOMI test_FISTTP \
>                         vdso_restorer
> diff --git a/tools/testing/selftests/x86/test_vsyscall.c b/tools/testing/selftests/x86/test_vsyscall.c
> new file mode 100644
> index 000000000000..7a744fa7b786
> --- /dev/null
> +++ b/tools/testing/selftests/x86/test_vsyscall.c
> @@ -0,0 +1,500 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdio.h>
> +#include <sys/time.h>
> +#include <time.h>
> +#include <stdlib.h>
> +#include <sys/syscall.h>
> +#include <unistd.h>
> +#include <dlfcn.h>
> +#include <string.h>
> +#include <inttypes.h>
> +#include <signal.h>
> +#include <sys/ucontext.h>
> +#include <errno.h>
> +#include <err.h>
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <setjmp.h>
> +
> +#ifdef __x86_64__
> +# define VSYS(x) (x)
> +#else
> +# define VSYS(x) 0
> +#endif
> +
> +#ifndef SYS_getcpu
> +# ifdef __x86_64__
> +#  define SYS_getcpu 309
> +# else
> +#  define SYS_getcpu 318
> +# endif
> +#endif
> +
> +static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
> +                      int flags)
> +{
> +       struct sigaction sa;
> +       memset(&sa, 0, sizeof(sa));
> +       sa.sa_sigaction = handler;
> +       sa.sa_flags = SA_SIGINFO | flags;
> +       sigemptyset(&sa.sa_mask);
> +       if (sigaction(sig, &sa, 0))
> +               err(1, "sigaction");
> +}
> +
> +/* vsyscalls and vDSO */
> +bool should_read_vsyscall = false;
> +
> +typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
> +gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000);
> +gtod_t vdso_gtod;
> +
> +typedef int (*vgettime_t)(clockid_t, struct timespec *);
> +vgettime_t vdso_gettime;
> +
> +typedef long (*time_func_t)(time_t *t);
> +time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400);
> +time_func_t vdso_time;
> +
> +typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
> +getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800);
> +getcpu_t vdso_getcpu;
> +
> +static void init_vdso(void)
> +{
> +       void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso)
> +               vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
> +       if (!vdso) {
> +               printf("[WARN]\tfailed to find vDSO\n");
> +               return;
> +       }
> +
> +       vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday");
> +       if (!vdso_gtod)
> +               printf("[WARN]\tfailed to find gettimeofday in vDSO\n");
> +
> +       vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
> +       if (!vdso_gettime)
> +               printf("[WARN]\tfailed to find clock_gettime in vDSO\n");
> +
> +       vdso_time = (time_func_t)dlsym(vdso, "__vdso_time");
> +       if (!vdso_time)
> +               printf("[WARN]\tfailed to find time in vDSO\n");
> +
> +       vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
> +       if (!vdso_getcpu) {
> +               /* getcpu() was never wired up in the 32-bit vDSO. */
> +               printf("[%s]\tfailed to find getcpu in vDSO\n",
> +                      sizeof(long) == 8 ? "WARN" : "NOTE");
> +       }
> +}
> +
> +static int init_vsys(void)
> +{
> +#ifdef __x86_64__
> +       int nerrs = 0;
> +       FILE *maps;
> +       char line[128];
> +       bool found = false;
> +
> +       maps = fopen("/proc/self/maps", "r");
> +       if (!maps) {
> +               printf("[WARN]\tCould not open /proc/self/maps -- assuming vsyscall is r-x\n");
> +               should_read_vsyscall = true;
> +               return 0;
> +       }
> +
> +       while (fgets(line, sizeof(line), maps)) {
> +               char r, x;
> +               void *start, *end;
> +               char name[128];
> +               if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
> +                          &start, &end, &r, &x, name) != 5)
> +                       continue;
> +
> +               if (strcmp(name, "[vsyscall]"))
> +                       continue;
> +
> +               printf("\tvsyscall map: %s", line);
> +
> +               if (start != (void *)0xffffffffff600000 ||
> +                   end != (void *)0xffffffffff601000) {
> +                       printf("[FAIL]\taddress range is nonsense\n");
> +                       nerrs++;
> +               }
> +
> +               printf("\tvsyscall permissions are %c-%c\n", r, x);
> +               should_read_vsyscall = (r == 'r');
> +               if (x != 'x') {
> +                       vgtod = NULL;
> +                       vtime = NULL;
> +                       vgetcpu = NULL;
> +               }
> +
> +               found = true;
> +               break;
> +       }
> +
> +       fclose(maps);
> +
> +       if (!found) {
> +               printf("\tno vsyscall map in /proc/self/maps\n");
> +               should_read_vsyscall = false;
> +               vgtod = NULL;
> +               vtime = NULL;
> +               vgetcpu = NULL;
> +       }
> +
> +       return nerrs;
> +#else
> +       return 0;
> +#endif
> +}
> +
> +/* syscalls */
> +static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
> +{
> +       return syscall(SYS_gettimeofday, tv, tz);
> +}
> +
> +static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
> +{
> +       return syscall(SYS_clock_gettime, id, ts);
> +}
> +
> +static inline long sys_time(time_t *t)
> +{
> +       return syscall(SYS_time, t);
> +}
> +
> +static inline long sys_getcpu(unsigned * cpu, unsigned * node,
> +                             void* cache)
> +{
> +       return syscall(SYS_getcpu, cpu, node, cache);
> +}
> +
> +static jmp_buf jmpbuf;
> +
> +static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       siglongjmp(jmpbuf, 1);
> +}
> +
> +static double tv_diff(const struct timeval *a, const struct timeval *b)
> +{
> +       return (double)(a->tv_sec - b->tv_sec) +
> +               (double)((int)a->tv_usec - (int)b->tv_usec) * 1e-6;
> +}
> +
> +static int check_gtod(const struct timeval *tv_sys1,
> +                     const struct timeval *tv_sys2,
> +                     const struct timezone *tz_sys,
> +                     const char *which,
> +                     const struct timeval *tv_other,
> +                     const struct timezone *tz_other)
> +{
> +       int nerrs = 0;
> +       double d1, d2;
> +
> +       if (tz_other && (tz_sys->tz_minuteswest != tz_other->tz_minuteswest || tz_sys->tz_dsttime != tz_other->tz_dsttime)) {
> +               printf("[FAIL] %s tz mismatch\n", which);
> +               nerrs++;
> +       }
> +
> +       d1 = tv_diff(tv_other, tv_sys1);
> +       d2 = tv_diff(tv_sys2, tv_other);
> +       printf("\t%s time offsets: %lf %lf\n", which, d1, d2);
> +
> +       if (d1 < 0 || d2 < 0) {
> +               printf("[FAIL]\t%s time was inconsistent with the syscall\n", which);
> +               nerrs++;
> +       } else {
> +               printf("[OK]\t%s gettimeofday()'s timeval was okay\n", which);
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_gtod(void)
> +{
> +       struct timeval tv_sys1, tv_sys2, tv_vdso, tv_vsys;
> +       struct timezone tz_sys, tz_vdso, tz_vsys;
> +       long ret_vdso = -1;
> +       long ret_vsys = -1;
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest gettimeofday()\n");
> +
> +       if (sys_gtod(&tv_sys1, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +       if (vdso_gtod)
> +               ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso);
> +       if (vgtod)
> +               ret_vsys = vgtod(&tv_vsys, &tz_vsys);
> +       if (sys_gtod(&tv_sys2, &tz_sys) != 0)
> +               err(1, "syscall gettimeofday");
> +
> +       if (vdso_gtod) {
> +               if (ret_vdso == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vDSO", &tv_vdso, &tz_vdso);
> +               } else {
> +                       printf("[FAIL]\tvDSO gettimeofday() failed: %ld\n", ret_vdso);
> +                       nerrs++;
> +               }
> +       }
> +
> +       if (vgtod) {
> +               if (ret_vsys == 0) {
> +                       nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys);
> +               } else {
> +                       printf("[FAIL]\tvsys gettimeofday() failed: %ld\n", ret_vsys);
> +                       nerrs++;
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_time(void) {
> +       int nerrs = 0;
> +
> +       printf("[RUN]\ttest time()\n");
> +       long t_sys1, t_sys2, t_vdso = 0, t_vsys = 0;
> +       long t2_sys1 = -1, t2_sys2 = -1, t2_vdso = -1, t2_vsys = -1;
> +       t_sys1 = sys_time(&t2_sys1);
> +       if (vdso_time)
> +               t_vdso = vdso_time(&t2_vdso);
> +       if (vtime)
> +               t_vsys = vtime(&t2_vsys);
> +       t_sys2 = sys_time(&t2_sys2);
> +       if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) {
> +               printf("[FAIL]\tsyscall failed (ret1:%ld output1:%ld ret2:%ld output2:%ld)\n", t_sys1, t2_sys1, t_sys2, t2_sys2);
> +               nerrs++;
> +               return nerrs;
> +       }
> +
> +       if (vdso_time) {
> +               if (t_vdso < 0 || t_vdso != t2_vdso) {
> +                       printf("[FAIL]\tvDSO failed (ret:%ld output:%ld)\n", t_vdso, t2_vdso);
> +                       nerrs++;
> +               } else if (t_vdso < t_sys1 || t_vdso > t_sys2) {
> +                       printf("[FAIL]\tvDSO returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vdso, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvDSO time() is okay\n");
> +               }
> +       }
> +
> +       if (vtime) {
> +               if (t_vsys < 0 || t_vsys != t2_vsys) {
> +                       printf("[FAIL]\tvsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys);
> +                       nerrs++;
> +               } else if (t_vsys < t_sys1 || t_vsys > t_sys2) {
> +                       printf("[FAIL]\tvsyscall returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vsys, t_sys2);
> +                       nerrs++;
> +               } else {
> +                       printf("[OK]\tvsyscall time() is okay\n");
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_getcpu(int cpu)
> +{
> +       int nerrs = 0;
> +       long ret_sys, ret_vdso = -1, ret_vsys = -1;
> +
> +       printf("[RUN]\tgetcpu() on CPU %d\n", cpu);
> +
> +       cpu_set_t cpuset;
> +       CPU_ZERO(&cpuset);
> +       CPU_SET(cpu, &cpuset);
> +       if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
> +               printf("[SKIP]\tfailed to force CPU %d\n", cpu);
> +               return nerrs;
> +       }
> +
> +       unsigned cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys;
> +       unsigned node = 0;
> +       bool have_node = false;
> +       ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
> +       if (vdso_getcpu)
> +               ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
> +       if (vgetcpu)
> +               ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
> +
> +       if (ret_sys == 0) {
> +               if (cpu_sys != cpu) {
> +                       printf("[FAIL]\tsyscall reported CPU %hu but should be %d\n", cpu_sys, cpu);
> +                       nerrs++;
> +               }
> +
> +               have_node = true;
> +               node = node_sys;
> +       }
> +
> +       if (vdso_getcpu) {
> +               if (ret_vdso) {
> +                       printf("[FAIL]\tvDSO getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vdso;
> +                       }
> +
> +                       if (cpu_vdso != cpu) {
> +                               printf("[FAIL]\tvDSO reported CPU %hu but should be %d\n", cpu_vdso, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct CPU\n");
> +                       }
> +
> +                       if (node_vdso != node) {
> +                               printf("[FAIL]\tvDSO reported node %hu but should be %hu\n", node_vdso, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvDSO reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       if (vgetcpu) {
> +               if (ret_vsys) {
> +                       printf("[FAIL]\tvsyscall getcpu() failed\n");
> +                       nerrs++;
> +               } else {
> +                       if (!have_node) {
> +                               have_node = true;
> +                               node = node_vsys;
> +                       }
> +
> +                       if (cpu_vsys != cpu) {
> +                               printf("[FAIL]\tvsyscall reported CPU %hu but should be %d\n", cpu_vsys, cpu);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct CPU\n");
> +                       }
> +
> +                       if (node_vsys != node) {
> +                               printf("[FAIL]\tvsyscall reported node %hu but should be %hu\n", node_vsys, node);
> +                               nerrs++;
> +                       } else {
> +                               printf("[OK]\tvsyscall reported correct node\n");
> +                       }
> +               }
> +       }
> +
> +       return nerrs;
> +}
> +
> +static int test_vsys_r(void)
> +{
> +#ifdef __x86_64__
> +       printf("[RUN]\tChecking read access to the vsyscall page\n");
> +       bool can_read;
> +       if (sigsetjmp(jmpbuf, 1) == 0) {
> +               *(volatile int *)0xffffffffff600000;
> +               can_read = true;
> +       } else {
> +               can_read = false;
> +       }
> +
> +       if (can_read && !should_read_vsyscall) {
> +               printf("[FAIL]\tWe have read access, but we shouldn't\n");
> +               return 1;
> +       } else if (!can_read && should_read_vsyscall) {
> +               printf("[FAIL]\tWe don't have read access, but we should\n");
> +               return 1;
> +       } else {
> +               printf("[OK]\tgot expected result\n");
> +       }
> +#endif
> +
> +       return 0;
> +}
> +
> +
> +#ifdef __x86_64__
> +#define X86_EFLAGS_TF (1UL << 8)
> +static volatile sig_atomic_t num_vsyscall_traps;
> +
> +static unsigned long get_eflags(void)
> +{
> +       unsigned long eflags;
> +       asm volatile ("pushfq\n\tpopq %0" : "=rm" (eflags));
> +       return eflags;
> +}
> +
> +static void set_eflags(unsigned long eflags)
> +{
> +       asm volatile ("pushq %0\n\tpopfq" : : "rm" (eflags) : "flags");
> +}
> +
> +static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
> +{
> +       ucontext_t *ctx = (ucontext_t *)ctx_void;
> +       unsigned long ip = ctx->uc_mcontext.gregs[REG_RIP];
> +
> +       if (((ip ^ 0xffffffffff600000UL) & ~0xfffUL) == 0)
> +               num_vsyscall_traps++;
> +}
> +
> +static int test_native_vsyscall(void)
> +{
> +       time_t tmp;
> +       bool is_native;
> +
> +       if (!vtime)
> +               return 0;
> +
> +       printf("[RUN]\tchecking for native vsyscall\n");
> +       sethandler(SIGTRAP, sigtrap, 0);
> +       set_eflags(get_eflags() | X86_EFLAGS_TF);
> +       vtime(&tmp);
> +       set_eflags(get_eflags() & ~X86_EFLAGS_TF);
> +
> +       /*
> +        * If vsyscalls are emulated, we expect a single trap in the
> +        * vsyscall page -- the call instruction will trap with RIP
> +        * pointing to the entry point before emulation takes over.
> +        * In native mode, we expect two traps, since whatever code
> +        * the vsyscall page contains will be more than just a ret
> +        * instruction.
> +        */
> +       is_native = (num_vsyscall_traps > 1);
> +
> +       printf("\tvsyscalls are %s (%d instructions in vsyscall page)\n",
> +              (is_native ? "native" : "emulated"),
> +              (int)num_vsyscall_traps);
> +
> +       return 0;
> +}
> +#endif
> +
> +int main(int argc, char **argv)
> +{
> +       int nerrs = 0;
> +
> +       init_vdso();
> +       nerrs += init_vsys();
> +
> +       nerrs += test_gtod();
> +       nerrs += test_time();
> +       nerrs += test_getcpu(0);
> +       nerrs += test_getcpu(1);
> +
> +       sethandler(SIGSEGV, sigsegv, 0);
> +       nerrs += test_vsys_r();
> +
> +#ifdef __x86_64__
> +       nerrs += test_native_vsyscall();
> +#endif
> +
> +       return nerrs ? 1 : 0;
> +}
> --
> 2.13.6
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kselftest" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] selftests/x86: Add test_vsyscall
  2018-01-12 12:57 ` Ingo Molnar
@ 2018-01-13  6:29   ` Andy Lutomirski
  2018-01-13 10:20     ` Ingo Molnar
  0 siblings, 1 reply; 12+ messages in thread
From: Andy Lutomirski @ 2018-01-13  6:29 UTC (permalink / raw)
  To: Ingo Molnar
  Cc: Andy Lutomirski, X86 ML, LKML, Borislav Petkov, Kees Cook,
	Peter Zijlstra, Greg Kroah-Hartman, Hugh Dickins, stable

On Fri, Jan 12, 2018 at 4:57 AM, Ingo Molnar <mingo@kernel.org> wrote:
>
> * Andy Lutomirski <luto@kernel.org> wrote:
>
>> tools/testing/selftests/x86/Makefile        |   2 +-
>>  tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
>>  2 files changed, 501 insertions(+), 1 deletion(-)
>>  create mode 100644 tools/testing/selftests/x86/test_vsyscall.c
>
> Hm, it doesn't even build here, on Ubuntu latest (Artful):
>
> triton:~/tip/tools/testing/selftests/x86> make test_vsyscall
> gcc -O2 -g -std=gnu99 -pthread -Wall -no-pie    test_vsyscall.c   -o test_vsyscall
> /tmp/ccKyelfb.o: In function `init_vdso':
> /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:68: undefined reference to `dlopen'
> /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:76: undefined reference to `dlsym'
> /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:80: undefined reference to `dlsym'
> /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:84: undefined reference to `dlsym'
> /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:88: undefined reference to `dlsym'
> /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:70: undefined reference to `dlopen'
> collect2: error: ld returned 1 exit status
> <builtin>: recipe for target 'test_vsyscall' failed
> make: *** [test_vsyscall] Error 1
>

You're not supposed to do that :)  The targets are test_vsyscall_32
and test_vsyscall_64 or just plain make.  I think you're just ending
up with make's default rule to try to build it and it's not working.

We could try adding something like:

# The x86 selftests have files like foo.c that don't produce binaries
# called foo (they're foo_32 and foo_64 instead).  Prevent confusion
# if someone types 'make foo'.
% : %.c
% : %.o

to tools/testing/selftests/x86/Makefile, but that's a bit gross.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH] selftests/x86: Add test_vsyscall
  2018-01-13  6:29   ` Andy Lutomirski
@ 2018-01-13 10:20     ` Ingo Molnar
  0 siblings, 0 replies; 12+ messages in thread
From: Ingo Molnar @ 2018-01-13 10:20 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: X86 ML, LKML, Borislav Petkov, Kees Cook, Peter Zijlstra,
	Greg Kroah-Hartman, Hugh Dickins, stable


* Andy Lutomirski <luto@kernel.org> wrote:

> On Fri, Jan 12, 2018 at 4:57 AM, Ingo Molnar <mingo@kernel.org> wrote:
> >
> > * Andy Lutomirski <luto@kernel.org> wrote:
> >
> >> tools/testing/selftests/x86/Makefile        |   2 +-
> >>  tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
> >>  2 files changed, 501 insertions(+), 1 deletion(-)
> >>  create mode 100644 tools/testing/selftests/x86/test_vsyscall.c
> >
> > Hm, it doesn't even build here, on Ubuntu latest (Artful):
> >
> > triton:~/tip/tools/testing/selftests/x86> make test_vsyscall
> > gcc -O2 -g -std=gnu99 -pthread -Wall -no-pie    test_vsyscall.c   -o test_vsyscall
> > /tmp/ccKyelfb.o: In function `init_vdso':
> > /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:68: undefined reference to `dlopen'
> > /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:76: undefined reference to `dlsym'
> > /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:80: undefined reference to `dlsym'
> > /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:84: undefined reference to `dlsym'
> > /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:88: undefined reference to `dlsym'
> > /home/mingo/tip/tools/testing/selftests/x86/test_vsyscall.c:70: undefined reference to `dlopen'
> > collect2: error: ld returned 1 exit status
> > <builtin>: recipe for target 'test_vsyscall' failed
> > make: *** [test_vsyscall] Error 1
> >
> 
> You're not supposed to do that :)  The targets are test_vsyscall_32
> and test_vsyscall_64 or just plain make.  I think you're just ending
> up with make's default rule to try to build it and it's not working.

D'oh, of course! :-)

> We could try adding something like:
> 
> # The x86 selftests have files like foo.c that don't produce binaries
> # called foo (they're foo_32 and foo_64 instead).  Prevent confusion
> # if someone types 'make foo'.
> % : %.c
> % : %.o
> 
> to tools/testing/selftests/x86/Makefile, but that's a bit gross.

Well, we could make it to produce the proper targets, which wouldn't be gross.

But it's probably overkill - I've applied your patch.

Thanks,

	Ingo

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tip:x86/pti] selftests/x86: Add test_vsyscall
  2018-01-12  1:16 [PATCH] selftests/x86: Add test_vsyscall Andy Lutomirski
                   ` (4 preceding siblings ...)
  2018-01-13  6:22   ` Andy Lutomirski
@ 2018-01-13 10:28 ` tip-bot for Andy Lutomirski
  5 siblings, 0 replies; 12+ messages in thread
From: tip-bot for Andy Lutomirski @ 2018-01-13 10:28 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: keescook, gregkh, dave.hansen, mingo, tglx, jgross, jpoimboe, bp,
	luto, hpa, peterz, hughd, torvalds, linux-kernel

Commit-ID:  352909b49ba0d74929b96af6dfbefc854ab6ebb5
Gitweb:     https://git.kernel.org/tip/352909b49ba0d74929b96af6dfbefc854ab6ebb5
Author:     Andy Lutomirski <luto@kernel.org>
AuthorDate: Thu, 11 Jan 2018 17:16:51 -0800
Committer:  Ingo Molnar <mingo@kernel.org>
CommitDate: Sat, 13 Jan 2018 11:23:03 +0100

selftests/x86: Add test_vsyscall

This tests that the vsyscall entries do what they're expected to do.
It also confirms that attempts to read the vsyscall page behave as
expected.

If changes are made to the vsyscall code or its memory map handling,
running this test in all three of vsyscall=none, vsyscall=emulate,
and vsyscall=native are helpful.

(Because it's easy, this also compares the vsyscall results to their
 vDSO equivalents.)

Note to KAISER backporters: please test this under all three
vsyscall modes.  Also, in the emulate and native modes, make sure
that test_vsyscall_64 agrees with the command line or config
option as to which mode you're in.  It's quite easy to mess up
the kernel such that native mode accidentally emulates
or vice versa.

Greg, etc: please backport this to all your Meltdown-patched
kernels.  It'll help make sure the patches didn't regress
vsyscalls.

CSigned-off-by: Andy Lutomirski <luto@kernel.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Juergen Gross <jgross@suse.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: stable@vger.kernel.org
Link: http://lkml.kernel.org/r/2b9c5a174c1d60fd7774461d518aa75598b1d8fd.1515719552.git.luto@kernel.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>
---
 tools/testing/selftests/x86/Makefile        |   2 +-
 tools/testing/selftests/x86/test_vsyscall.c | 500 ++++++++++++++++++++++++++++
 2 files changed, 501 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 7b1adee..91fbfa8 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -7,7 +7,7 @@ include ../lib.mk
 
 TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_syscall test_mremap_vdso \
 			check_initial_reg_state sigreturn ldt_gdt iopl mpx-mini-test ioperm \
-			protection_keys test_vdso
+			protection_keys test_vdso test_vsyscall
 TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \
 			test_FCMOV test_FCOMI test_FISTTP \
 			vdso_restorer
diff --git a/tools/testing/selftests/x86/test_vsyscall.c b/tools/testing/selftests/x86/test_vsyscall.c
new file mode 100644
index 0000000..7a744fa
--- /dev/null
+++ b/tools/testing/selftests/x86/test_vsyscall.c
@@ -0,0 +1,500 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <sys/ucontext.h>
+#include <errno.h>
+#include <err.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <setjmp.h>
+
+#ifdef __x86_64__
+# define VSYS(x) (x)
+#else
+# define VSYS(x) 0
+#endif
+
+#ifndef SYS_getcpu
+# ifdef __x86_64__
+#  define SYS_getcpu 309
+# else
+#  define SYS_getcpu 318
+# endif
+#endif
+
+static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
+		       int flags)
+{
+	struct sigaction sa;
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_sigaction = handler;
+	sa.sa_flags = SA_SIGINFO | flags;
+	sigemptyset(&sa.sa_mask);
+	if (sigaction(sig, &sa, 0))
+		err(1, "sigaction");
+}
+
+/* vsyscalls and vDSO */
+bool should_read_vsyscall = false;
+
+typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
+gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000);
+gtod_t vdso_gtod;
+
+typedef int (*vgettime_t)(clockid_t, struct timespec *);
+vgettime_t vdso_gettime;
+
+typedef long (*time_func_t)(time_t *t);
+time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400);
+time_func_t vdso_time;
+
+typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
+getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800);
+getcpu_t vdso_getcpu;
+
+static void init_vdso(void)
+{
+	void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+	if (!vdso)
+		vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+	if (!vdso) {
+		printf("[WARN]\tfailed to find vDSO\n");
+		return;
+	}
+
+	vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday");
+	if (!vdso_gtod)
+		printf("[WARN]\tfailed to find gettimeofday in vDSO\n");
+
+	vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
+	if (!vdso_gettime)
+		printf("[WARN]\tfailed to find clock_gettime in vDSO\n");
+
+	vdso_time = (time_func_t)dlsym(vdso, "__vdso_time");
+	if (!vdso_time)
+		printf("[WARN]\tfailed to find time in vDSO\n");
+
+	vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
+	if (!vdso_getcpu) {
+		/* getcpu() was never wired up in the 32-bit vDSO. */
+		printf("[%s]\tfailed to find getcpu in vDSO\n",
+		       sizeof(long) == 8 ? "WARN" : "NOTE");
+	}
+}
+
+static int init_vsys(void)
+{
+#ifdef __x86_64__
+	int nerrs = 0;
+	FILE *maps;
+	char line[128];
+	bool found = false;
+
+	maps = fopen("/proc/self/maps", "r");
+	if (!maps) {
+		printf("[WARN]\tCould not open /proc/self/maps -- assuming vsyscall is r-x\n");
+		should_read_vsyscall = true;
+		return 0;
+	}
+
+	while (fgets(line, sizeof(line), maps)) {
+		char r, x;
+		void *start, *end;
+		char name[128];
+		if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
+			   &start, &end, &r, &x, name) != 5)
+			continue;
+
+		if (strcmp(name, "[vsyscall]"))
+			continue;
+
+		printf("\tvsyscall map: %s", line);
+
+		if (start != (void *)0xffffffffff600000 ||
+		    end != (void *)0xffffffffff601000) {
+			printf("[FAIL]\taddress range is nonsense\n");
+			nerrs++;
+		}
+
+		printf("\tvsyscall permissions are %c-%c\n", r, x);
+		should_read_vsyscall = (r == 'r');
+		if (x != 'x') {
+			vgtod = NULL;
+			vtime = NULL;
+			vgetcpu = NULL;
+		}
+
+		found = true;
+		break;
+	}
+
+	fclose(maps);
+
+	if (!found) {
+		printf("\tno vsyscall map in /proc/self/maps\n");
+		should_read_vsyscall = false;
+		vgtod = NULL;
+		vtime = NULL;
+		vgetcpu = NULL;
+	}
+
+	return nerrs;
+#else
+	return 0;
+#endif
+}
+
+/* syscalls */
+static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
+{
+	return syscall(SYS_gettimeofday, tv, tz);
+}
+
+static inline int sys_clock_gettime(clockid_t id, struct timespec *ts)
+{
+	return syscall(SYS_clock_gettime, id, ts);
+}
+
+static inline long sys_time(time_t *t)
+{
+	return syscall(SYS_time, t);
+}
+
+static inline long sys_getcpu(unsigned * cpu, unsigned * node,
+			      void* cache)
+{
+	return syscall(SYS_getcpu, cpu, node, cache);
+}
+
+static jmp_buf jmpbuf;
+
+static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
+{
+	siglongjmp(jmpbuf, 1);
+}
+
+static double tv_diff(const struct timeval *a, const struct timeval *b)
+{
+	return (double)(a->tv_sec - b->tv_sec) +
+		(double)((int)a->tv_usec - (int)b->tv_usec) * 1e-6;
+}
+
+static int check_gtod(const struct timeval *tv_sys1,
+		      const struct timeval *tv_sys2,
+		      const struct timezone *tz_sys,
+		      const char *which,
+		      const struct timeval *tv_other,
+		      const struct timezone *tz_other)
+{
+	int nerrs = 0;
+	double d1, d2;
+
+	if (tz_other && (tz_sys->tz_minuteswest != tz_other->tz_minuteswest || tz_sys->tz_dsttime != tz_other->tz_dsttime)) {
+		printf("[FAIL] %s tz mismatch\n", which);
+		nerrs++;
+	}
+
+	d1 = tv_diff(tv_other, tv_sys1);
+	d2 = tv_diff(tv_sys2, tv_other); 
+	printf("\t%s time offsets: %lf %lf\n", which, d1, d2);
+
+	if (d1 < 0 || d2 < 0) {
+		printf("[FAIL]\t%s time was inconsistent with the syscall\n", which);
+		nerrs++;
+	} else {
+		printf("[OK]\t%s gettimeofday()'s timeval was okay\n", which);
+	}
+
+	return nerrs;
+}
+
+static int test_gtod(void)
+{
+	struct timeval tv_sys1, tv_sys2, tv_vdso, tv_vsys;
+	struct timezone tz_sys, tz_vdso, tz_vsys;
+	long ret_vdso = -1;
+	long ret_vsys = -1;
+	int nerrs = 0;
+
+	printf("[RUN]\ttest gettimeofday()\n");
+
+	if (sys_gtod(&tv_sys1, &tz_sys) != 0)
+		err(1, "syscall gettimeofday");
+	if (vdso_gtod)
+		ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso);
+	if (vgtod)
+		ret_vsys = vgtod(&tv_vsys, &tz_vsys);
+	if (sys_gtod(&tv_sys2, &tz_sys) != 0)
+		err(1, "syscall gettimeofday");
+
+	if (vdso_gtod) {
+		if (ret_vdso == 0) {
+			nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vDSO", &tv_vdso, &tz_vdso);
+		} else {
+			printf("[FAIL]\tvDSO gettimeofday() failed: %ld\n", ret_vdso);
+			nerrs++;
+		}
+	}
+
+	if (vgtod) {
+		if (ret_vsys == 0) {
+			nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys);
+		} else {
+			printf("[FAIL]\tvsys gettimeofday() failed: %ld\n", ret_vsys);
+			nerrs++;
+		}
+	}
+
+	return nerrs;
+}
+
+static int test_time(void) {
+	int nerrs = 0;
+
+	printf("[RUN]\ttest time()\n");
+	long t_sys1, t_sys2, t_vdso = 0, t_vsys = 0;
+	long t2_sys1 = -1, t2_sys2 = -1, t2_vdso = -1, t2_vsys = -1;
+	t_sys1 = sys_time(&t2_sys1);
+	if (vdso_time)
+		t_vdso = vdso_time(&t2_vdso);
+	if (vtime)
+		t_vsys = vtime(&t2_vsys);
+	t_sys2 = sys_time(&t2_sys2);
+	if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) {
+		printf("[FAIL]\tsyscall failed (ret1:%ld output1:%ld ret2:%ld output2:%ld)\n", t_sys1, t2_sys1, t_sys2, t2_sys2);
+		nerrs++;
+		return nerrs;
+	}
+
+	if (vdso_time) {
+		if (t_vdso < 0 || t_vdso != t2_vdso) {
+			printf("[FAIL]\tvDSO failed (ret:%ld output:%ld)\n", t_vdso, t2_vdso);
+			nerrs++;
+		} else if (t_vdso < t_sys1 || t_vdso > t_sys2) {
+			printf("[FAIL]\tvDSO returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vdso, t_sys2);
+			nerrs++;
+		} else {
+			printf("[OK]\tvDSO time() is okay\n");
+		}
+	}
+
+	if (vtime) {
+		if (t_vsys < 0 || t_vsys != t2_vsys) {
+			printf("[FAIL]\tvsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys);
+			nerrs++;
+		} else if (t_vsys < t_sys1 || t_vsys > t_sys2) {
+			printf("[FAIL]\tvsyscall returned the wrong time (%ld %ld %ld)\n", t_sys1, t_vsys, t_sys2);
+			nerrs++;
+		} else {
+			printf("[OK]\tvsyscall time() is okay\n");
+		}
+	}
+
+	return nerrs;
+}
+
+static int test_getcpu(int cpu)
+{
+	int nerrs = 0;
+	long ret_sys, ret_vdso = -1, ret_vsys = -1;
+
+	printf("[RUN]\tgetcpu() on CPU %d\n", cpu);
+
+	cpu_set_t cpuset;
+	CPU_ZERO(&cpuset);
+	CPU_SET(cpu, &cpuset);
+	if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
+		printf("[SKIP]\tfailed to force CPU %d\n", cpu);
+		return nerrs;
+	}
+
+	unsigned cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys;
+	unsigned node = 0;
+	bool have_node = false;
+	ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
+	if (vdso_getcpu)
+		ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
+	if (vgetcpu)
+		ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
+
+	if (ret_sys == 0) {
+		if (cpu_sys != cpu) {
+			printf("[FAIL]\tsyscall reported CPU %hu but should be %d\n", cpu_sys, cpu);
+			nerrs++;
+		}
+
+		have_node = true;
+		node = node_sys;
+	}
+
+	if (vdso_getcpu) {
+		if (ret_vdso) {
+			printf("[FAIL]\tvDSO getcpu() failed\n");
+			nerrs++;
+		} else {
+			if (!have_node) {
+				have_node = true;
+				node = node_vdso;
+			}
+
+			if (cpu_vdso != cpu) {
+				printf("[FAIL]\tvDSO reported CPU %hu but should be %d\n", cpu_vdso, cpu);
+				nerrs++;
+			} else {
+				printf("[OK]\tvDSO reported correct CPU\n");
+			}
+
+			if (node_vdso != node) {
+				printf("[FAIL]\tvDSO reported node %hu but should be %hu\n", node_vdso, node);
+				nerrs++;
+			} else {
+				printf("[OK]\tvDSO reported correct node\n");
+			}
+		}
+	}
+
+	if (vgetcpu) {
+		if (ret_vsys) {
+			printf("[FAIL]\tvsyscall getcpu() failed\n");
+			nerrs++;
+		} else {
+			if (!have_node) {
+				have_node = true;
+				node = node_vsys;
+			}
+
+			if (cpu_vsys != cpu) {
+				printf("[FAIL]\tvsyscall reported CPU %hu but should be %d\n", cpu_vsys, cpu);
+				nerrs++;
+			} else {
+				printf("[OK]\tvsyscall reported correct CPU\n");
+			}
+
+			if (node_vsys != node) {
+				printf("[FAIL]\tvsyscall reported node %hu but should be %hu\n", node_vsys, node);
+				nerrs++;
+			} else {
+				printf("[OK]\tvsyscall reported correct node\n");
+			}
+		}
+	}
+
+	return nerrs;
+}
+
+static int test_vsys_r(void)
+{
+#ifdef __x86_64__
+	printf("[RUN]\tChecking read access to the vsyscall page\n");
+	bool can_read;
+	if (sigsetjmp(jmpbuf, 1) == 0) {
+		*(volatile int *)0xffffffffff600000;
+		can_read = true;
+	} else {
+		can_read = false;
+	}
+
+	if (can_read && !should_read_vsyscall) {
+		printf("[FAIL]\tWe have read access, but we shouldn't\n");
+		return 1;
+	} else if (!can_read && should_read_vsyscall) {
+		printf("[FAIL]\tWe don't have read access, but we should\n");
+		return 1;
+	} else {
+		printf("[OK]\tgot expected result\n");
+	}
+#endif
+
+	return 0;
+}
+
+
+#ifdef __x86_64__
+#define X86_EFLAGS_TF (1UL << 8)
+static volatile sig_atomic_t num_vsyscall_traps;
+
+static unsigned long get_eflags(void)
+{
+	unsigned long eflags;
+	asm volatile ("pushfq\n\tpopq %0" : "=rm" (eflags));
+	return eflags;
+}
+
+static void set_eflags(unsigned long eflags)
+{
+	asm volatile ("pushq %0\n\tpopfq" : : "rm" (eflags) : "flags");
+}
+
+static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
+{
+	ucontext_t *ctx = (ucontext_t *)ctx_void;
+	unsigned long ip = ctx->uc_mcontext.gregs[REG_RIP];
+
+	if (((ip ^ 0xffffffffff600000UL) & ~0xfffUL) == 0)
+		num_vsyscall_traps++;
+}
+
+static int test_native_vsyscall(void)
+{
+	time_t tmp;
+	bool is_native;
+
+	if (!vtime)
+		return 0;
+
+	printf("[RUN]\tchecking for native vsyscall\n");
+	sethandler(SIGTRAP, sigtrap, 0);
+	set_eflags(get_eflags() | X86_EFLAGS_TF);
+	vtime(&tmp);
+	set_eflags(get_eflags() & ~X86_EFLAGS_TF);
+
+	/*
+	 * If vsyscalls are emulated, we expect a single trap in the
+	 * vsyscall page -- the call instruction will trap with RIP
+	 * pointing to the entry point before emulation takes over.
+	 * In native mode, we expect two traps, since whatever code
+	 * the vsyscall page contains will be more than just a ret
+	 * instruction.
+	 */
+	is_native = (num_vsyscall_traps > 1);
+
+	printf("\tvsyscalls are %s (%d instructions in vsyscall page)\n",
+	       (is_native ? "native" : "emulated"),
+	       (int)num_vsyscall_traps);
+
+	return 0;
+}
+#endif
+
+int main(int argc, char **argv)
+{
+	int nerrs = 0;
+
+	init_vdso();
+	nerrs += init_vsys();
+
+	nerrs += test_gtod();
+	nerrs += test_time();
+	nerrs += test_getcpu(0);
+	nerrs += test_getcpu(1);
+
+	sethandler(SIGSEGV, sigsegv, 0);
+	nerrs += test_vsys_r();
+
+#ifdef __x86_64__
+	nerrs += test_native_vsyscall();
+#endif
+
+	return nerrs ? 1 : 0;
+}

^ permalink raw reply related	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2018-01-13 10:35 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-01-12  1:16 [PATCH] selftests/x86: Add test_vsyscall Andy Lutomirski
2018-01-12  4:12 ` Kees Cook
2018-01-12  7:32 ` Greg Kroah-Hartman
2018-01-12 12:45 ` Ingo Molnar
2018-01-12 12:57 ` Ingo Molnar
2018-01-13  6:29   ` Andy Lutomirski
2018-01-13 10:20     ` Ingo Molnar
2018-01-13  6:22 ` Andy Lutomirski
2018-01-13  6:22   ` [Linux-kselftest-mirror] " Andy Lutomirski
2018-01-13  6:22   ` luto
2018-01-13  6:22   ` Andy Lutomirski
2018-01-13 10:28 ` [tip:x86/pti] " tip-bot for Andy Lutomirski

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.