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}
287

source code of linux/tools/testing/selftests/x86/mov_ss_trap.c