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