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 */
