1/*
2 * Copyright (c) 2017 Google, Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program, if not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Regression test for commit 814fb7bb7db5 ("x86/fpu: Don't let userspace set
20 * bogus xcomp_bv"), or CVE-2017-15537.  This bug allowed ptrace(pid,
21 * PTRACE_SETREGSET, NT_X86_XSTATE, &iov) to assign a task an invalid FPU state
22 * --- specifically, by setting reserved bits in xstate_header.xcomp_bv.  This
23 * made restoring the FPU registers fail when switching to the task, causing the
24 * FPU registers to take on the values from other tasks.
25 *
26 * To detect the bug, we have a subprocess run a loop checking its xmm0 register
27 * for corruption.  This detects the case where the FPU state became invalid and
28 * the kernel is not restoring the process's registers.  Note that we have to
29 * set the expected value of xmm0 to all 0's since it is acceptable behavior for
30 * the kernel to simply reinitialize the FPU state upon seeing that it is
31 * invalid.  To increase the chance of detecting the problem, we also create
32 * additional subprocesses that spin with different xmm0 contents.
33 *
34 * Thus bug affected the x86 architecture only.  Other architectures could have
35 * similar bugs as well, but this test has to be x86-specific because it has to
36 * know about the architecture-dependent FPU state.
37 */
38
39#include <errno.h>
40#include <inttypes.h>
41#include <sched.h>
42#include <stdbool.h>
43#include <stdlib.h>
44#include <sys/uio.h>
45#include <sys/wait.h>
46
47#include "config.h"
48#include "ptrace.h"
49#include "tst_test.h"
50
51#ifndef PTRACE_GETREGSET
52# define PTRACE_GETREGSET 0x4204
53#endif
54
55#ifndef PTRACE_SETREGSET
56# define PTRACE_SETREGSET 0x4205
57#endif
58
59#ifndef NT_X86_XSTATE
60# define NT_X86_XSTATE 0x202
61#endif
62
63#ifdef __x86_64__
64static void check_regs_loop(uint32_t initval)
65{
66	const unsigned long num_iters = 1000000000;
67	uint32_t xmm0[4] = { initval, initval, initval, initval };
68	int status = 1;
69
70	asm volatile("   movdqu %0, %%xmm0\n"
71		     "   mov %0, %%rbx\n"
72		     "1: dec %2\n"
73		     "   jz 2f\n"
74		     "   movdqu %%xmm0, %0\n"
75		     "   mov %0, %%rax\n"
76		     "   cmp %%rax, %%rbx\n"
77		     "   je 1b\n"
78		     "   jmp 3f\n"
79		     "2: mov $0, %1\n"
80		     "3:\n"
81		     : "+m" (xmm0), "+r" (status)
82		     : "r" (num_iters) : "rax", "rbx", "xmm0");
83
84	if (status) {
85		tst_res(TFAIL,
86			"xmm registers corrupted!  initval=%08X, xmm0=%08X%08X%08X%08X\n",
87			initval, xmm0[0], xmm0[1], xmm0[2], xmm0[3]);
88	}
89	exit(status);
90}
91
92static void do_test(void)
93{
94	int i;
95	int num_cpus = tst_ncpus();
96	pid_t pid;
97	uint64_t xstate[512];
98	struct iovec iov = { .iov_base = xstate, .iov_len = sizeof(xstate) };
99	int status;
100	bool okay;
101
102	pid = SAFE_FORK();
103	if (pid == 0) {
104		TST_CHECKPOINT_WAKE(0);
105		check_regs_loop(0x00000000);
106	}
107	for (i = 0; i < num_cpus; i++) {
108		if (SAFE_FORK() == 0)
109			check_regs_loop(0xDEADBEEF);
110	}
111
112	TST_CHECKPOINT_WAIT(0);
113	sched_yield();
114
115	TEST(ptrace(PTRACE_ATTACH, pid, 0, 0));
116	if (TEST_RETURN != 0)
117		tst_brk(TBROK | TTERRNO, "PTRACE_ATTACH failed");
118
119	SAFE_WAITPID(pid, NULL, 0);
120	TEST(ptrace(PTRACE_GETREGSET, pid, NT_X86_XSTATE, &iov));
121	if (TEST_RETURN != 0) {
122		if (TEST_ERRNO == EIO)
123			tst_brk(TCONF, "GETREGSET/SETREGSET is unsupported");
124
125		if (TEST_ERRNO == EINVAL)
126			tst_brk(TCONF, "NT_X86_XSTATE is unsupported");
127
128		if (TEST_ERRNO == ENODEV)
129			tst_brk(TCONF, "CPU doesn't support XSAVE instruction");
130
131		tst_brk(TBROK | TTERRNO,
132			"PTRACE_GETREGSET failed with unexpected error");
133	}
134
135	xstate[65] = -1; /* sets all bits in xstate_header.xcomp_bv */
136
137	/*
138	 * Old kernels simply masked out all the reserved bits in the xstate
139	 * header (causing the PTRACE_SETREGSET command here to succeed), while
140	 * new kernels will reject them (causing the PTRACE_SETREGSET command
141	 * here to fail with EINVAL).  We accept either behavior, as neither
142	 * behavior reliably tells us whether the real bug (which we test for
143	 * below in either case) is present.
144	 */
145	TEST(ptrace(PTRACE_SETREGSET, pid, NT_X86_XSTATE, &iov));
146	if (TEST_RETURN == 0) {
147		tst_res(TINFO, "PTRACE_SETREGSET with reserved bits succeeded");
148	} else if (TEST_ERRNO == EINVAL) {
149		tst_res(TINFO,
150			"PTRACE_SETREGSET with reserved bits failed with EINVAL");
151	} else {
152		tst_brk(TBROK | TTERRNO,
153			"PTRACE_SETREGSET failed with unexpected error");
154	}
155
156	TEST(ptrace(PTRACE_CONT, pid, 0, 0));
157	if (TEST_RETURN != 0)
158		tst_brk(TBROK | TTERRNO, "PTRACE_CONT failed");
159
160	okay = true;
161	for (i = 0; i < num_cpus + 1; i++) {
162		SAFE_WAIT(&status);
163		okay &= (WIFEXITED(status) && WEXITSTATUS(status) == 0);
164	}
165	if (okay)
166		tst_res(TPASS, "wasn't able to set invalid FPU state");
167}
168
169static struct tst_test test = {
170	.test_all = do_test,
171	.forks_child = 1,
172	.needs_checkpoints = 1,
173};
174
175#else /* !__x86_64__ */
176	TST_TEST_TCONF("this test is only supported on x86_64");
177#endif /* __x86_64__ */
178