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 "tst_test.h"
28
29 #ifdef __x86_64__
30
31 #include <errno.h>
32 #include <inttypes.h>
33 #include <sched.h>
34 #include <stdbool.h>
35 #include <stdlib.h>
36 #include <sys/uio.h>
37 #include <sys/wait.h>
38
39 #include "config.h"
40 #include "ptrace.h"
41 #include "tst_safe_macros.h"
42 #include "lapi/cpuid.h"
43
44 #ifndef PTRACE_GETREGSET
45 # define PTRACE_GETREGSET 0x4204
46 #endif
47
48 #ifndef PTRACE_SETREGSET
49 # define PTRACE_SETREGSET 0x4205
50 #endif
51
52 #ifndef NT_X86_XSTATE
53 # define NT_X86_XSTATE 0x202
54 #endif
55
56 #ifndef CPUID_LEAF_XSTATE
57 # define CPUID_LEAF_XSTATE 0xd
58 #endif
59
check_regs_loop(uint32_t initval)60 static void check_regs_loop(uint32_t initval)
61 {
62 const unsigned long num_iters = 1000000000;
63 uint32_t xmm0[4] = { initval, initval, initval, initval };
64 int status = 1;
65
66 asm volatile(" movdqu %0, %%xmm0\n"
67 " mov %0, %%rbx\n"
68 "1: dec %2\n"
69 " jz 2f\n"
70 " movdqu %%xmm0, %0\n"
71 " mov %0, %%rax\n"
72 " cmp %%rax, %%rbx\n"
73 " je 1b\n"
74 " jmp 3f\n"
75 "2: mov $0, %1\n"
76 "3:\n"
77 : "+m" (xmm0), "+r" (status)
78 : "r" (num_iters) : "rax", "rbx", "xmm0");
79
80 if (status) {
81 tst_res(TFAIL,
82 "xmm registers corrupted! initval=%08X, xmm0=%08X%08X%08X%08X\n",
83 initval, xmm0[0], xmm0[1], xmm0[2], xmm0[3]);
84 }
85 exit(status);
86 }
87
do_test(void)88 static void do_test(void)
89 {
90 int i;
91 int num_cpus = tst_ncpus();
92 pid_t pid;
93 uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
94 uint64_t *xstate;
95 /*
96 * CPUID.(EAX=0DH, ECX=0H):EBX: maximum size (bytes, from the beginning
97 * of the XSAVE/XRSTOR save area) required by enabled features in XCR0.
98 */
99 __cpuid_count(CPUID_LEAF_XSTATE, ecx, eax, ebx, ecx, edx);
100 xstate = SAFE_MEMALIGN(64, ebx);
101 struct iovec iov = { .iov_base = xstate, .iov_len = ebx };
102 int status;
103 bool okay;
104
105 tst_res(TINFO, "CPUID.(EAX=%u, ECX=0):EAX=%u, EBX=%u, ECX=%u, EDX=%u",
106 CPUID_LEAF_XSTATE, eax, ebx, ecx, edx);
107 pid = SAFE_FORK();
108 if (pid == 0) {
109 TST_CHECKPOINT_WAKE(0);
110 check_regs_loop(0x00000000);
111 }
112 for (i = 0; i < num_cpus; i++) {
113 if (SAFE_FORK() == 0)
114 check_regs_loop(0xDEADBEEF);
115 }
116
117 TST_CHECKPOINT_WAIT(0);
118 sched_yield();
119
120 TEST(ptrace(PTRACE_ATTACH, pid, 0, 0));
121 if (TST_RET != 0) {
122 free(xstate);
123 tst_brk(TBROK | TTERRNO, "PTRACE_ATTACH failed");
124 }
125
126 SAFE_WAITPID(pid, NULL, 0);
127 TEST(ptrace(PTRACE_GETREGSET, pid, NT_X86_XSTATE, &iov));
128 if (TST_RET != 0) {
129 free(xstate);
130 if (TST_ERR == EIO)
131 tst_brk(TCONF, "GETREGSET/SETREGSET is unsupported");
132
133 if (TST_ERR == EINVAL)
134 tst_brk(TCONF, "NT_X86_XSTATE is unsupported");
135
136 if (TST_ERR == ENODEV)
137 tst_brk(TCONF, "CPU doesn't support XSAVE instruction");
138
139 tst_brk(TBROK | TTERRNO,
140 "PTRACE_GETREGSET failed with unexpected error");
141 }
142
143 xstate[65] = -1; /* sets all bits in xstate_header.xcomp_bv */
144
145 /*
146 * Old kernels simply masked out all the reserved bits in the xstate
147 * header (causing the PTRACE_SETREGSET command here to succeed), while
148 * new kernels will reject them (causing the PTRACE_SETREGSET command
149 * here to fail with EINVAL). We accept either behavior, as neither
150 * behavior reliably tells us whether the real bug (which we test for
151 * below in either case) is present.
152 */
153 TEST(ptrace(PTRACE_SETREGSET, pid, NT_X86_XSTATE, &iov));
154 if (TST_RET == 0) {
155 tst_res(TINFO, "PTRACE_SETREGSET with reserved bits succeeded");
156 } else if (TST_ERR == EINVAL) {
157 tst_res(TINFO,
158 "PTRACE_SETREGSET with reserved bits failed with EINVAL");
159 } else {
160 free(xstate);
161 tst_brk(TBROK | TTERRNO,
162 "PTRACE_SETREGSET failed with unexpected error");
163 }
164
165 /*
166 * It is possible for test child 'pid' to crash on AMD
167 * systems (e.g. AMD Opteron(TM) Processor 6234) with
168 * older kernels. This causes tracee to stop and sleep
169 * in ptrace_stop(). Without resuming the tracee, the
170 * test hangs at do_test()->tst_reap_children() called
171 * by the library. Use detach here, so we don't need to
172 * worry about potential stops after this point.
173 */
174 TEST(ptrace(PTRACE_DETACH, pid, 0, 0));
175 if (TST_RET != 0) {
176 free(xstate);
177 tst_brk(TBROK | TTERRNO, "PTRACE_DETACH failed");
178 }
179
180 /* If child 'pid' crashes, only report it as info. */
181 SAFE_WAITPID(pid, &status, 0);
182 if (WIFEXITED(status)) {
183 tst_res(TINFO, "test child %d exited, retcode: %d",
184 pid, WEXITSTATUS(status));
185 }
186 if (WIFSIGNALED(status)) {
187 tst_res(TINFO, "test child %d exited, termsig: %d",
188 pid, WTERMSIG(status));
189 }
190
191 okay = true;
192 for (i = 0; i < num_cpus; i++) {
193 SAFE_WAIT(&status);
194 okay &= (WIFEXITED(status) && WEXITSTATUS(status) == 0);
195 }
196 if (okay)
197 tst_res(TPASS, "wasn't able to set invalid FPU state");
198 free(xstate);
199 }
200
201 static struct tst_test test = {
202 .test_all = do_test,
203 .forks_child = 1,
204 .needs_checkpoints = 1,
205 .supported_archs = (const char *const []) {
206 "x86_64",
207 NULL
208 },
209 .tags = (const struct tst_tag[]) {
210 {"linux-git", "814fb7bb7db5"},
211 {"CVE", "2017-15537"},
212 {}
213 }
214
215 };
216
217 #else
218 TST_TEST_TCONF("Tests an x86_64 feature");
219 #endif /* if x86 */
220