1 /* SPDX-License-Identifier: GPL-2.0 */
3 * mov_ss_trap.c: Exercise the bizarre side effects of a watchpoint on MOV SS
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
11 * - Any fault: drop the pending #DB trap.
12 * - INT $N, INT3, INTO, SYSCALL, SYSENTER: enter the kernel and then
14 * - ICEBP: enter the kernel but do not deliver the watchpoint trap
15 * - breakpoint: only one #DB is delivered (phew!)
17 * There are plenty of ways for a kernel to handle this incorrectly. This
18 * test tries to exercise all the cases.
20 * This should mostly cover CVE-2018-1087 and CVE-2018-8897.
25 #include <sys/ptrace.h>
26 #include <sys/types.h>
29 #include <sys/syscall.h>
37 #include <sys/prctl.h>
39 #define X86_EFLAGS_RF (1UL << 16)
42 # define REG_IP REG_RIP
44 # define REG_IP REG_EIP
48 extern unsigned char breakpoint_insn
[];
50 static unsigned char altstack_data
[SIGSTKSZ
];
52 static void enable_watchpoint(void)
54 pid_t parent
= getpid();
62 if (waitpid(child
, &status
, 0) != child
)
63 err(1, "waitpid for child");
65 unsigned long dr0
, dr1
, dr7
;
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 */
74 if (ptrace(PTRACE_ATTACH
, parent
, NULL
, NULL
) != 0)
75 err(1, "PTRACE_ATTACH");
77 if (waitpid(parent
, &status
, 0) != parent
)
78 err(1, "waitpid for child");
80 if (ptrace(PTRACE_POKEUSER
, parent
, (void *)offsetof(struct user
, u_debugreg
[0]), dr0
) != 0)
81 err(1, "PTRACE_POKEUSER DR0");
83 if (ptrace(PTRACE_POKEUSER
, parent
, (void *)offsetof(struct user
, u_debugreg
[1]), dr1
) != 0)
84 err(1, "PTRACE_POKEUSER DR1");
86 if (ptrace(PTRACE_POKEUSER
, parent
, (void *)offsetof(struct user
, u_debugreg
[7]), dr7
) != 0)
87 err(1, "PTRACE_POKEUSER DR7");
89 printf("\tDR0 = %lx, DR1 = %lx, DR7 = %lx\n", dr0
, dr1
, dr7
);
91 if (ptrace(PTRACE_DETACH
, parent
, NULL
, NULL
) != 0)
92 err(1, "PTRACE_DETACH");
98 static void sethandler(int sig
, void (*handler
)(int, siginfo_t
*, void *),
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))
110 static char const * const signames
[] = {
111 [SIGSEGV
] = "SIGSEGV",
113 [SIGTRAP
] = "SIGTRAP",
117 static void sigtrap(int sig
, siginfo_t
*si
, void *ctx_void
)
119 ucontext_t
*ctx
= ctx_void
;
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
));
126 static void handle_and_return(int sig
, siginfo_t
*si
, void *ctx_void
)
128 ucontext_t
*ctx
= ctx_void
;
130 printf("\tGot %s with RIP=%lx\n", signames
[sig
],
131 (unsigned long)ctx
->uc_mcontext
.gregs
[REG_IP
]);
134 static void handle_and_longjmp(int sig
, siginfo_t
*si
, void *ctx_void
)
136 ucontext_t
*ctx
= ctx_void
;
138 printf("\tGot %s with RIP=%lx\n", signames
[sig
],
139 (unsigned long)ctx
->uc_mcontext
.gregs
[REG_IP
]);
141 siglongjmp(jmpbuf
, 1);
148 asm volatile ("mov %%ss, %[ss]" : [ss
] "=m" (ss
));
149 printf("\tSS = 0x%hx, &SS = 0x%p\n", ss
, &ss
);
151 if (prctl(PR_SET_PTRACER
, PR_SET_PTRACER_ANY
, 0, 0, 0) == 0)
152 printf("\tPR_SET_PTRACER_ANY succeeded\n");
154 printf("\tSet up a watchpoint\n");
155 sethandler(SIGTRAP
, sigtrap
, 0);
158 printf("[RUN]\tRead from watched memory (should get SIGTRAP)\n");
159 asm volatile ("mov %[ss], %[tmp]" : [tmp
] "=r" (nr
) : [ss
] "m" (ss
));
161 printf("[RUN]\tMOV SS; INT3\n");
162 asm volatile ("mov %[ss], %%ss; int3" :: [ss
] "m" (ss
));
164 printf("[RUN]\tMOV SS; INT 3\n");
165 asm volatile ("mov %[ss], %%ss; .byte 0xcd, 0x3" :: [ss
] "m" (ss
));
167 printf("[RUN]\tMOV SS; CS CS INT3\n");
168 asm volatile ("mov %[ss], %%ss; .byte 0x2e, 0x2e; int3" :: [ss
] "m" (ss
));
170 printf("[RUN]\tMOV SS; CSx14 INT3\n");
171 asm volatile ("mov %[ss], %%ss; .fill 14,1,0x2e; int3" :: [ss
] "m" (ss
));
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
));
178 printf("[RUN]\tMOV SS; INTO\n");
179 sethandler(SIGSEGV
, handle_and_return
, SA_RESETHAND
);
181 asm volatile ("add $1, %[tmp]; mov %[ss], %%ss; into"
182 : [tmp
] "+r" (nr
) : [ss
] "m" (ss
));
185 if (sigsetjmp(jmpbuf
, 1) == 0) {
186 printf("[RUN]\tMOV SS; ICEBP\n");
188 /* Some emulators (e.g. QEMU TCG) don't emulate ICEBP. */
189 sethandler(SIGILL
, handle_and_longjmp
, SA_RESETHAND
);
191 asm volatile ("mov %[ss], %%ss; .byte 0xf1" :: [ss
] "m" (ss
));
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
));
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
));
208 * INT $1: if #DB has DPL=3 and there isn't special handling,
209 * then the kernel will die.
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
));
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.
223 if (sigsetjmp(jmpbuf
, 1) == 0) {
224 printf("[RUN]\tMOV SS; SYSCALL\n");
225 sethandler(SIGILL
, handle_and_longjmp
, SA_RESETHAND
);
228 * Toggle the high bit of RSP to make it noncanonical to
229 * strengthen this test on non-SMAP systems.
231 asm volatile ("btc $63, %%rsp\n\t"
232 "mov %[ss], %%ss; syscall\n\t"
234 : "+a" (nr
) : [ss
] "m" (ss
)
243 printf("[RUN]\tMOV SS; breakpointed NOP\n");
244 asm volatile ("mov %[ss], %%ss; breakpoint_insn: nop" :: [ss
] "m" (ss
));
247 * Invoking SYSENTER directly breaks all the rules. Just handle
250 if (sigsetjmp(jmpbuf
, 1) == 0) {
251 printf("[RUN]\tMOV SS; SYSENTER\n");
253 .ss_sp
= altstack_data
,
256 if (sigaltstack(&stack
, NULL
) != 0)
257 err(1, "sigaltstack");
258 sethandler(SIGSEGV
, handle_and_longjmp
, SA_RESETHAND
| SA_ONSTACK
);
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"
268 /* We're unreachable here. SYSENTER forgets RIP. */
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
)
279 , "r8", "r9", "r10", "r11"
284 printf("[OK]\tI aten't dead\n");