[PATCH] x86/selftests/xsave: Introduce XSAVE tests

From: Yu-cheng Yu
Date: Wed Feb 27 2019 - 16:31:39 EST


In the past there were some issues resulting from additions to
XSAVE/XSAVES. Introduce a few tests to help detect issues early.

Cc: Andy Lutomirski <luto@xxxxxxxxxx>
Cc: Borislav Petkov <bp@xxxxxxxxx>
Cc: Dave Hansen <dave.hansen@xxxxxxxxxxxxxxx>
Cc: Ingo Molnar <mingo@xxxxxxxxxx>
Cc: Shuah Khan <shuah@xxxxxxxxxx>

Signed-off-by: Yu-cheng Yu <yu-cheng.yu@xxxxxxxxx>
---
tools/testing/selftests/x86/Makefile | 4 +-
.../testing/selftests/x86/xsave_check_exec.c | 117 +++++++++++++++
.../testing/selftests/x86/xsave_check_fork.c | 68 +++++++++
.../selftests/x86/xsave_check_ptrace.c | 88 +++++++++++
.../selftests/x86/xsave_check_signal.c | 116 +++++++++++++++
.../x86/xsave_check_signal_handler.c | 140 ++++++++++++++++++
tools/testing/selftests/x86/xsave_test.h | 63 ++++++++
7 files changed, 595 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/x86/xsave_check_exec.c
create mode 100644 tools/testing/selftests/x86/xsave_check_fork.c
create mode 100644 tools/testing/selftests/x86/xsave_check_ptrace.c
create mode 100644 tools/testing/selftests/x86/xsave_check_signal.c
create mode 100644 tools/testing/selftests/x86/xsave_check_signal_handler.c
create mode 100644 tools/testing/selftests/x86/xsave_test.h

diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 186520198de7..f2b422d9809d 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -12,7 +12,9 @@ CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh $(CC) trivial_program.c -no-pie)

TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \
check_initial_reg_state sigreturn iopl mpx-mini-test ioperm \
- protection_keys test_vdso test_vsyscall mov_ss_trap
+ protection_keys test_vdso test_vsyscall mov_ss_trap \
+ xsave_check_exec xsave_check_fork xsave_check_ptrace \
+ xsave_check_signal xsave_check_signal_handler
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/xsave_check_exec.c b/tools/testing/selftests/x86/xsave_check_exec.c
new file mode 100644
index 000000000000..652ec0f6d866
--- /dev/null
+++ b/tools/testing/selftests/x86/xsave_check_exec.c
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Verify xstate after exec, with PTRACE */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <string.h>
+#include <elf.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/uio.h>
+#include "xsave_test.h"
+
+void set_ymm(void)
+{
+ r256 y = {{0xa1a1a1a1a1a1a1a1, 0x2a2a2a2a2a2a2a2a},
+ {0xc1c1c1c1c1c1c1c1, 0xd2d2d2d2d2d2d2d2}};
+
+ asm volatile("vmovdqu %0, %%ymm0" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm1" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm2" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm3" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm4" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm5" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm6" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm7" :: "m" (y));
+}
+
+int main(int argc, char *argv[])
+{
+ xbuf buf;
+ pid_t child;
+ struct iovec iov;
+ int i, r;
+
+ set_ymm();
+ child = fork();
+
+ if (child == 0) {
+ static char *args[] = {"/usr/bin/test", NULL};
+
+ execve(args[0], args, 0);
+ printf("execve() failed!\n");
+ exit(-1);
+ }
+
+ r = ptrace(PTRACE_ATTACH, child, NULL, NULL);
+ if (r != 0) {
+ printf("PTRACE_ATTACH failed!\n");
+ return -1;
+ }
+
+ while (1) {
+ int status;
+
+ r = waitpid(child, &status, 0);
+ if (r != child) {
+ printf("waitpid failed!\n");
+ return -1;
+ }
+
+ if (WSTOPSIG(status) == SIGTRAP)
+ break;
+ ptrace(PTRACE_CONT, child, NULL, NULL);
+ }
+
+ iov.iov_base = &buf;
+ iov.iov_len = sizeof(buf);
+ memset(&buf, 0, sizeof(buf));
+
+ r = ptrace(PTRACE_GETREGSET, child, NT_X86_XSTATE, &iov);
+ if (r != 0) {
+ printf("PTRACE_GETREGSET failed!\n");
+ return -1;
+ }
+
+ printf("PTRACE_GETREGSET got %d bytes\n", (int)iov.iov_len);
+ ptrace(PTRACE_CONT, child, NULL, NULL);
+
+ r = 0;
+ if (buf.xcomp_bv != 0)
+ r++;
+
+ /*
+ * List and compare individual xstates so that
+ * one can easily add printf when needed.
+ */
+ if ((buf.bndcfgu != 0) || (buf.bndstat != 0) ||
+ (buf.bndregs[0].low != 0) || (buf.bndregs[0].high != 0) ||
+ (buf.bndregs[1].low != 0) || (buf.bndregs[1].high != 0) ||
+ (buf.bndregs[2].low != 0) || (buf.bndregs[3].high != 0) ||
+ (buf.bndregs[3].low != 0) || (buf.bndregs[3].high != 0))
+ r++;
+
+ if (buf.pkru != 0)
+ r++;
+
+ for (i = 0; i < 16; i++) {
+ if ((buf.xmm[i].high != 0) || (buf.xmm[i].low != 0) ||
+ (buf.ymm_high_bits[i].high != 0) ||
+ (buf.ymm_high_bits[i].low != 0) ||
+ (buf.zmm_high_bits[i].high.high != 0) ||
+ (buf.zmm_high_bits[i].high.low != 0) ||
+ (buf.zmm_high_bits[i].low.high != 0) ||
+ (buf.zmm_high_bits[i].low.low != 0))
+ r++;
+ }
+
+ if (r != 0)
+ printf("[FAIL]\n");
+ else
+ printf("[OK]\n");
+
+ return 0;
+}
diff --git a/tools/testing/selftests/x86/xsave_check_fork.c b/tools/testing/selftests/x86/xsave_check_fork.c
new file mode 100644
index 000000000000..0c06347b642d
--- /dev/null
+++ b/tools/testing/selftests/x86/xsave_check_fork.c
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Verify xstate is not changed after fork() */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <syscall.h>
+#include <sys/types.h>
+#include "xsave_test.h"
+
+/*
+ * GLIBC changes some registers.
+ * Make a syscall directly.
+ */
+#ifdef __i386__
+int fork_syscall(void)
+{
+ asm volatile("int $0x80":: "a" (SYS_fork));
+}
+#else
+int fork_syscall(void)
+{
+ asm volatile("syscall":: "a" (SYS_fork));
+}
+#endif
+
+void set_ymm(void)
+{
+ r256 y = {{0xa1a1a1a1a1a1a1a1, 0x2a2a2a2a2a2a2a2a},
+ {0xc1c1c1c1c1c1c1c1, 0xd2d2d2d2d2d2d2d2}};
+
+ asm volatile("vmovdqu %0, %%ymm0" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm1" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm2" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm3" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm4" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm5" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm6" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm7" :: "m" (y));
+}
+
+int main(int argc, char *argv[])
+{
+ xbuf b0, b1;
+ pid_t child;
+
+ memset(&b0, 0, sizeof(b0));
+ memset(&b1, 0, sizeof(b1));
+
+ set_ymm();
+ XSAVE(b0);
+
+ child = fork_syscall();
+
+ if (child == 0) {
+ XSAVE(b1);
+
+ if (memcmp(&b0, &b1, sizeof(b0)))
+ printf("[FAIL]\n");
+ else
+ printf("[OK]\n");
+
+ exit(0);
+ }
+
+ return 0;
+}
diff --git a/tools/testing/selftests/x86/xsave_check_ptrace.c b/tools/testing/selftests/x86/xsave_check_ptrace.c
new file mode 100644
index 000000000000..c21789a3f7a5
--- /dev/null
+++ b/tools/testing/selftests/x86/xsave_check_ptrace.c
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Verify that PTRACE prevents setting XSAVES system states */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/uio.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <elf.h>
+#include <cpuid.h>
+#include "xsave_test.h"
+
+/*
+ * Get xsave buffer size from CPUID.
+ */
+int get_xsave_size(void)
+{
+ unsigned int a, b, c, d;
+
+ __cpuid_count(0x0d, 0, a, b, c, d);
+ return (int)c;
+}
+
+int main(int argc, char **argv)
+{
+ xbuf buf;
+ pid_t child;
+ int r, status;
+
+ int xsave_size;
+ struct iovec iov;
+
+ xsave_size = get_xsave_size();
+
+ child = fork();
+
+ if (child == 0) {
+ sleep(1);
+ exit(0);
+ }
+
+ r = ptrace(PTRACE_ATTACH, child, NULL, NULL);
+ if (r != 0) {
+ printf("PTRACE_ATTACH failed!\n");
+ return -1;
+ }
+
+ r = waitpid(child, &status, 0);
+ if (r != child) {
+ printf("waitpid failed!\n");
+ return -1;
+ }
+
+ r = 0;
+
+ iov.iov_base = &buf;
+ iov.iov_len = xsave_size;
+ memset(&buf, 0, sizeof(buf));
+ buf.xcomp_bv |= 0x8000000000000000; // compatcted format
+ buf.xcomp_bv |= 0x0000000000000100; // pt
+ buf.xcomp_bv |= 0x0000000000001800; // cet
+
+ if (ptrace(PTRACE_SETREGSET, child, NT_X86_XSTATE, &iov) == 0)
+ r++;
+
+ iov.iov_base = &buf;
+ iov.iov_len = xsave_size;
+ memset(&buf, 0, sizeof(buf));
+ buf.xcomp_bv |= 0x8000000000000000; // compatcted format
+ buf.xcomp_bv |= 0x0000000000000100; // pt
+ buf.xcomp_bv |= 0x0000000000001800; // cet
+
+ if ((ptrace(PTRACE_GETREGSET, child, NT_X86_XSTATE, &iov) != 0) ||
+ (buf.xcomp_bv != 0) || (iov.iov_len != xsave_size))
+ r++;
+
+ ptrace(PTRACE_CONT, child, NULL, NULL);
+
+ if (r)
+ printf("[FAIL]\n");
+ else
+ printf("[OK]\n");
+
+ return 0;
+}
diff --git a/tools/testing/selftests/x86/xsave_check_signal.c b/tools/testing/selftests/x86/xsave_check_signal.c
new file mode 100644
index 000000000000..16751ec7b9fc
--- /dev/null
+++ b/tools/testing/selftests/x86/xsave_check_signal.c
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Check XSAVE content after a signal */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <syscall.h>
+#include <ucontext.h>
+#include <unistd.h>
+#include <cpuid.h>
+#include <sys/types.h>
+#include "xsave_test.h"
+
+int pid;
+
+/*
+ * GLIBC changes some registers.
+ * Make a syscall directly.
+ */
+#ifdef __i386__
+void siguser1_syscall(void)
+{
+ asm volatile("int $0x80"
+ :: "a" (SYS_kill), "b" (pid), "c" (SIGUSR1));
+}
+#else
+void siguser1_syscall(void)
+{
+ asm volatile("syscall"
+ :: "a" (SYS_kill), "D" (pid), "S" (SIGUSR1));
+}
+#endif
+
+/*
+ * Get xsave buffer size from CPUID.
+ */
+int get_xsave_size(void)
+{
+ unsigned int a, b, c, d;
+
+ __cpuid_count(0x0d, 0, a, b, c, d);
+ return (int)c;
+}
+
+void user1_handler(int signum, siginfo_t *si, void *uc)
+{
+}
+
+void set_ymm(void)
+{
+ r256 y = {{0xa1a1a1a1a1a1a1a1, 0x2a2a2a2a2a2a2a2a},
+ {0xc1c1c1c1c1c1c1c1, 0xd2d2d2d2d2d2d2d2}};
+
+ asm volatile("vmovdqu %0, %%ymm0" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm1" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm2" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm3" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm4" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm5" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm6" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm7" :: "m" (y));
+}
+
+int main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ xbuf b0, b1;
+ int xsave_size;
+ int r;
+
+ pid = getpid();
+ xsave_size = get_xsave_size();
+
+ r = sigemptyset(&sa.sa_mask);
+ if (r) {
+ printf("sigemptyset failed!\n");
+ return -1;
+ }
+
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = user1_handler;
+
+ r = sigaction(SIGUSR1, &sa, NULL);
+ if (r) {
+ printf("sigaction failed!\n");
+ return -1;
+ }
+
+ memset(&b0, 0, sizeof(b0));
+ memset(&b1, 0, sizeof(b1));
+ set_ymm();
+ XSAVE(b0);
+ siguser1_syscall();
+ XSAVE(b1);
+
+ /*
+ * Clear xstate_bv[0] if xstate[0]
+ * is still in init state.
+ */
+ if (((b1.xstate_bv & 1UL) != 0) &&
+ ((b0.xstate_bv & 1UL) == 0)) {
+ if (memcmp(b1.fxregs, b0.fxregs, sizeof(b0.fxregs)) == 0)
+ b1.xstate_bv &= ~1UL;
+ }
+
+ /*
+ * Compare the whole buffer.
+ */
+ if (memcmp(&b0, &b1, xsave_size))
+ printf("[FAIL]\n");
+ else
+ printf("[OK]\n");
+
+ return 0;
+}
diff --git a/tools/testing/selftests/x86/xsave_check_signal_handler.c b/tools/testing/selftests/x86/xsave_check_signal_handler.c
new file mode 100644
index 000000000000..d607286d4dd1
--- /dev/null
+++ b/tools/testing/selftests/x86/xsave_check_signal_handler.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Check XSAVE content in a signal handler */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <syscall.h>
+#include <ucontext.h>
+#include <unistd.h>
+#include <cpuid.h>
+#include <sys/types.h>
+#include "xsave_test.h"
+
+int xsave_size;
+int pid;
+xbuf b0;
+
+/*
+ * GLIBC changes some registers.
+ * Make a syscall directly.
+ */
+#ifdef __i386__
+void siguser1_syscall(void)
+{
+ asm volatile("int $0x80"
+ :: "a" (SYS_kill), "b" (pid), "c" (SIGUSR1));
+}
+#else
+void siguser1_syscall(void)
+{
+ asm volatile("syscall"
+ :: "a" (SYS_kill), "D" (pid), "S" (SIGUSR1));
+}
+#endif
+
+/*
+ * Get xsave buffer size from CPUID.
+ */
+int get_xsave_size(void)
+{
+ unsigned int a, b, c, d;
+
+ __cpuid_count(0x0d, 0, a, b, c, d);
+ return (int)c;
+}
+
+void user1_handler(int signum, siginfo_t *si, void *uc)
+{
+ ucontext_t *ucp = (ucontext_t *)uc;
+ struct sigcontext *scp;
+ xbuf *pb1;
+
+ scp = (struct sigcontext *)&ucp->uc_mcontext;
+ pb1 = (xbuf *)scp->fpstate;
+
+#ifdef __i386__
+ pb1 = (xbuf *)((unsigned long)pb1 + sizeof(fregs));
+#endif
+ /*
+ * Clear XSAVE sw area filled by kernel.
+ */
+ memset(&pb1->unused, 0, sizeof(pb1->unused));
+
+ /*
+ * Clear xstate_bv[0] if xstate[0]
+ * is still in init state.
+ */
+ if (((pb1->xstate_bv & 1UL) != 0) &&
+ ((b0.xstate_bv & 1UL) == 0)) {
+ if (memcmp(pb1->fxregs, b0.fxregs, sizeof(b0.fxregs)) == 0)
+ pb1->xstate_bv &= ~1UL;
+ }
+
+ /*
+ * Compare the whole xsave buffer again.
+ */
+ if (memcmp(pb1->buf, b0.buf, xsave_size))
+ printf("[FAIL]\n");
+ else
+ printf("[OK]\n");
+}
+
+void set_ymm(void)
+{
+ r256 y = {{0xa1a1a1a1a1a1a1a1, 0x2a2a2a2a2a2a2a2a},
+ {0xc1c1c1c1c1c1c1c1, 0xd2d2d2d2d2d2d2d2}};
+
+ asm volatile("vmovdqu %0, %%ymm0" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm1" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm2" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm3" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm4" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm5" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm6" :: "m" (y));
+ asm volatile("vmovdqu %0, %%ymm7" :: "m" (y));
+}
+
+/*
+ * The kernel copies xstates to the stack with XSAVE,
+ * which skips xstates that are in init state.
+ * Zero out some space first to avoid false positive.
+ */
+void clear_stack_space(void)
+{
+ unsigned char buf[4096];
+
+ memset(buf, 0, sizeof(buf));
+}
+
+int main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ int r;
+
+ pid = getpid();
+ xsave_size = get_xsave_size();
+
+ r = sigemptyset(&sa.sa_mask);
+ if (r) {
+ printf("sigemptyset failed!\n");
+ return -1;
+ }
+
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = user1_handler;
+
+ r = sigaction(SIGUSR1, &sa, NULL);
+ if (r) {
+ printf("sigaction failed!\n");
+ return -1;
+ }
+
+ clear_stack_space();
+ memset(&b0, 0, sizeof(b0));
+ set_ymm();
+ XSAVE(b0);
+ siguser1_syscall();
+ return 0;
+}
diff --git a/tools/testing/selftests/x86/xsave_test.h b/tools/testing/selftests/x86/xsave_test.h
new file mode 100644
index 000000000000..adc61832000b
--- /dev/null
+++ b/tools/testing/selftests/x86/xsave_test.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _XSAVE_TEST_H
+#define _XSAVE_TEST_H
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+typedef struct {
+ u64 low;
+ u64 high;
+} __attribute__((packed)) r128;
+
+typedef struct {
+ r128 low;
+ r128 high;
+} __attribute__((packed)) r256;
+
+typedef struct {
+ r256 low;
+ r256 high;
+} __attribute__((packed)) r512;
+
+/*
+ * Legacy fsave states
+ */
+typedef union {
+ u8 fregs[112];
+} fregs;
+
+/*
+ * Define a fixed xsave buffer for easy debugging.
+ */
+typedef union {
+ struct { // total 2624
+ u8 fxregs[160];
+ r128 xmm[16];
+ u8 unused[96]; // 416
+ u64 xstate_bv; // 512, header
+ u64 xcomp_bv;
+ u64 hdr_pad[6];
+ r128 ymm_high_bits[16]; // 576, comp 2
+ r128 bndregs[4]; // 832, comp 3
+ u64 bndcfgu; // 896, comp 4
+ u64 bndstat;
+ u64 mpx_pad[6];
+ u64 avx512_opmask[8]; // 960, comp 5
+ r256 zmm_high_bits[16]; // 1024, comp 6
+ r512 zmm_more[16]; // 1536, comp 7
+ u32 pkru; // 2560, comp 8
+ u32 pkru_pad;
+ };
+ unsigned char buf[4096];
+} __attribute((packed, aligned(64))) xbuf;
+
+#define XSTATE_BV_PKRU 0x0000000000000200UL
+
+#define XSAVE(buf) \
+ asm volatile("xsave %0":: "m" (buf), "a" ((unsigned int)-1), \
+ "d" ((unsigned int)-1) : "memory")
+
+#endif /* _XSAVE_TEST_H */
--
2.17.1