mov_ss_trap.c (7843B)
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 47unsigned short ss; 48extern unsigned char breakpoint_insn[]; 49sigjmp_buf jmpbuf; 50 51static 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 97static 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 109static char const * const signames[] = { 110 [SIGSEGV] = "SIGSEGV", 111 [SIGBUS] = "SIBGUS", 112 [SIGTRAP] = "SIGTRAP", 113 [SIGILL] = "SIGILL", 114}; 115 116static 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 125static 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 133static 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 143int 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}