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 
dereference_seg_base(void)4240c45904SAndy Lutomirski static unsigned int dereference_seg_base(void)
4340c45904SAndy Lutomirski {
4440c45904SAndy Lutomirski 	int ret;
4540c45904SAndy Lutomirski 	asm volatile ("mov %" SEG ":(0), %0" : "=rm" (ret));
4640c45904SAndy Lutomirski 	return ret;
4740c45904SAndy Lutomirski }
4840c45904SAndy Lutomirski 
init_seg(void)4940c45904SAndy Lutomirski static void init_seg(void)
5040c45904SAndy Lutomirski {
5140c45904SAndy Lutomirski 	unsigned int *target = mmap(
5240c45904SAndy Lutomirski 		NULL, sizeof(unsigned int),
5340c45904SAndy Lutomirski 		PROT_READ | PROT_WRITE,
5440c45904SAndy Lutomirski 		MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
5540c45904SAndy Lutomirski 	if (target == MAP_FAILED)
5640c45904SAndy Lutomirski 		err(1, "mmap");
5740c45904SAndy Lutomirski 
5840c45904SAndy Lutomirski 	*target = EXPECTED_VALUE;
5940c45904SAndy Lutomirski 
6040c45904SAndy Lutomirski 	printf("\tsegment base address = 0x%lx\n", (unsigned long)target);
6140c45904SAndy Lutomirski 
6240c45904SAndy Lutomirski 	struct user_desc desc = {
6340c45904SAndy Lutomirski 		.entry_number    = 0,
6440c45904SAndy Lutomirski 		.base_addr       = (unsigned int)(uintptr_t)target,
6540c45904SAndy Lutomirski 		.limit           = sizeof(unsigned int) - 1,
6640c45904SAndy Lutomirski 		.seg_32bit       = 1,
6740c45904SAndy Lutomirski 		.contents        = 0, /* Data, grow-up */
6840c45904SAndy Lutomirski 		.read_exec_only  = 0,
6940c45904SAndy Lutomirski 		.limit_in_pages  = 0,
7040c45904SAndy Lutomirski 		.seg_not_present = 0,
7140c45904SAndy Lutomirski 		.useable         = 0
7240c45904SAndy Lutomirski 	};
7340c45904SAndy Lutomirski 	if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
7440c45904SAndy Lutomirski 		printf("\tusing LDT slot 0\n");
7540c45904SAndy Lutomirski 		asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7));
7640c45904SAndy Lutomirski 	} else {
7740c45904SAndy Lutomirski 		/* No modify_ldt for us (configured out, perhaps) */
7840c45904SAndy Lutomirski 
7940c45904SAndy Lutomirski 		struct user_desc *low_desc = mmap(
8040c45904SAndy Lutomirski 			NULL, sizeof(desc),
8140c45904SAndy Lutomirski 			PROT_READ | PROT_WRITE,
8240c45904SAndy Lutomirski 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
8340c45904SAndy Lutomirski 		memcpy(low_desc, &desc, sizeof(desc));
8440c45904SAndy Lutomirski 
8540c45904SAndy Lutomirski 		low_desc->entry_number = -1;
8640c45904SAndy Lutomirski 
8740c45904SAndy Lutomirski 		/* 32-bit set_thread_area */
8840c45904SAndy Lutomirski 		long ret;
8940c45904SAndy Lutomirski 		asm volatile ("int $0x80"
9040c45904SAndy Lutomirski 			      : "=a" (ret), "+m" (*low_desc)
9140c45904SAndy Lutomirski 			      : "a" (243), "b" (low_desc)
9240c45904SAndy Lutomirski #ifdef __x86_64__
9340c45904SAndy Lutomirski 			      : "r8", "r9", "r10", "r11"
9440c45904SAndy Lutomirski #endif
9540c45904SAndy Lutomirski 			);
9640c45904SAndy Lutomirski 		memcpy(&desc, low_desc, sizeof(desc));
9740c45904SAndy Lutomirski 		munmap(low_desc, sizeof(desc));
9840c45904SAndy Lutomirski 
9940c45904SAndy Lutomirski 		if (ret != 0) {
10040c45904SAndy Lutomirski 			printf("[NOTE]\tcould not create a segment -- can't test anything\n");
10140c45904SAndy Lutomirski 			exit(0);
10240c45904SAndy Lutomirski 		}
10340c45904SAndy Lutomirski 		printf("\tusing GDT slot %d\n", desc.entry_number);
10440c45904SAndy Lutomirski 
10540c45904SAndy Lutomirski 		unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3);
10640c45904SAndy Lutomirski 		asm volatile ("mov %0, %" SEG :: "rm" (sel));
10740c45904SAndy Lutomirski 	}
10840c45904SAndy Lutomirski }
10940c45904SAndy Lutomirski 
tracee_zap_segment(void)11040c45904SAndy Lutomirski static void tracee_zap_segment(void)
11140c45904SAndy Lutomirski {
11240c45904SAndy Lutomirski 	/*
11340c45904SAndy Lutomirski 	 * The tracer will redirect execution here.  This is meant to
11440c45904SAndy Lutomirski 	 * work like gdb's 'p func()' feature.  The tricky bit is that
11540c45904SAndy Lutomirski 	 * we modify a segment register in order to make sure that ptrace
11640c45904SAndy Lutomirski 	 * can correctly restore segment registers.
11740c45904SAndy Lutomirski 	 */
11840c45904SAndy Lutomirski 	printf("\tTracee: in tracee_zap_segment()\n");
11940c45904SAndy Lutomirski 
12040c45904SAndy Lutomirski 	/*
12140c45904SAndy Lutomirski 	 * Write a nonzero selector with base zero to the segment register.
12240c45904SAndy Lutomirski 	 * Using a null selector would defeat the test on AMD pre-Zen2
12340c45904SAndy Lutomirski 	 * CPUs, as such CPUs don't clear the base when loading a null
12440c45904SAndy Lutomirski 	 * selector.
12540c45904SAndy Lutomirski 	 */
12640c45904SAndy Lutomirski 	unsigned short sel;
12740c45904SAndy Lutomirski 	asm volatile ("mov %%ss, %0\n\t"
12840c45904SAndy Lutomirski 		      "mov %0, %" SEG
12940c45904SAndy Lutomirski 		      : "=rm" (sel));
13040c45904SAndy Lutomirski 
13140c45904SAndy Lutomirski 	pid_t pid = getpid(), tid = syscall(SYS_gettid);
13240c45904SAndy Lutomirski 
13340c45904SAndy Lutomirski 	printf("\tTracee is going back to sleep\n");
13440c45904SAndy Lutomirski 	syscall(SYS_tgkill, pid, tid, SIGSTOP);
13540c45904SAndy Lutomirski 
13640c45904SAndy Lutomirski 	/* Should not get here. */
13740c45904SAndy Lutomirski 	while (true) {
13840c45904SAndy Lutomirski 		printf("[FAIL]\tTracee hit unreachable code\n");
13940c45904SAndy Lutomirski 		pause();
14040c45904SAndy Lutomirski 	}
14140c45904SAndy Lutomirski }
14240c45904SAndy Lutomirski 
main()14340c45904SAndy Lutomirski int main()
14440c45904SAndy Lutomirski {
14540c45904SAndy Lutomirski 	printf("\tSetting up a segment\n");
14640c45904SAndy Lutomirski 	init_seg();
14740c45904SAndy Lutomirski 
14840c45904SAndy Lutomirski 	unsigned int val = dereference_seg_base();
14940c45904SAndy Lutomirski 	if (val != EXPECTED_VALUE) {
15040c45904SAndy Lutomirski 		printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
15140c45904SAndy Lutomirski 		return 1;
15240c45904SAndy Lutomirski 	}
15340c45904SAndy Lutomirski 	printf("[OK]\tThe segment points to the right place.\n");
15440c45904SAndy Lutomirski 
15540c45904SAndy Lutomirski 	pid_t chld = fork();
15640c45904SAndy Lutomirski 	if (chld < 0)
15740c45904SAndy Lutomirski 		err(1, "fork");
15840c45904SAndy Lutomirski 
15940c45904SAndy Lutomirski 	if (chld == 0) {
16040c45904SAndy Lutomirski 		prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0, 0);
16140c45904SAndy Lutomirski 
16240c45904SAndy Lutomirski 		if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
16340c45904SAndy Lutomirski 			err(1, "PTRACE_TRACEME");
16440c45904SAndy Lutomirski 
16540c45904SAndy Lutomirski 		pid_t pid = getpid(), tid = syscall(SYS_gettid);
16640c45904SAndy Lutomirski 
16740c45904SAndy Lutomirski 		printf("\tTracee will take a nap until signaled\n");
16840c45904SAndy Lutomirski 		syscall(SYS_tgkill, pid, tid, SIGSTOP);
16940c45904SAndy Lutomirski 
17040c45904SAndy Lutomirski 		printf("\tTracee was resumed.  Will re-check segment.\n");
17140c45904SAndy Lutomirski 
17240c45904SAndy Lutomirski 		val = dereference_seg_base();
17340c45904SAndy Lutomirski 		if (val != EXPECTED_VALUE) {
17440c45904SAndy Lutomirski 			printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
17540c45904SAndy Lutomirski 			exit(1);
17640c45904SAndy Lutomirski 		}
17740c45904SAndy Lutomirski 
17840c45904SAndy Lutomirski 		printf("[OK]\tThe segment points to the right place.\n");
17940c45904SAndy Lutomirski 		exit(0);
18040c45904SAndy Lutomirski 	}
18140c45904SAndy Lutomirski 
18240c45904SAndy Lutomirski 	int status;
18340c45904SAndy Lutomirski 
18440c45904SAndy Lutomirski 	/* Wait for SIGSTOP. */
18540c45904SAndy Lutomirski 	if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
18640c45904SAndy Lutomirski 		err(1, "waitpid");
18740c45904SAndy Lutomirski 
18840c45904SAndy Lutomirski 	struct user_regs_struct regs;
18940c45904SAndy Lutomirski 
19040c45904SAndy Lutomirski 	if (ptrace(PTRACE_GETREGS, chld, NULL, &regs) != 0)
19140c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
19240c45904SAndy Lutomirski 
19340c45904SAndy Lutomirski #ifdef __x86_64__
19440c45904SAndy Lutomirski 	printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs.gs, (unsigned long)regs.gs_base);
19540c45904SAndy Lutomirski #else
19640c45904SAndy Lutomirski 	printf("\tChild FS=0x%lx\n", (unsigned long)regs.xfs);
19740c45904SAndy Lutomirski #endif
19840c45904SAndy Lutomirski 
19940c45904SAndy Lutomirski 	struct user_regs_struct regs2 = regs;
20040c45904SAndy Lutomirski #ifdef __x86_64__
20140c45904SAndy Lutomirski 	regs2.rip = (unsigned long)tracee_zap_segment;
20240c45904SAndy Lutomirski 	regs2.rsp -= 128;	/* Don't clobber the redzone. */
20340c45904SAndy Lutomirski #else
20440c45904SAndy Lutomirski 	regs2.eip = (unsigned long)tracee_zap_segment;
20540c45904SAndy Lutomirski #endif
20640c45904SAndy Lutomirski 
20740c45904SAndy Lutomirski 	printf("\tTracer: redirecting tracee to tracee_zap_segment()\n");
20840c45904SAndy Lutomirski 	if (ptrace(PTRACE_SETREGS, chld, NULL, &regs2) != 0)
20940c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
21040c45904SAndy Lutomirski 	if (ptrace(PTRACE_CONT, chld, NULL, NULL) != 0)
21140c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
21240c45904SAndy Lutomirski 
21340c45904SAndy Lutomirski 	/* Wait for SIGSTOP. */
21440c45904SAndy Lutomirski 	if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
21540c45904SAndy Lutomirski 		err(1, "waitpid");
21640c45904SAndy Lutomirski 
21740c45904SAndy Lutomirski 	printf("\tTracer: restoring tracee state\n");
21840c45904SAndy Lutomirski 	if (ptrace(PTRACE_SETREGS, chld, NULL, &regs) != 0)
21940c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
22040c45904SAndy Lutomirski 	if (ptrace(PTRACE_DETACH, chld, NULL, NULL) != 0)
22140c45904SAndy Lutomirski 		err(1, "PTRACE_GETREGS");
22240c45904SAndy Lutomirski 
22340c45904SAndy Lutomirski 	/* Wait for SIGSTOP. */
22440c45904SAndy Lutomirski 	if (waitpid(chld, &status, 0) != chld)
22540c45904SAndy Lutomirski 		err(1, "waitpid");
22640c45904SAndy Lutomirski 
22740c45904SAndy Lutomirski 	if (WIFSIGNALED(status)) {
22840c45904SAndy Lutomirski 		printf("[FAIL]\tTracee crashed\n");
22940c45904SAndy Lutomirski 		return 1;
23040c45904SAndy Lutomirski 	}
23140c45904SAndy Lutomirski 
23240c45904SAndy Lutomirski 	if (!WIFEXITED(status)) {
23340c45904SAndy Lutomirski 		printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status);
23440c45904SAndy Lutomirski 		return 1;
23540c45904SAndy Lutomirski 	}
23640c45904SAndy Lutomirski 
23740c45904SAndy Lutomirski 	int exitcode = WEXITSTATUS(status);
23840c45904SAndy Lutomirski 	if (exitcode != 0) {
23940c45904SAndy Lutomirski 		printf("[FAIL]\tTracee reported failure\n");
24040c45904SAndy Lutomirski 		return 1;
24140c45904SAndy Lutomirski 	}
24240c45904SAndy Lutomirski 
24340c45904SAndy Lutomirski 	printf("[OK]\tAll is well.\n");
24440c45904SAndy Lutomirski 	return 0;
24540c45904SAndy Lutomirski }
246