xref: /linux/tools/testing/selftests/x86/fsgsbase_restore.c (revision c771600c6af14749609b49565ffb4cac2959710d)
140c45904SAndy Lutomirski // SPDX-License-Identifier: GPL-2.0-only
240c45904SAndy Lutomirski /*
340c45904SAndy Lutomirski  * fsgsbase_restore.c, test ptrace vs fsgsbase
440c45904SAndy Lutomirski  * Copyright (c) 2020 Andy Lutomirski
540c45904SAndy Lutomirski  *
640c45904SAndy Lutomirski  * This test case simulates a tracer redirecting tracee execution to
740c45904SAndy Lutomirski  * a function and then restoring tracee state using PTRACE_GETREGS and
840c45904SAndy Lutomirski  * PTRACE_SETREGS.  This is similar to what gdb does when doing
940c45904SAndy Lutomirski  * 'p func()'.  The catch is that this test has the called function
1040c45904SAndy Lutomirski  * modify a segment register.  This makes sure that ptrace correctly
1140c45904SAndy Lutomirski  * restores segment state when using PTRACE_SETREGS.
1240c45904SAndy Lutomirski  *
1340c45904SAndy Lutomirski  * This is not part of fsgsbase.c, because that test is 64-bit only.
1440c45904SAndy Lutomirski  */
1540c45904SAndy Lutomirski 
1640c45904SAndy Lutomirski #define _GNU_SOURCE
1740c45904SAndy Lutomirski #include <stdio.h>
1840c45904SAndy Lutomirski #include <stdlib.h>
1940c45904SAndy Lutomirski #include <stdbool.h>
2040c45904SAndy Lutomirski #include <string.h>
2140c45904SAndy Lutomirski #include <sys/syscall.h>
2240c45904SAndy Lutomirski #include <unistd.h>
2340c45904SAndy Lutomirski #include <err.h>
2440c45904SAndy Lutomirski #include <sys/user.h>
2540c45904SAndy Lutomirski #include <asm/prctl.h>
2640c45904SAndy Lutomirski #include <sys/prctl.h>
2740c45904SAndy Lutomirski #include <asm/ldt.h>
2840c45904SAndy Lutomirski #include <sys/mman.h>
2940c45904SAndy Lutomirski #include <stddef.h>
3040c45904SAndy Lutomirski #include <sys/ptrace.h>
3140c45904SAndy Lutomirski #include <sys/wait.h>
3240c45904SAndy Lutomirski #include <stdint.h>
3340c45904SAndy Lutomirski 
3440c45904SAndy Lutomirski #define EXPECTED_VALUE 0x1337f00d
3540c45904SAndy Lutomirski 
3640c45904SAndy Lutomirski #ifdef __x86_64__
3740c45904SAndy Lutomirski # define SEG "%gs"
3840c45904SAndy Lutomirski #else
3940c45904SAndy Lutomirski # define SEG "%fs"
4040c45904SAndy Lutomirski #endif
4140c45904SAndy Lutomirski 
4211586553SJohn Hubbard /*
4311586553SJohn Hubbard  * Defined in clang_helpers_[32|64].S, because unlike gcc, clang inline asm does
4411586553SJohn Hubbard  * not support segmentation prefixes.
4511586553SJohn Hubbard  */
4611586553SJohn Hubbard unsigned int dereference_seg_base(void);
4740c45904SAndy Lutomirski 
init_seg(void)4840c45904SAndy Lutomirski static void init_seg(void)
4940c45904SAndy Lutomirski {
5040c45904SAndy Lutomirski 	unsigned int *target = mmap(
5140c45904SAndy Lutomirski 		NULL, sizeof(unsigned int),
5240c45904SAndy Lutomirski 		PROT_READ | PROT_WRITE,
5340c45904SAndy Lutomirski 		MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
5440c45904SAndy Lutomirski 	if (target == MAP_FAILED)
5540c45904SAndy Lutomirski 		err(1, "mmap");
5640c45904SAndy Lutomirski 
5740c45904SAndy Lutomirski 	*target = EXPECTED_VALUE;
5840c45904SAndy Lutomirski 
5940c45904SAndy Lutomirski 	printf("\tsegment base address = 0x%lx\n", (unsigned long)target);
6040c45904SAndy Lutomirski 
6140c45904SAndy Lutomirski 	struct user_desc desc = {
6240c45904SAndy Lutomirski 		.entry_number    = 0,
6340c45904SAndy Lutomirski 		.base_addr       = (unsigned int)(uintptr_t)target,
6440c45904SAndy Lutomirski 		.limit           = sizeof(unsigned int) - 1,
6540c45904SAndy Lutomirski 		.seg_32bit       = 1,
6640c45904SAndy Lutomirski 		.contents        = 0, /* Data, grow-up */
6740c45904SAndy Lutomirski 		.read_exec_only  = 0,
6840c45904SAndy Lutomirski 		.limit_in_pages  = 0,
6940c45904SAndy Lutomirski 		.seg_not_present = 0,
7040c45904SAndy Lutomirski 		.useable         = 0
7140c45904SAndy Lutomirski 	};
7240c45904SAndy Lutomirski 	if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
7340c45904SAndy Lutomirski 		printf("\tusing LDT slot 0\n");
7440c45904SAndy Lutomirski 		asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7));
7540c45904SAndy Lutomirski 	} else {
7640c45904SAndy Lutomirski 		/* No modify_ldt for us (configured out, perhaps) */
7740c45904SAndy Lutomirski 
7840c45904SAndy Lutomirski 		struct user_desc *low_desc = mmap(
7940c45904SAndy Lutomirski 			NULL, sizeof(desc),
8040c45904SAndy Lutomirski 			PROT_READ | PROT_WRITE,
8140c45904SAndy Lutomirski 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
8240c45904SAndy Lutomirski 		memcpy(low_desc, &desc, sizeof(desc));
8340c45904SAndy Lutomirski 
8440c45904SAndy Lutomirski 		low_desc->entry_number = -1;
8540c45904SAndy Lutomirski 
8640c45904SAndy Lutomirski 		/* 32-bit set_thread_area */
8740c45904SAndy Lutomirski 		long ret;
8840c45904SAndy Lutomirski 		asm volatile ("int $0x80"
8940c45904SAndy Lutomirski 			      : "=a" (ret), "+m" (*low_desc)
9040c45904SAndy Lutomirski 			      : "a" (243), "b" (low_desc)
9140c45904SAndy Lutomirski #ifdef __x86_64__
9240c45904SAndy Lutomirski 			      : "r8", "r9", "r10", "r11"
9340c45904SAndy Lutomirski #endif
9440c45904SAndy Lutomirski 			);
9540c45904SAndy Lutomirski 		memcpy(&desc, low_desc, sizeof(desc));
9640c45904SAndy Lutomirski 		munmap(low_desc, sizeof(desc));
9740c45904SAndy Lutomirski 
9840c45904SAndy Lutomirski 		if (ret != 0) {
9940c45904SAndy Lutomirski 			printf("[NOTE]\tcould not create a segment -- can't test anything\n");
10040c45904SAndy Lutomirski 			exit(0);
10140c45904SAndy Lutomirski 		}
10240c45904SAndy Lutomirski 		printf("\tusing GDT slot %d\n", desc.entry_number);
10340c45904SAndy Lutomirski 
10440c45904SAndy Lutomirski 		unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3);
10540c45904SAndy Lutomirski 		asm volatile ("mov %0, %" SEG :: "rm" (sel));
10640c45904SAndy Lutomirski 	}
10740c45904SAndy Lutomirski }
10840c45904SAndy Lutomirski 
tracee_zap_segment(void)10940c45904SAndy Lutomirski static void tracee_zap_segment(void)
11040c45904SAndy Lutomirski {
11140c45904SAndy Lutomirski 	/*
11240c45904SAndy Lutomirski 	 * The tracer will redirect execution here.  This is meant to
11340c45904SAndy Lutomirski 	 * work like gdb's 'p func()' feature.  The tricky bit is that
11440c45904SAndy Lutomirski 	 * we modify a segment register in order to make sure that ptrace
11540c45904SAndy Lutomirski 	 * can correctly restore segment registers.
11640c45904SAndy Lutomirski 	 */
11740c45904SAndy Lutomirski 	printf("\tTracee: in tracee_zap_segment()\n");
11840c45904SAndy Lutomirski 
11940c45904SAndy Lutomirski 	/*
12040c45904SAndy Lutomirski 	 * Write a nonzero selector with base zero to the segment register.
12140c45904SAndy Lutomirski 	 * Using a null selector would defeat the test on AMD pre-Zen2
12240c45904SAndy Lutomirski 	 * CPUs, as such CPUs don't clear the base when loading a null
12340c45904SAndy Lutomirski 	 * selector.
12440c45904SAndy Lutomirski 	 */
12540c45904SAndy Lutomirski 	unsigned short sel;
12640c45904SAndy Lutomirski 	asm volatile ("mov %%ss, %0\n\t"
12740c45904SAndy Lutomirski 		      "mov %0, %" SEG
12840c45904SAndy Lutomirski 		      : "=rm" (sel));
12940c45904SAndy Lutomirski 
13040c45904SAndy Lutomirski 	pid_t pid = getpid(), tid = syscall(SYS_gettid);
13140c45904SAndy Lutomirski 
13240c45904SAndy Lutomirski 	printf("\tTracee is going back to sleep\n");
13340c45904SAndy Lutomirski 	syscall(SYS_tgkill, pid, tid, SIGSTOP);
13440c45904SAndy Lutomirski 
13540c45904SAndy Lutomirski 	/* Should not get here. */
13640c45904SAndy Lutomirski 	while (true) {
13740c45904SAndy Lutomirski 		printf("[FAIL]\tTracee hit unreachable code\n");
13840c45904SAndy Lutomirski 		pause();
13940c45904SAndy Lutomirski 	}
14040c45904SAndy Lutomirski }
14140c45904SAndy Lutomirski 
main()14240c45904SAndy Lutomirski int main()
14340c45904SAndy Lutomirski {
14440c45904SAndy Lutomirski 	printf("\tSetting up a segment\n");
14540c45904SAndy Lutomirski 	init_seg();
14640c45904SAndy Lutomirski 
14740c45904SAndy Lutomirski 	unsigned int val = dereference_seg_base();
14840c45904SAndy Lutomirski 	if (val != EXPECTED_VALUE) {
14940c45904SAndy Lutomirski 		printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
15040c45904SAndy Lutomirski 		return 1;
15140c45904SAndy Lutomirski 	}
15240c45904SAndy Lutomirski 	printf("[OK]\tThe segment points to the right place.\n");
15340c45904SAndy Lutomirski 
15440c45904SAndy Lutomirski 	pid_t chld = fork();
15540c45904SAndy Lutomirski 	if (chld < 0)
15640c45904SAndy Lutomirski 		err(1, "fork");
15740c45904SAndy Lutomirski 
15840c45904SAndy Lutomirski 	if (chld == 0) {
15940c45904SAndy Lutomirski 		prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0, 0);
16040c45904SAndy Lutomirski 
16140c45904SAndy Lutomirski 		if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
16240c45904SAndy Lutomirski 			err(1, "PTRACE_TRACEME");
16340c45904SAndy Lutomirski 
16440c45904SAndy Lutomirski 		pid_t pid = getpid(), tid = syscall(SYS_gettid);
16540c45904SAndy Lutomirski 
16640c45904SAndy Lutomirski 		printf("\tTracee will take a nap until signaled\n");
16740c45904SAndy Lutomirski 		syscall(SYS_tgkill, pid, tid, SIGSTOP);
16840c45904SAndy Lutomirski 
16940c45904SAndy Lutomirski 		printf("\tTracee was resumed.  Will re-check segment.\n");
17040c45904SAndy Lutomirski 
17140c45904SAndy Lutomirski 		val = dereference_seg_base();
17240c45904SAndy Lutomirski 		if (val != EXPECTED_VALUE) {
17340c45904SAndy Lutomirski 			printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
17440c45904SAndy Lutomirski 			exit(1);
17540c45904SAndy Lutomirski 		}
17640c45904SAndy Lutomirski 
17740c45904SAndy Lutomirski 		printf("[OK]\tThe segment points to the right place.\n");
17840c45904SAndy Lutomirski 		exit(0);
17940c45904SAndy Lutomirski 	}
18040c45904SAndy Lutomirski 
18140c45904SAndy Lutomirski 	int status;
18240c45904SAndy Lutomirski 
18340c45904SAndy Lutomirski 	/* Wait for SIGSTOP. */
18440c45904SAndy Lutomirski 	if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
18540c45904SAndy Lutomirski 		err(1, "waitpid");
18640c45904SAndy Lutomirski 
18740c45904SAndy Lutomirski 	struct user_regs_struct regs;
18840c45904SAndy Lutomirski 
18940c45904SAndy Lutomirski 	if (ptrace(PTRACE_GETREGS, chld, NULL, &regs) != 0)
19040c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
19140c45904SAndy Lutomirski 
19240c45904SAndy Lutomirski #ifdef __x86_64__
19340c45904SAndy Lutomirski 	printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs.gs, (unsigned long)regs.gs_base);
19440c45904SAndy Lutomirski #else
19540c45904SAndy Lutomirski 	printf("\tChild FS=0x%lx\n", (unsigned long)regs.xfs);
19640c45904SAndy Lutomirski #endif
19740c45904SAndy Lutomirski 
19840c45904SAndy Lutomirski 	struct user_regs_struct regs2 = regs;
19940c45904SAndy Lutomirski #ifdef __x86_64__
20040c45904SAndy Lutomirski 	regs2.rip = (unsigned long)tracee_zap_segment;
20140c45904SAndy Lutomirski 	regs2.rsp -= 128;	/* Don't clobber the redzone. */
20240c45904SAndy Lutomirski #else
20340c45904SAndy Lutomirski 	regs2.eip = (unsigned long)tracee_zap_segment;
20440c45904SAndy Lutomirski #endif
20540c45904SAndy Lutomirski 
20640c45904SAndy Lutomirski 	printf("\tTracer: redirecting tracee to tracee_zap_segment()\n");
20740c45904SAndy Lutomirski 	if (ptrace(PTRACE_SETREGS, chld, NULL, &regs2) != 0)
20840c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
20940c45904SAndy Lutomirski 	if (ptrace(PTRACE_CONT, chld, NULL, NULL) != 0)
21040c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
21140c45904SAndy Lutomirski 
21240c45904SAndy Lutomirski 	/* Wait for SIGSTOP. */
21340c45904SAndy Lutomirski 	if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
21440c45904SAndy Lutomirski 		err(1, "waitpid");
21540c45904SAndy Lutomirski 
21640c45904SAndy Lutomirski 	printf("\tTracer: restoring tracee state\n");
21740c45904SAndy Lutomirski 	if (ptrace(PTRACE_SETREGS, chld, NULL, &regs) != 0)
21840c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
21940c45904SAndy Lutomirski 	if (ptrace(PTRACE_DETACH, chld, NULL, NULL) != 0)
22040c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
22140c45904SAndy Lutomirski 
22240c45904SAndy Lutomirski 	/* Wait for SIGSTOP. */
22340c45904SAndy Lutomirski 	if (waitpid(chld, &status, 0) != chld)
22440c45904SAndy Lutomirski 		err(1, "waitpid");
22540c45904SAndy Lutomirski 
22640c45904SAndy Lutomirski 	if (WIFSIGNALED(status)) {
22740c45904SAndy Lutomirski 		printf("[FAIL]\tTracee crashed\n");
22840c45904SAndy Lutomirski 		return 1;
22940c45904SAndy Lutomirski 	}
23040c45904SAndy Lutomirski 
23140c45904SAndy Lutomirski 	if (!WIFEXITED(status)) {
23240c45904SAndy Lutomirski 		printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status);
23340c45904SAndy Lutomirski 		return 1;
23440c45904SAndy Lutomirski 	}
23540c45904SAndy Lutomirski 
23640c45904SAndy Lutomirski 	int exitcode = WEXITSTATUS(status);
23740c45904SAndy Lutomirski 	if (exitcode != 0) {
23840c45904SAndy Lutomirski 		printf("[FAIL]\tTracee reported failure\n");
23940c45904SAndy Lutomirski 		return 1;
24040c45904SAndy Lutomirski 	}
24140c45904SAndy Lutomirski 
24240c45904SAndy Lutomirski 	printf("[OK]\tAll is well.\n");
24340c45904SAndy Lutomirski 	return 0;
24440c45904SAndy Lutomirski }
245