1 /* SPDX-License-Identifier: GPL-2.0 */ 2 /* 3 * mov_ss_trap.c: Exercise the bizarre side effects of a watchpoint on MOV SS 4 * 5 * This does MOV SS from a watchpointed address followed by various 6 * types of kernel entries. A MOV SS that hits a watchpoint will queue 7 * up a #DB trap but will not actually deliver that trap. The trap 8 * will be delivered after the next instruction instead. The CPU's logic 9 * seems to be: 10 * 11 * - Any fault: drop the pending #DB trap. 12 * - INT $N, INT3, INTO, SYSCALL, SYSENTER: enter the kernel and then 13 * deliver #DB. 14 * - ICEBP: enter the kernel but do not deliver the watchpoint trap 15 * - breakpoint: only one #DB is delivered (phew!) 16 * 17 * There are plenty of ways for a kernel to handle this incorrectly. This 18 * test tries to exercise all the cases. 19 * 20 * This should mostly cover CVE-2018-1087 and CVE-2018-8897. 21 */ 22 #define _GNU_SOURCE 23 24 #include <stdlib.h> 25 #include <sys/ptrace.h> 26 #include <sys/types.h> 27 #include <sys/wait.h> 28 #include <sys/user.h> 29 #include <sys/syscall.h> 30 #include <unistd.h> 31 #include <errno.h> 32 #include <stddef.h> 33 #include <stdio.h> 34 #include <err.h> 35 #include <string.h> 36 #include <setjmp.h> 37 #include <sys/prctl.h> 38 39 #include "helpers.h" 40 41 #if __x86_64__ 42 # define REG_IP REG_RIP 43 #else 44 # define REG_IP REG_EIP 45 #endif 46 47 unsigned short ss; 48 extern unsigned char breakpoint_insn[]; 49 sigjmp_buf jmpbuf; 50 51 static void enable_watchpoint(void) 52 { 53 pid_t parent = getpid(); 54 int status; 55 56 pid_t child = fork(); 57 if (child < 0) 58 err(1, "fork"); 59 60 if (child) { 61 if (waitpid(child, &status, 0) != child) 62 err(1, "waitpid for child"); 63 } else { 64 unsigned long dr0, dr1, dr7; 65 66 dr0 = (unsigned long)&ss; 67 dr1 = (unsigned long)breakpoint_insn; 68 dr7 = ((1UL << 1) | /* G0 */ 69 (3UL << 16) | /* RW0 = read or write */ 70 (1UL << 18) | /* LEN0 = 2 bytes */ 71 (1UL << 3)); /* G1, RW1 = insn */ 72 73 if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) != 0) 74 err(1, "PTRACE_ATTACH"); 75 76 if (waitpid(parent, &status, 0) != parent) 77 err(1, "waitpid for child"); 78 79 if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[0]), dr0) != 0) 80 err(1, "PTRACE_POKEUSER DR0"); 81 82 if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[1]), dr1) != 0) 83 err(1, "PTRACE_POKEUSER DR1"); 84 85 if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[7]), dr7) != 0) 86 err(1, "PTRACE_POKEUSER DR7"); 87 88 printf("\tDR0 = %lx, DR1 = %lx, DR7 = %lx\n", dr0, dr1, dr7); 89 90 if (ptrace(PTRACE_DETACH, parent, NULL, NULL) != 0) 91 err(1, "PTRACE_DETACH"); 92 93 exit(0); 94 } 95 } 96 97 static char const * const signames[] = { 98 [SIGSEGV] = "SIGSEGV", 99 [SIGBUS] = "SIBGUS", 100 [SIGTRAP] = "SIGTRAP", 101 [SIGILL] = "SIGILL", 102 }; 103 104 static void sigtrap(int sig, siginfo_t *si, void *ctx_void) 105 { 106 ucontext_t *ctx = ctx_void; 107 108 printf("\tGot SIGTRAP with RIP=%lx, EFLAGS.RF=%d\n", 109 (unsigned long)ctx->uc_mcontext.gregs[REG_IP], 110 !!(ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_RF)); 111 } 112 113 static void handle_and_return(int sig, siginfo_t *si, void *ctx_void) 114 { 115 ucontext_t *ctx = ctx_void; 116 117 printf("\tGot %s with RIP=%lx\n", signames[sig], 118 (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); 119 } 120 121 static void handle_and_longjmp(int sig, siginfo_t *si, void *ctx_void) 122 { 123 ucontext_t *ctx = ctx_void; 124 125 printf("\tGot %s with RIP=%lx\n", signames[sig], 126 (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); 127 128 siglongjmp(jmpbuf, 1); 129 } 130 131 int main() 132 { 133 unsigned long nr; 134 135 asm volatile ("mov %%ss, %[ss]" : [ss] "=m" (ss)); 136 printf("\tSS = 0x%hx, &SS = 0x%p\n", ss, &ss); 137 138 if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) == 0) 139 printf("\tPR_SET_PTRACER_ANY succeeded\n"); 140 141 printf("\tSet up a watchpoint\n"); 142 sethandler(SIGTRAP, sigtrap, 0); 143 enable_watchpoint(); 144 145 printf("[RUN]\tRead from watched memory (should get SIGTRAP)\n"); 146 asm volatile ("mov %[ss], %[tmp]" : [tmp] "=r" (nr) : [ss] "m" (ss)); 147 148 printf("[RUN]\tMOV SS; INT3\n"); 149 asm volatile ("mov %[ss], %%ss; int3" :: [ss] "m" (ss)); 150 151 printf("[RUN]\tMOV SS; INT 3\n"); 152 asm volatile ("mov %[ss], %%ss; .byte 0xcd, 0x3" :: [ss] "m" (ss)); 153 154 printf("[RUN]\tMOV SS; CS CS INT3\n"); 155 asm volatile ("mov %[ss], %%ss; .byte 0x2e, 0x2e; int3" :: [ss] "m" (ss)); 156 157 printf("[RUN]\tMOV SS; CSx14 INT3\n"); 158 asm volatile ("mov %[ss], %%ss; .fill 14,1,0x2e; int3" :: [ss] "m" (ss)); 159 160 printf("[RUN]\tMOV SS; INT 4\n"); 161 sethandler(SIGSEGV, handle_and_return, SA_RESETHAND); 162 asm volatile ("mov %[ss], %%ss; int $4" :: [ss] "m" (ss)); 163 164 #ifdef __i386__ 165 printf("[RUN]\tMOV SS; INTO\n"); 166 sethandler(SIGSEGV, handle_and_return, SA_RESETHAND); 167 nr = -1; 168 asm volatile ("add $1, %[tmp]; mov %[ss], %%ss; into" 169 : [tmp] "+r" (nr) : [ss] "m" (ss)); 170 #endif 171 172 if (sigsetjmp(jmpbuf, 1) == 0) { 173 printf("[RUN]\tMOV SS; ICEBP\n"); 174 175 /* Some emulators (e.g. QEMU TCG) don't emulate ICEBP. */ 176 sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND); 177 178 asm volatile ("mov %[ss], %%ss; .byte 0xf1" :: [ss] "m" (ss)); 179 } 180 181 if (sigsetjmp(jmpbuf, 1) == 0) { 182 printf("[RUN]\tMOV SS; CLI\n"); 183 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND); 184 asm volatile ("mov %[ss], %%ss; cli" :: [ss] "m" (ss)); 185 } 186 187 if (sigsetjmp(jmpbuf, 1) == 0) { 188 printf("[RUN]\tMOV SS; #PF\n"); 189 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND); 190 asm volatile ("mov %[ss], %%ss; mov (-1), %[tmp]" 191 : [tmp] "=r" (nr) : [ss] "m" (ss)); 192 } 193 194 /* 195 * INT $1: if #DB has DPL=3 and there isn't special handling, 196 * then the kernel will die. 197 */ 198 if (sigsetjmp(jmpbuf, 1) == 0) { 199 printf("[RUN]\tMOV SS; INT 1\n"); 200 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND); 201 asm volatile ("mov %[ss], %%ss; int $1" :: [ss] "m" (ss)); 202 } 203 204 #ifdef __x86_64__ 205 /* 206 * In principle, we should test 32-bit SYSCALL as well, but 207 * the calling convention is so unpredictable that it's 208 * not obviously worth the effort. 209 */ 210 if (sigsetjmp(jmpbuf, 1) == 0) { 211 printf("[RUN]\tMOV SS; SYSCALL\n"); 212 sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND); 213 nr = SYS_getpid; 214 /* 215 * Toggle the high bit of RSP to make it noncanonical to 216 * strengthen this test on non-SMAP systems. 217 */ 218 asm volatile ("btc $63, %%rsp\n\t" 219 "mov %[ss], %%ss; syscall\n\t" 220 "btc $63, %%rsp" 221 : "+a" (nr) : [ss] "m" (ss) 222 : "rcx" 223 #ifdef __x86_64__ 224 , "r11" 225 #endif 226 ); 227 } 228 #endif 229 230 printf("[RUN]\tMOV SS; breakpointed NOP\n"); 231 asm volatile ("mov %[ss], %%ss; breakpoint_insn: nop" :: [ss] "m" (ss)); 232 233 /* 234 * Invoking SYSENTER directly breaks all the rules. Just handle 235 * the SIGSEGV. 236 */ 237 if (sigsetjmp(jmpbuf, 1) == 0) { 238 printf("[RUN]\tMOV SS; SYSENTER\n"); 239 stack_t stack = { 240 .ss_sp = malloc(sizeof(char) * SIGSTKSZ), 241 .ss_size = SIGSTKSZ, 242 }; 243 if (sigaltstack(&stack, NULL) != 0) 244 err(1, "sigaltstack"); 245 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND | SA_ONSTACK); 246 nr = SYS_getpid; 247 free(stack.ss_sp); 248 /* Clear EBP first to make sure we segfault cleanly. */ 249 asm volatile ("xorl %%ebp, %%ebp; mov %[ss], %%ss; SYSENTER" : "+a" (nr) 250 : [ss] "m" (ss) : "flags", "rcx" 251 #ifdef __x86_64__ 252 , "r11" 253 #endif 254 ); 255 256 /* We're unreachable here. SYSENTER forgets RIP. */ 257 } 258 259 if (sigsetjmp(jmpbuf, 1) == 0) { 260 printf("[RUN]\tMOV SS; INT $0x80\n"); 261 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND); 262 nr = 20; /* compat getpid */ 263 asm volatile ("mov %[ss], %%ss; int $0x80" 264 : "+a" (nr) : [ss] "m" (ss) 265 : "flags" 266 #ifdef __x86_64__ 267 , "r8", "r9", "r10", "r11" 268 #endif 269 ); 270 } 271 272 printf("[OK]\tI aten't dead\n"); 273 return 0; 274 } 275