162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * mov_ss_trap.c: Exercise the bizarre side effects of a watchpoint on MOV SS
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * This does MOV SS from a watchpointed address followed by various
662306a36Sopenharmony_ci * types of kernel entries.  A MOV SS that hits a watchpoint will queue
762306a36Sopenharmony_ci * up a #DB trap but will not actually deliver that trap.  The trap
862306a36Sopenharmony_ci * will be delivered after the next instruction instead.  The CPU's logic
962306a36Sopenharmony_ci * seems to be:
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci *  - Any fault: drop the pending #DB trap.
1262306a36Sopenharmony_ci *  - INT $N, INT3, INTO, SYSCALL, SYSENTER: enter the kernel and then
1362306a36Sopenharmony_ci *    deliver #DB.
1462306a36Sopenharmony_ci *  - ICEBP: enter the kernel but do not deliver the watchpoint trap
1562306a36Sopenharmony_ci *  - breakpoint: only one #DB is delivered (phew!)
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci * There are plenty of ways for a kernel to handle this incorrectly.  This
1862306a36Sopenharmony_ci * test tries to exercise all the cases.
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci * This should mostly cover CVE-2018-1087 and CVE-2018-8897.
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ci#define _GNU_SOURCE
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <stdlib.h>
2562306a36Sopenharmony_ci#include <sys/ptrace.h>
2662306a36Sopenharmony_ci#include <sys/types.h>
2762306a36Sopenharmony_ci#include <sys/wait.h>
2862306a36Sopenharmony_ci#include <sys/user.h>
2962306a36Sopenharmony_ci#include <sys/syscall.h>
3062306a36Sopenharmony_ci#include <unistd.h>
3162306a36Sopenharmony_ci#include <errno.h>
3262306a36Sopenharmony_ci#include <stddef.h>
3362306a36Sopenharmony_ci#include <stdio.h>
3462306a36Sopenharmony_ci#include <err.h>
3562306a36Sopenharmony_ci#include <string.h>
3662306a36Sopenharmony_ci#include <setjmp.h>
3762306a36Sopenharmony_ci#include <sys/prctl.h>
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define X86_EFLAGS_RF (1UL << 16)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#if __x86_64__
4262306a36Sopenharmony_ci# define REG_IP REG_RIP
4362306a36Sopenharmony_ci#else
4462306a36Sopenharmony_ci# define REG_IP REG_EIP
4562306a36Sopenharmony_ci#endif
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ciunsigned short ss;
4862306a36Sopenharmony_ciextern unsigned char breakpoint_insn[];
4962306a36Sopenharmony_cisigjmp_buf jmpbuf;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic void enable_watchpoint(void)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	pid_t parent = getpid();
5462306a36Sopenharmony_ci	int status;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	pid_t child = fork();
5762306a36Sopenharmony_ci	if (child < 0)
5862306a36Sopenharmony_ci		err(1, "fork");
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (child) {
6162306a36Sopenharmony_ci		if (waitpid(child, &status, 0) != child)
6262306a36Sopenharmony_ci			err(1, "waitpid for child");
6362306a36Sopenharmony_ci	} else {
6462306a36Sopenharmony_ci		unsigned long dr0, dr1, dr7;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci		dr0 = (unsigned long)&ss;
6762306a36Sopenharmony_ci		dr1 = (unsigned long)breakpoint_insn;
6862306a36Sopenharmony_ci		dr7 = ((1UL << 1) |	/* G0 */
6962306a36Sopenharmony_ci		       (3UL << 16) |	/* RW0 = read or write */
7062306a36Sopenharmony_ci		       (1UL << 18) |	/* LEN0 = 2 bytes */
7162306a36Sopenharmony_ci		       (1UL << 3));	/* G1, RW1 = insn */
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci		if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) != 0)
7462306a36Sopenharmony_ci			err(1, "PTRACE_ATTACH");
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci		if (waitpid(parent, &status, 0) != parent)
7762306a36Sopenharmony_ci			err(1, "waitpid for child");
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[0]), dr0) != 0)
8062306a36Sopenharmony_ci			err(1, "PTRACE_POKEUSER DR0");
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[1]), dr1) != 0)
8362306a36Sopenharmony_ci			err(1, "PTRACE_POKEUSER DR1");
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci		if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[7]), dr7) != 0)
8662306a36Sopenharmony_ci			err(1, "PTRACE_POKEUSER DR7");
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci		printf("\tDR0 = %lx, DR1 = %lx, DR7 = %lx\n", dr0, dr1, dr7);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci		if (ptrace(PTRACE_DETACH, parent, NULL, NULL) != 0)
9162306a36Sopenharmony_ci			err(1, "PTRACE_DETACH");
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		exit(0);
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
9862306a36Sopenharmony_ci		       int flags)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	struct sigaction sa;
10162306a36Sopenharmony_ci	memset(&sa, 0, sizeof(sa));
10262306a36Sopenharmony_ci	sa.sa_sigaction = handler;
10362306a36Sopenharmony_ci	sa.sa_flags = SA_SIGINFO | flags;
10462306a36Sopenharmony_ci	sigemptyset(&sa.sa_mask);
10562306a36Sopenharmony_ci	if (sigaction(sig, &sa, 0))
10662306a36Sopenharmony_ci		err(1, "sigaction");
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic char const * const signames[] = {
11062306a36Sopenharmony_ci	[SIGSEGV] = "SIGSEGV",
11162306a36Sopenharmony_ci	[SIGBUS] = "SIBGUS",
11262306a36Sopenharmony_ci	[SIGTRAP] = "SIGTRAP",
11362306a36Sopenharmony_ci	[SIGILL] = "SIGILL",
11462306a36Sopenharmony_ci};
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic void sigtrap(int sig, siginfo_t *si, void *ctx_void)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	ucontext_t *ctx = ctx_void;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	printf("\tGot SIGTRAP with RIP=%lx, EFLAGS.RF=%d\n",
12162306a36Sopenharmony_ci	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP],
12262306a36Sopenharmony_ci	       !!(ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_RF));
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic void handle_and_return(int sig, siginfo_t *si, void *ctx_void)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	ucontext_t *ctx = ctx_void;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	printf("\tGot %s with RIP=%lx\n", signames[sig],
13062306a36Sopenharmony_ci	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic void handle_and_longjmp(int sig, siginfo_t *si, void *ctx_void)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	ucontext_t *ctx = ctx_void;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	printf("\tGot %s with RIP=%lx\n", signames[sig],
13862306a36Sopenharmony_ci	       (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	siglongjmp(jmpbuf, 1);
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ciint main()
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	unsigned long nr;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	asm volatile ("mov %%ss, %[ss]" : [ss] "=m" (ss));
14862306a36Sopenharmony_ci	printf("\tSS = 0x%hx, &SS = 0x%p\n", ss, &ss);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) == 0)
15162306a36Sopenharmony_ci		printf("\tPR_SET_PTRACER_ANY succeeded\n");
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	printf("\tSet up a watchpoint\n");
15462306a36Sopenharmony_ci	sethandler(SIGTRAP, sigtrap, 0);
15562306a36Sopenharmony_ci	enable_watchpoint();
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	printf("[RUN]\tRead from watched memory (should get SIGTRAP)\n");
15862306a36Sopenharmony_ci	asm volatile ("mov %[ss], %[tmp]" : [tmp] "=r" (nr) : [ss] "m" (ss));
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	printf("[RUN]\tMOV SS; INT3\n");
16162306a36Sopenharmony_ci	asm volatile ("mov %[ss], %%ss; int3" :: [ss] "m" (ss));
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	printf("[RUN]\tMOV SS; INT 3\n");
16462306a36Sopenharmony_ci	asm volatile ("mov %[ss], %%ss; .byte 0xcd, 0x3" :: [ss] "m" (ss));
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	printf("[RUN]\tMOV SS; CS CS INT3\n");
16762306a36Sopenharmony_ci	asm volatile ("mov %[ss], %%ss; .byte 0x2e, 0x2e; int3" :: [ss] "m" (ss));
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	printf("[RUN]\tMOV SS; CSx14 INT3\n");
17062306a36Sopenharmony_ci	asm volatile ("mov %[ss], %%ss; .fill 14,1,0x2e; int3" :: [ss] "m" (ss));
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	printf("[RUN]\tMOV SS; INT 4\n");
17362306a36Sopenharmony_ci	sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
17462306a36Sopenharmony_ci	asm volatile ("mov %[ss], %%ss; int $4" :: [ss] "m" (ss));
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci#ifdef __i386__
17762306a36Sopenharmony_ci	printf("[RUN]\tMOV SS; INTO\n");
17862306a36Sopenharmony_ci	sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
17962306a36Sopenharmony_ci	nr = -1;
18062306a36Sopenharmony_ci	asm volatile ("add $1, %[tmp]; mov %[ss], %%ss; into"
18162306a36Sopenharmony_ci		      : [tmp] "+r" (nr) : [ss] "m" (ss));
18262306a36Sopenharmony_ci#endif
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
18562306a36Sopenharmony_ci		printf("[RUN]\tMOV SS; ICEBP\n");
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci		/* Some emulators (e.g. QEMU TCG) don't emulate ICEBP. */
18862306a36Sopenharmony_ci		sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci		asm volatile ("mov %[ss], %%ss; .byte 0xf1" :: [ss] "m" (ss));
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
19462306a36Sopenharmony_ci		printf("[RUN]\tMOV SS; CLI\n");
19562306a36Sopenharmony_ci		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
19662306a36Sopenharmony_ci		asm volatile ("mov %[ss], %%ss; cli" :: [ss] "m" (ss));
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
20062306a36Sopenharmony_ci		printf("[RUN]\tMOV SS; #PF\n");
20162306a36Sopenharmony_ci		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
20262306a36Sopenharmony_ci		asm volatile ("mov %[ss], %%ss; mov (-1), %[tmp]"
20362306a36Sopenharmony_ci			      : [tmp] "=r" (nr) : [ss] "m" (ss));
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	/*
20762306a36Sopenharmony_ci	 * INT $1: if #DB has DPL=3 and there isn't special handling,
20862306a36Sopenharmony_ci	 * then the kernel will die.
20962306a36Sopenharmony_ci	 */
21062306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
21162306a36Sopenharmony_ci		printf("[RUN]\tMOV SS; INT 1\n");
21262306a36Sopenharmony_ci		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
21362306a36Sopenharmony_ci		asm volatile ("mov %[ss], %%ss; int $1" :: [ss] "m" (ss));
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci#ifdef __x86_64__
21762306a36Sopenharmony_ci	/*
21862306a36Sopenharmony_ci	 * In principle, we should test 32-bit SYSCALL as well, but
21962306a36Sopenharmony_ci	 * the calling convention is so unpredictable that it's
22062306a36Sopenharmony_ci	 * not obviously worth the effort.
22162306a36Sopenharmony_ci	 */
22262306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
22362306a36Sopenharmony_ci		printf("[RUN]\tMOV SS; SYSCALL\n");
22462306a36Sopenharmony_ci		sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
22562306a36Sopenharmony_ci		nr = SYS_getpid;
22662306a36Sopenharmony_ci		/*
22762306a36Sopenharmony_ci		 * Toggle the high bit of RSP to make it noncanonical to
22862306a36Sopenharmony_ci		 * strengthen this test on non-SMAP systems.
22962306a36Sopenharmony_ci		 */
23062306a36Sopenharmony_ci		asm volatile ("btc $63, %%rsp\n\t"
23162306a36Sopenharmony_ci			      "mov %[ss], %%ss; syscall\n\t"
23262306a36Sopenharmony_ci			      "btc $63, %%rsp"
23362306a36Sopenharmony_ci			      : "+a" (nr) : [ss] "m" (ss)
23462306a36Sopenharmony_ci			      : "rcx"
23562306a36Sopenharmony_ci#ifdef __x86_64__
23662306a36Sopenharmony_ci				, "r11"
23762306a36Sopenharmony_ci#endif
23862306a36Sopenharmony_ci			);
23962306a36Sopenharmony_ci	}
24062306a36Sopenharmony_ci#endif
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	printf("[RUN]\tMOV SS; breakpointed NOP\n");
24362306a36Sopenharmony_ci	asm volatile ("mov %[ss], %%ss; breakpoint_insn: nop" :: [ss] "m" (ss));
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	/*
24662306a36Sopenharmony_ci	 * Invoking SYSENTER directly breaks all the rules.  Just handle
24762306a36Sopenharmony_ci	 * the SIGSEGV.
24862306a36Sopenharmony_ci	 */
24962306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
25062306a36Sopenharmony_ci		printf("[RUN]\tMOV SS; SYSENTER\n");
25162306a36Sopenharmony_ci		stack_t stack = {
25262306a36Sopenharmony_ci			.ss_sp = malloc(sizeof(char) * SIGSTKSZ),
25362306a36Sopenharmony_ci			.ss_size = SIGSTKSZ,
25462306a36Sopenharmony_ci		};
25562306a36Sopenharmony_ci		if (sigaltstack(&stack, NULL) != 0)
25662306a36Sopenharmony_ci			err(1, "sigaltstack");
25762306a36Sopenharmony_ci		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND | SA_ONSTACK);
25862306a36Sopenharmony_ci		nr = SYS_getpid;
25962306a36Sopenharmony_ci		free(stack.ss_sp);
26062306a36Sopenharmony_ci		/* Clear EBP first to make sure we segfault cleanly. */
26162306a36Sopenharmony_ci		asm volatile ("xorl %%ebp, %%ebp; mov %[ss], %%ss; SYSENTER" : "+a" (nr)
26262306a36Sopenharmony_ci			      : [ss] "m" (ss) : "flags", "rcx"
26362306a36Sopenharmony_ci#ifdef __x86_64__
26462306a36Sopenharmony_ci				, "r11"
26562306a36Sopenharmony_ci#endif
26662306a36Sopenharmony_ci			);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci		/* We're unreachable here.  SYSENTER forgets RIP. */
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
27262306a36Sopenharmony_ci		printf("[RUN]\tMOV SS; INT $0x80\n");
27362306a36Sopenharmony_ci		sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
27462306a36Sopenharmony_ci		nr = 20;	/* compat getpid */
27562306a36Sopenharmony_ci		asm volatile ("mov %[ss], %%ss; int $0x80"
27662306a36Sopenharmony_ci			      : "+a" (nr) : [ss] "m" (ss)
27762306a36Sopenharmony_ci			      : "flags"
27862306a36Sopenharmony_ci#ifdef __x86_64__
27962306a36Sopenharmony_ci				, "r8", "r9", "r10", "r11"
28062306a36Sopenharmony_ci#endif
28162306a36Sopenharmony_ci			);
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	printf("[OK]\tI aten't dead\n");
28562306a36Sopenharmony_ci	return 0;
28662306a36Sopenharmony_ci}
287