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__ 64 static 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 92 static 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 (TST_RET != 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 (TST_RET != 0) { 122 if (TST_ERR == EIO) 123 tst_brk(TCONF, "GETREGSET/SETREGSET is unsupported"); 124 125 if (TST_ERR == EINVAL) 126 tst_brk(TCONF, "NT_X86_XSTATE is unsupported"); 127 128 if (TST_ERR == 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 (TST_RET == 0) { 147 tst_res(TINFO, "PTRACE_SETREGSET with reserved bits succeeded"); 148 } else if (TST_ERR == 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 /* 157 * It is possible for test child 'pid' to crash on AMD 158 * systems (e.g. AMD Opteron(TM) Processor 6234) with 159 * older kernels. This causes tracee to stop and sleep 160 * in ptrace_stop(). Without resuming the tracee, the 161 * test hangs at do_test()->tst_reap_children() called 162 * by the library. Use detach here, so we don't need to 163 * worry about potential stops after this point. 164 */ 165 TEST(ptrace(PTRACE_DETACH, pid, 0, 0)); 166 if (TST_RET != 0) 167 tst_brk(TBROK | TTERRNO, "PTRACE_DETACH failed"); 168 169 /* If child 'pid' crashes, only report it as info. */ 170 SAFE_WAITPID(pid, &status, 0); 171 if (WIFEXITED(status)) { 172 tst_res(TINFO, "test child %d exited, retcode: %d", 173 pid, WEXITSTATUS(status)); 174 } 175 if (WIFSIGNALED(status)) { 176 tst_res(TINFO, "test child %d exited, termsig: %d", 177 pid, WTERMSIG(status)); 178 } 179 180 okay = true; 181 for (i = 0; i < num_cpus; i++) { 182 SAFE_WAIT(&status); 183 okay &= (WIFEXITED(status) && WEXITSTATUS(status) == 0); 184 } 185 if (okay) 186 tst_res(TPASS, "wasn't able to set invalid FPU state"); 187 } 188 189 static struct tst_test test = { 190 .test_all = do_test, 191 .forks_child = 1, 192 .needs_checkpoints = 1, 193 }; 194 195 #else /* !__x86_64__ */ 196 TST_TEST_TCONF("this test is only supported on x86_64"); 197 #endif /* __x86_64__ */ 198