• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2017 Google, Inc.
4  */
5 
6 /*
7  * Regression test for commit 814fb7bb7db5 ("x86/fpu: Don't let userspace set
8  * bogus xcomp_bv"), or CVE-2017-15537.  This bug allowed ptrace(pid,
9  * PTRACE_SETREGSET, NT_X86_XSTATE, &iov) to assign a task an invalid FPU state
10  * --- specifically, by setting reserved bits in xstate_header.xcomp_bv.  This
11  * made restoring the FPU registers fail when switching to the task, causing the
12  * FPU registers to take on the values from other tasks.
13  *
14  * To detect the bug, we have a subprocess run a loop checking its xmm0 register
15  * for corruption.  This detects the case where the FPU state became invalid and
16  * the kernel is not restoring the process's registers.  Note that we have to
17  * set the expected value of xmm0 to all 0's since it is acceptable behavior for
18  * the kernel to simply reinitialize the FPU state upon seeing that it is
19  * invalid.  To increase the chance of detecting the problem, we also create
20  * additional subprocesses that spin with different xmm0 contents.
21  *
22  * Thus bug affected the x86 architecture only.  Other architectures could have
23  * similar bugs as well, but this test has to be x86-specific because it has to
24  * know about the architecture-dependent FPU state.
25  */
26 
27 #include <errno.h>
28 #include <inttypes.h>
29 #include <sched.h>
30 #include <stdbool.h>
31 #include <stdlib.h>
32 #include <sys/uio.h>
33 #include <sys/wait.h>
34 
35 #include "config.h"
36 #include "ptrace.h"
37 #include "tst_test.h"
38 
39 #ifndef PTRACE_GETREGSET
40 # define PTRACE_GETREGSET 0x4204
41 #endif
42 
43 #ifndef PTRACE_SETREGSET
44 # define PTRACE_SETREGSET 0x4205
45 #endif
46 
47 #ifndef NT_X86_XSTATE
48 # define NT_X86_XSTATE 0x202
49 #endif
50 
51 #ifdef __x86_64__
check_regs_loop(uint32_t initval)52 static void check_regs_loop(uint32_t initval)
53 {
54 	const unsigned long num_iters = 1000000000;
55 	uint32_t xmm0[4] = { initval, initval, initval, initval };
56 	int status = 1;
57 
58 	asm volatile("   movdqu %0, %%xmm0\n"
59 		     "   mov %0, %%rbx\n"
60 		     "1: dec %2\n"
61 		     "   jz 2f\n"
62 		     "   movdqu %%xmm0, %0\n"
63 		     "   mov %0, %%rax\n"
64 		     "   cmp %%rax, %%rbx\n"
65 		     "   je 1b\n"
66 		     "   jmp 3f\n"
67 		     "2: mov $0, %1\n"
68 		     "3:\n"
69 		     : "+m" (xmm0), "+r" (status)
70 		     : "r" (num_iters) : "rax", "rbx", "xmm0");
71 
72 	if (status) {
73 		tst_res(TFAIL,
74 			"xmm registers corrupted!  initval=%08X, xmm0=%08X%08X%08X%08X\n",
75 			initval, xmm0[0], xmm0[1], xmm0[2], xmm0[3]);
76 	}
77 	exit(status);
78 }
79 
do_test(void)80 static void do_test(void)
81 {
82 	int i;
83 	int num_cpus = tst_ncpus();
84 	pid_t pid;
85 	uint64_t xstate[512];
86 	struct iovec iov = { .iov_base = xstate, .iov_len = sizeof(xstate) };
87 	int status;
88 	bool okay;
89 
90 	pid = SAFE_FORK();
91 	if (pid == 0) {
92 		TST_CHECKPOINT_WAKE(0);
93 		check_regs_loop(0x00000000);
94 	}
95 	for (i = 0; i < num_cpus; i++) {
96 		if (SAFE_FORK() == 0)
97 			check_regs_loop(0xDEADBEEF);
98 	}
99 
100 	TST_CHECKPOINT_WAIT(0);
101 	sched_yield();
102 
103 	TEST(ptrace(PTRACE_ATTACH, pid, 0, 0));
104 	if (TST_RET != 0)
105 		tst_brk(TBROK | TTERRNO, "PTRACE_ATTACH failed");
106 
107 	SAFE_WAITPID(pid, NULL, 0);
108 	TEST(ptrace(PTRACE_GETREGSET, pid, NT_X86_XSTATE, &iov));
109 	if (TST_RET != 0) {
110 		if (TST_ERR == EIO)
111 			tst_brk(TCONF, "GETREGSET/SETREGSET is unsupported");
112 
113 		if (TST_ERR == EINVAL)
114 			tst_brk(TCONF, "NT_X86_XSTATE is unsupported");
115 
116 		if (TST_ERR == ENODEV)
117 			tst_brk(TCONF, "CPU doesn't support XSAVE instruction");
118 
119 		tst_brk(TBROK | TTERRNO,
120 			"PTRACE_GETREGSET failed with unexpected error");
121 	}
122 
123 	xstate[65] = -1; /* sets all bits in xstate_header.xcomp_bv */
124 
125 	/*
126 	 * Old kernels simply masked out all the reserved bits in the xstate
127 	 * header (causing the PTRACE_SETREGSET command here to succeed), while
128 	 * new kernels will reject them (causing the PTRACE_SETREGSET command
129 	 * here to fail with EINVAL).  We accept either behavior, as neither
130 	 * behavior reliably tells us whether the real bug (which we test for
131 	 * below in either case) is present.
132 	 */
133 	TEST(ptrace(PTRACE_SETREGSET, pid, NT_X86_XSTATE, &iov));
134 	if (TST_RET == 0) {
135 		tst_res(TINFO, "PTRACE_SETREGSET with reserved bits succeeded");
136 	} else if (TST_ERR == EINVAL) {
137 		tst_res(TINFO,
138 			"PTRACE_SETREGSET with reserved bits failed with EINVAL");
139 	} else {
140 		tst_brk(TBROK | TTERRNO,
141 			"PTRACE_SETREGSET failed with unexpected error");
142 	}
143 
144 	/*
145 	 * It is possible for test child 'pid' to crash on AMD
146 	 * systems (e.g. AMD Opteron(TM) Processor 6234) with
147 	 * older kernels. This causes tracee to stop and sleep
148 	 * in ptrace_stop(). Without resuming the tracee, the
149 	 * test hangs at do_test()->tst_reap_children() called
150 	 * by the library. Use detach here, so we don't need to
151 	 * worry about potential stops after this point.
152 	 */
153 	TEST(ptrace(PTRACE_DETACH, pid, 0, 0));
154 	if (TST_RET != 0)
155 		tst_brk(TBROK | TTERRNO, "PTRACE_DETACH failed");
156 
157 	/* If child 'pid' crashes, only report it as info. */
158 	SAFE_WAITPID(pid, &status, 0);
159 	if (WIFEXITED(status)) {
160 		tst_res(TINFO, "test child %d exited, retcode: %d",
161 			pid, WEXITSTATUS(status));
162 	}
163 	if (WIFSIGNALED(status)) {
164 		tst_res(TINFO, "test child %d exited, termsig: %d",
165 			pid, WTERMSIG(status));
166 	}
167 
168 	okay = true;
169 	for (i = 0; i < num_cpus; i++) {
170 		SAFE_WAIT(&status);
171 		okay &= (WIFEXITED(status) && WEXITSTATUS(status) == 0);
172 	}
173 	if (okay)
174 		tst_res(TPASS, "wasn't able to set invalid FPU state");
175 }
176 
177 static struct tst_test test = {
178 	.test_all = do_test,
179 	.forks_child = 1,
180 	.needs_checkpoints = 1,
181 	.tags = (const struct tst_tag[]) {
182 		{"linux-git", "814fb7bb7db5"},
183 		{"CVE", "2017-15537"},
184 		{}
185 	}
186 
187 };
188 
189 #else /* !__x86_64__ */
190 	TST_TEST_TCONF("this test is only supported on x86_64");
191 #endif /* __x86_64__ */
192