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;
50static unsigned char altstack_data[SIGSTKSZ];
51
52static void enable_watchpoint(void)
53{
54	pid_t parent = getpid();
55	int status;
56
57	pid_t child = fork();
58	if (child < 0)
59		err(1, "fork");
60
61	if (child) {
62		if (waitpid(child, &status, 0) != child)
63			err(1, "waitpid for child");
64	} else {
65		unsigned long dr0, dr1, dr7;
66
67		dr0 = (unsigned long)&ss;
68		dr1 = (unsigned long)breakpoint_insn;
69		dr7 = ((1UL << 1) |	/* G0 */
70		       (3UL << 16) |	/* RW0 = read or write */
71		       (1UL << 18) |	/* LEN0 = 2 bytes */
72		       (1UL << 3));	/* G1, RW1 = insn */
73
74		if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) != 0)
75			err(1, "PTRACE_ATTACH");
76
77		if (waitpid(parent, &status, 0) != parent)
78			err(1, "waitpid for child");
79
80		if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[0]), dr0) != 0)
81			err(1, "PTRACE_POKEUSER DR0");
82
83		if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[1]), dr1) != 0)
84			err(1, "PTRACE_POKEUSER DR1");
85
86		if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[7]), dr7) != 0)
87			err(1, "PTRACE_POKEUSER DR7");
88
89		printf("\tDR0 = %lx, DR1 = %lx, DR7 = %lx\n", dr0, dr1, dr7);
90
91		if (ptrace(PTRACE_DETACH, parent, NULL, NULL) != 0)
92			err(1, "PTRACE_DETACH");
93
94		exit(0);
95	}
96}
97
98static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
99		       int flags)
100{
101	struct sigaction sa;
102	memset(&sa, 0, sizeof(sa));
103	sa.sa_sigaction = handler;
104	sa.sa_flags = SA_SIGINFO | flags;
105	sigemptyset(&sa.sa_mask);
106	if (sigaction(sig, &sa, 0))
107		err(1, "sigaction");
108}
109
110static char const * const signames[] = {
111	[SIGSEGV] = "SIGSEGV",
112	[SIGBUS] = "SIBGUS",
113	[SIGTRAP] = "SIGTRAP",
114	[SIGILL] = "SIGILL",
115};
116
117static void sigtrap(int sig, siginfo_t *si, void *ctx_void)
118{
119	ucontext_t *ctx = ctx_void;
120
121	printf("\tGot SIGTRAP with RIP=%lx, EFLAGS.RF=%d\n",
122	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP],
123	       !!(ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_RF));
124}
125
126static void handle_and_return(int sig, siginfo_t *si, void *ctx_void)
127{
128	ucontext_t *ctx = ctx_void;
129
130	printf("\tGot %s with RIP=%lx\n", signames[sig],
131	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
132}
133
134static void handle_and_longjmp(int sig, siginfo_t *si, void *ctx_void)
135{
136	ucontext_t *ctx = ctx_void;
137
138	printf("\tGot %s with RIP=%lx\n", signames[sig],
139	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
140
141	siglongjmp(jmpbuf, 1);
142}
143
144int main()
145{
146	unsigned long nr;
147
148	asm volatile ("mov %%ss, %[ss]" : [ss] "=m" (ss));
149	printf("\tSS = 0x%hx, &SS = 0x%p\n", ss, &ss);
150
151	if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) == 0)
152		printf("\tPR_SET_PTRACER_ANY succeeded\n");
153
154	printf("\tSet up a watchpoint\n");
155	sethandler(SIGTRAP, sigtrap, 0);
156	enable_watchpoint();
157
158	printf("[RUN]\tRead from watched memory (should get SIGTRAP)\n");
159	asm volatile ("mov %[ss], %[tmp]" : [tmp] "=r" (nr) : [ss] "m" (ss));
160
161	printf("[RUN]\tMOV SS; INT3\n");
162	asm volatile ("mov %[ss], %%ss; int3" :: [ss] "m" (ss));
163
164	printf("[RUN]\tMOV SS; INT 3\n");
165	asm volatile ("mov %[ss], %%ss; .byte 0xcd, 0x3" :: [ss] "m" (ss));
166
167	printf("[RUN]\tMOV SS; CS CS INT3\n");
168	asm volatile ("mov %[ss], %%ss; .byte 0x2e, 0x2e; int3" :: [ss] "m" (ss));
169
170	printf("[RUN]\tMOV SS; CSx14 INT3\n");
171	asm volatile ("mov %[ss], %%ss; .fill 14,1,0x2e; int3" :: [ss] "m" (ss));
172
173	printf("[RUN]\tMOV SS; INT 4\n");
174	sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
175	asm volatile ("mov %[ss], %%ss; int $4" :: [ss] "m" (ss));
176
177#ifdef __i386__
178	printf("[RUN]\tMOV SS; INTO\n");
179	sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
180	nr = -1;
181	asm volatile ("add $1, %[tmp]; mov %[ss], %%ss; into"
182		      : [tmp] "+r" (nr) : [ss] "m" (ss));
183#endif
184
185	if (sigsetjmp(jmpbuf, 1) == 0) {
186		printf("[RUN]\tMOV SS; ICEBP\n");
187
188		/* Some emulators (e.g. QEMU TCG) don't emulate ICEBP. */
189		sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
190
191		asm volatile ("mov %[ss], %%ss; .byte 0xf1" :: [ss] "m" (ss));
192	}
193
194	if (sigsetjmp(jmpbuf, 1) == 0) {
195		printf("[RUN]\tMOV SS; CLI\n");
196		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
197		asm volatile ("mov %[ss], %%ss; cli" :: [ss] "m" (ss));
198	}
199
200	if (sigsetjmp(jmpbuf, 1) == 0) {
201		printf("[RUN]\tMOV SS; #PF\n");
202		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
203		asm volatile ("mov %[ss], %%ss; mov (-1), %[tmp]"
204			      : [tmp] "=r" (nr) : [ss] "m" (ss));
205	}
206
207	/*
208	 * INT $1: if #DB has DPL=3 and there isn't special handling,
209	 * then the kernel will die.
210	 */
211	if (sigsetjmp(jmpbuf, 1) == 0) {
212		printf("[RUN]\tMOV SS; INT 1\n");
213		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
214		asm volatile ("mov %[ss], %%ss; int $1" :: [ss] "m" (ss));
215	}
216
217#ifdef __x86_64__
218	/*
219	 * In principle, we should test 32-bit SYSCALL as well, but
220	 * the calling convention is so unpredictable that it's
221	 * not obviously worth the effort.
222	 */
223	if (sigsetjmp(jmpbuf, 1) == 0) {
224		printf("[RUN]\tMOV SS; SYSCALL\n");
225		sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
226		nr = SYS_getpid;
227		/*
228		 * Toggle the high bit of RSP to make it noncanonical to
229		 * strengthen this test on non-SMAP systems.
230		 */
231		asm volatile ("btc $63, %%rsp\n\t"
232			      "mov %[ss], %%ss; syscall\n\t"
233			      "btc $63, %%rsp"
234			      : "+a" (nr) : [ss] "m" (ss)
235			      : "rcx"
236#ifdef __x86_64__
237				, "r11"
238#endif
239			);
240	}
241#endif
242
243	printf("[RUN]\tMOV SS; breakpointed NOP\n");
244	asm volatile ("mov %[ss], %%ss; breakpoint_insn: nop" :: [ss] "m" (ss));
245
246	/*
247	 * Invoking SYSENTER directly breaks all the rules.  Just handle
248	 * the SIGSEGV.
249	 */
250	if (sigsetjmp(jmpbuf, 1) == 0) {
251		printf("[RUN]\tMOV SS; SYSENTER\n");
252		stack_t stack = {
253			.ss_sp = altstack_data,
254			.ss_size = SIGSTKSZ,
255		};
256		if (sigaltstack(&stack, NULL) != 0)
257			err(1, "sigaltstack");
258		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND | SA_ONSTACK);
259		nr = SYS_getpid;
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