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 <l...@kernel.org>
Cc: Borislav Petkov <b...@alien8.de> 
Cc: Dave Hansen <dave.han...@linux.intel.com>
Cc: Ingo Molnar <mi...@kernel.org>
Cc: Shuah Khan <sh...@kernel.org>

Signed-off-by: Yu-cheng Yu <yu-cheng...@intel.com>
---
 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

Reply via email to