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 #define X86_EFLAGS_RF (1UL << 16) 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 void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), 98 int flags) 99 { 100 struct sigaction sa; 101 memset(&sa, 0, sizeof(sa)); 102 sa.sa_sigaction = handler; 103 sa.sa_flags = SA_SIGINFO | flags; 104 sigemptyset(&sa.sa_mask); 105 if (sigaction(sig, &sa, 0)) 106 err(1, "sigaction"); 107 } 108 109 static char const * const signames[] = { 110 [SIGSEGV] = "SIGSEGV", 111 [SIGBUS] = "SIBGUS", 112 [SIGTRAP] = "SIGTRAP", 113 [SIGILL] = "SIGILL", 114 }; 115 116 static void sigtrap(int sig, siginfo_t *si, void *ctx_void) 117 { 118 ucontext_t *ctx = ctx_void; 119 120 printf("\tGot SIGTRAP with RIP=%lx, EFLAGS.RF=%d\n", 121 (unsigned long)ctx->uc_mcontext.gregs[REG_IP], 122 !!(ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_RF)); 123 } 124 125 static void handle_and_return(int sig, siginfo_t *si, void *ctx_void) 126 { 127 ucontext_t *ctx = ctx_void; 128 129 printf("\tGot %s with RIP=%lx\n", signames[sig], 130 (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); 131 } 132 133 static void handle_and_longjmp(int sig, siginfo_t *si, void *ctx_void) 134 { 135 ucontext_t *ctx = ctx_void; 136 137 printf("\tGot %s with RIP=%lx\n", signames[sig], 138 (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); 139 140 siglongjmp(jmpbuf, 1); 141 } 142 143 int main() 144 { 145 unsigned long nr; 146 147 asm volatile ("mov %%ss, %[ss]" : [ss] "=m" (ss)); 148 printf("\tSS = 0x%hx, &SS = 0x%p\n", ss, &ss); 149 150 if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) == 0) 151 printf("\tPR_SET_PTRACER_ANY succeeded\n"); 152 153 printf("\tSet up a watchpoint\n"); 154 sethandler(SIGTRAP, sigtrap, 0); 155 enable_watchpoint(); 156 157 printf("[RUN]\tRead from watched memory (should get SIGTRAP)\n"); 158 asm volatile ("mov %[ss], %[tmp]" : [tmp] "=r" (nr) : [ss] "m" (ss)); 159 160 printf("[RUN]\tMOV SS; INT3\n"); 161 asm volatile ("mov %[ss], %%ss; int3" :: [ss] "m" (ss)); 162 163 printf("[RUN]\tMOV SS; INT 3\n"); 164 asm volatile ("mov %[ss], %%ss; .byte 0xcd, 0x3" :: [ss] "m" (ss)); 165 166 printf("[RUN]\tMOV SS; CS CS INT3\n"); 167 asm volatile ("mov %[ss], %%ss; .byte 0x2e, 0x2e; int3" :: [ss] "m" (ss)); 168 169 printf("[RUN]\tMOV SS; CSx14 INT3\n"); 170 asm volatile ("mov %[ss], %%ss; .fill 14,1,0x2e; int3" :: [ss] "m" (ss)); 171 172 printf("[RUN]\tMOV SS; INT 4\n"); 173 sethandler(SIGSEGV, handle_and_return, SA_RESETHAND); 174 asm volatile ("mov %[ss], %%ss; int $4" :: [ss] "m" (ss)); 175 176 #ifdef __i386__ 177 printf("[RUN]\tMOV SS; INTO\n"); 178 sethandler(SIGSEGV, handle_and_return, SA_RESETHAND); 179 nr = -1; 180 asm volatile ("add $1, %[tmp]; mov %[ss], %%ss; into" 181 : [tmp] "+r" (nr) : [ss] "m" (ss)); 182 #endif 183 184 if (sigsetjmp(jmpbuf, 1) == 0) { 185 printf("[RUN]\tMOV SS; ICEBP\n"); 186 187 /* Some emulators (e.g. QEMU TCG) don't emulate ICEBP. */ 188 sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND); 189 190 asm volatile ("mov %[ss], %%ss; .byte 0xf1" :: [ss] "m" (ss)); 191 } 192 193 if (sigsetjmp(jmpbuf, 1) == 0) { 194 printf("[RUN]\tMOV SS; CLI\n"); 195 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND); 196 asm volatile ("mov %[ss], %%ss; cli" :: [ss] "m" (ss)); 197 } 198 199 if (sigsetjmp(jmpbuf, 1) == 0) { 200 printf("[RUN]\tMOV SS; #PF\n"); 201 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND); 202 asm volatile ("mov %[ss], %%ss; mov (-1), %[tmp]" 203 : [tmp] "=r" (nr) : [ss] "m" (ss)); 204 } 205 206 /* 207 * INT $1: if #DB has DPL=3 and there isn't special handling, 208 * then the kernel will die. 209 */ 210 if (sigsetjmp(jmpbuf, 1) == 0) { 211 printf("[RUN]\tMOV SS; INT 1\n"); 212 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND); 213 asm volatile ("mov %[ss], %%ss; int $1" :: [ss] "m" (ss)); 214 } 215 216 #ifdef __x86_64__ 217 /* 218 * In principle, we should test 32-bit SYSCALL as well, but 219 * the calling convention is so unpredictable that it's 220 * not obviously worth the effort. 221 */ 222 if (sigsetjmp(jmpbuf, 1) == 0) { 223 printf("[RUN]\tMOV SS; SYSCALL\n"); 224 sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND); 225 nr = SYS_getpid; 226 /* 227 * Toggle the high bit of RSP to make it noncanonical to 228 * strengthen this test on non-SMAP systems. 229 */ 230 asm volatile ("btc $63, %%rsp\n\t" 231 "mov %[ss], %%ss; syscall\n\t" 232 "btc $63, %%rsp" 233 : "+a" (nr) : [ss] "m" (ss) 234 : "rcx" 235 #ifdef __x86_64__ 236 , "r11" 237 #endif 238 ); 239 } 240 #endif 241 242 printf("[RUN]\tMOV SS; breakpointed NOP\n"); 243 asm volatile ("mov %[ss], %%ss; breakpoint_insn: nop" :: [ss] "m" (ss)); 244 245 /* 246 * Invoking SYSENTER directly breaks all the rules. Just handle 247 * the SIGSEGV. 248 */ 249 if (sigsetjmp(jmpbuf, 1) == 0) { 250 printf("[RUN]\tMOV SS; SYSENTER\n"); 251 stack_t stack = { 252 .ss_sp = malloc(sizeof(char) * SIGSTKSZ), 253 .ss_size = SIGSTKSZ, 254 }; 255 if (sigaltstack(&stack, NULL) != 0) 256 err(1, "sigaltstack"); 257 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND | SA_ONSTACK); 258 nr = SYS_getpid; 259 free(stack.ss_sp); 260 /* Clear EBP first to make sure we segfault cleanly. */ 261 asm volatile ("xorl %%ebp, %%ebp; mov %[ss], %%ss; SYSENTER" : "+a" (nr) 262 : [ss] "m" (ss) : "flags", "rcx" 263 #ifdef __x86_64__ 264 , "r11" 265 #endif 266 ); 267 268 /* We're unreachable here. SYSENTER forgets RIP. */ 269 } 270 271 if (sigsetjmp(jmpbuf, 1) == 0) { 272 printf("[RUN]\tMOV SS; INT $0x80\n"); 273 sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND); 274 nr = 20; /* compat getpid */ 275 asm volatile ("mov %[ss], %%ss; int $0x80" 276 : "+a" (nr) : [ss] "m" (ss) 277 : "flags" 278 #ifdef __x86_64__ 279 , "r8", "r9", "r10", "r11" 280 #endif 281 ); 282 } 283 284 printf("[OK]\tI aten't dead\n"); 285 return 0; 286 } 287
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.