File Index
Files and directories
Sign in
FILE

mov_ss_trap.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/x86/mov_ss_trap.c C
  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#include "helpers.h"
 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 char const * const signames[] = {
 98	[SIGSEGV] = "SIGSEGV",
 99	[SIGBUS] = "SIBGUS",
100	[SIGTRAP] = "SIGTRAP",
101	[SIGILL] = "SIGILL",
102};
103
104static void sigtrap(int sig, siginfo_t *si, void *ctx_void)
105{
106	ucontext_t *ctx = ctx_void;
107
108	printf("\tGot SIGTRAP with RIP=%lx, EFLAGS.RF=%d\n",
109	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP],
110	       !!(ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_RF));
111}
112
113static void handle_and_return(int sig, siginfo_t *si, void *ctx_void)
114{
115	ucontext_t *ctx = ctx_void;
116
117	printf("\tGot %s with RIP=%lx\n", signames[sig],
118	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
119}
120
121static void handle_and_longjmp(int sig, siginfo_t *si, void *ctx_void)
122{
123	ucontext_t *ctx = ctx_void;
124
125	printf("\tGot %s with RIP=%lx\n", signames[sig],
126	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
127
128	siglongjmp(jmpbuf, 1);
129}
130
131int main()
132{
133	unsigned long nr;
134
135	asm volatile ("mov %%ss, %[ss]" : [ss] "=m" (ss));
136	printf("\tSS = 0x%hx, &SS = 0x%p\n", ss, &ss);
137
138	if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) == 0)
139		printf("\tPR_SET_PTRACER_ANY succeeded\n");
140
141	printf("\tSet up a watchpoint\n");
142	sethandler(SIGTRAP, sigtrap, 0);
143	enable_watchpoint();
144
145	printf("[RUN]\tRead from watched memory (should get SIGTRAP)\n");
146	asm volatile ("mov %[ss], %[tmp]" : [tmp] "=r" (nr) : [ss] "m" (ss));
147
148	printf("[RUN]\tMOV SS; INT3\n");
149	asm volatile ("mov %[ss], %%ss; int3" :: [ss] "m" (ss));
150
151	printf("[RUN]\tMOV SS; INT 3\n");
152	asm volatile ("mov %[ss], %%ss; .byte 0xcd, 0x3" :: [ss] "m" (ss));
153
154	printf("[RUN]\tMOV SS; CS CS INT3\n");
155	asm volatile ("mov %[ss], %%ss; .byte 0x2e, 0x2e; int3" :: [ss] "m" (ss));
156
157	printf("[RUN]\tMOV SS; CSx14 INT3\n");
158	asm volatile ("mov %[ss], %%ss; .fill 14,1,0x2e; int3" :: [ss] "m" (ss));
159
160	printf("[RUN]\tMOV SS; INT 4\n");
161	sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
162	asm volatile ("mov %[ss], %%ss; int $4" :: [ss] "m" (ss));
163
164#ifdef __i386__
165	printf("[RUN]\tMOV SS; INTO\n");
166	sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
167	nr = -1;
168	asm volatile ("add $1, %[tmp]; mov %[ss], %%ss; into"
169		      : [tmp] "+r" (nr) : [ss] "m" (ss));
170#endif
171
172	if (sigsetjmp(jmpbuf, 1) == 0) {
173		printf("[RUN]\tMOV SS; ICEBP\n");
174
175		/* Some emulators (e.g. QEMU TCG) don't emulate ICEBP. */
176		sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
177
178		asm volatile ("mov %[ss], %%ss; .byte 0xf1" :: [ss] "m" (ss));
179	}
180
181	if (sigsetjmp(jmpbuf, 1) == 0) {
182		printf("[RUN]\tMOV SS; CLI\n");
183		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
184		asm volatile ("mov %[ss], %%ss; cli" :: [ss] "m" (ss));
185	}
186
187	if (sigsetjmp(jmpbuf, 1) == 0) {
188		printf("[RUN]\tMOV SS; #PF\n");
189		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
190		asm volatile ("mov %[ss], %%ss; mov (-1), %[tmp]"
191			      : [tmp] "=r" (nr) : [ss] "m" (ss));
192	}
193
194	/*
195	 * INT $1: if #DB has DPL=3 and there isn't special handling,
196	 * then the kernel will die.
197	 */
198	if (sigsetjmp(jmpbuf, 1) == 0) {
199		printf("[RUN]\tMOV SS; INT 1\n");
200		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
201		asm volatile ("mov %[ss], %%ss; int $1" :: [ss] "m" (ss));
202	}
203
204#ifdef __x86_64__
205	/*
206	 * In principle, we should test 32-bit SYSCALL as well, but
207	 * the calling convention is so unpredictable that it's
208	 * not obviously worth the effort.
209	 */
210	if (sigsetjmp(jmpbuf, 1) == 0) {
211		printf("[RUN]\tMOV SS; SYSCALL\n");
212		sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
213		nr = SYS_getpid;
214		/*
215		 * Toggle the high bit of RSP to make it noncanonical to
216		 * strengthen this test on non-SMAP systems.
217		 */
218		asm volatile ("btc $63, %%rsp\n\t"
219			      "mov %[ss], %%ss; syscall\n\t"
220			      "btc $63, %%rsp"
221			      : "+a" (nr) : [ss] "m" (ss)
222			      : "rcx"
223#ifdef __x86_64__
224				, "r11"
225#endif
226			);
227	}
228#endif
229
230	printf("[RUN]\tMOV SS; breakpointed NOP\n");
231	asm volatile ("mov %[ss], %%ss; breakpoint_insn: nop" :: [ss] "m" (ss));
232
233	/*
234	 * Invoking SYSENTER directly breaks all the rules.  Just handle
235	 * the SIGSEGV.
236	 */
237	if (sigsetjmp(jmpbuf, 1) == 0) {
238		printf("[RUN]\tMOV SS; SYSENTER\n");
239		stack_t stack = {
240			.ss_sp = malloc(sizeof(char) * SIGSTKSZ),
241			.ss_size = SIGSTKSZ,
242		};
243		if (sigaltstack(&stack, NULL) != 0)
244			err(1, "sigaltstack");
245		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND | SA_ONSTACK);
246		nr = SYS_getpid;
247		free(stack.ss_sp);
248		/* Clear EBP first to make sure we segfault cleanly. */
249		asm volatile ("xorl %%ebp, %%ebp; mov %[ss], %%ss; SYSENTER" : "+a" (nr)
250			      : [ss] "m" (ss) : "flags", "rcx"
251#ifdef __x86_64__
252				, "r11"
253#endif
254			);
255
256		/* We're unreachable here.  SYSENTER forgets RIP. */
257	}
258
259	if (sigsetjmp(jmpbuf, 1) == 0) {
260		printf("[RUN]\tMOV SS; INT $0x80\n");
261		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
262		nr = 20;	/* compat getpid */
263		asm volatile ("mov %[ss], %%ss; int $0x80"
264			      : "+a" (nr) : [ss] "m" (ss)
265			      : "flags"
266#ifdef __x86_64__
267				, "r8", "r9", "r10", "r11"
268#endif
269			);
270	}
271
272	printf("[OK]\tI aten't dead\n");
273	return 0;
274}
~/ Read-only file preview.