162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * fsgsbase_restore.c, test ptrace vs fsgsbase
462306a36Sopenharmony_ci * Copyright (c) 2020 Andy Lutomirski
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This test case simulates a tracer redirecting tracee execution to
762306a36Sopenharmony_ci * a function and then restoring tracee state using PTRACE_GETREGS and
862306a36Sopenharmony_ci * PTRACE_SETREGS.  This is similar to what gdb does when doing
962306a36Sopenharmony_ci * 'p func()'.  The catch is that this test has the called function
1062306a36Sopenharmony_ci * modify a segment register.  This makes sure that ptrace correctly
1162306a36Sopenharmony_ci * restores segment state when using PTRACE_SETREGS.
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * This is not part of fsgsbase.c, because that test is 64-bit only.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define _GNU_SOURCE
1762306a36Sopenharmony_ci#include <stdio.h>
1862306a36Sopenharmony_ci#include <stdlib.h>
1962306a36Sopenharmony_ci#include <stdbool.h>
2062306a36Sopenharmony_ci#include <string.h>
2162306a36Sopenharmony_ci#include <sys/syscall.h>
2262306a36Sopenharmony_ci#include <unistd.h>
2362306a36Sopenharmony_ci#include <err.h>
2462306a36Sopenharmony_ci#include <sys/user.h>
2562306a36Sopenharmony_ci#include <asm/prctl.h>
2662306a36Sopenharmony_ci#include <sys/prctl.h>
2762306a36Sopenharmony_ci#include <asm/ldt.h>
2862306a36Sopenharmony_ci#include <sys/mman.h>
2962306a36Sopenharmony_ci#include <stddef.h>
3062306a36Sopenharmony_ci#include <sys/ptrace.h>
3162306a36Sopenharmony_ci#include <sys/wait.h>
3262306a36Sopenharmony_ci#include <stdint.h>
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define EXPECTED_VALUE 0x1337f00d
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#ifdef __x86_64__
3762306a36Sopenharmony_ci# define SEG "%gs"
3862306a36Sopenharmony_ci#else
3962306a36Sopenharmony_ci# define SEG "%fs"
4062306a36Sopenharmony_ci#endif
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic unsigned int dereference_seg_base(void)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	int ret;
4562306a36Sopenharmony_ci	asm volatile ("mov %" SEG ":(0), %0" : "=rm" (ret));
4662306a36Sopenharmony_ci	return ret;
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic void init_seg(void)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	unsigned int *target = mmap(
5262306a36Sopenharmony_ci		NULL, sizeof(unsigned int),
5362306a36Sopenharmony_ci		PROT_READ | PROT_WRITE,
5462306a36Sopenharmony_ci		MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
5562306a36Sopenharmony_ci	if (target == MAP_FAILED)
5662306a36Sopenharmony_ci		err(1, "mmap");
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	*target = EXPECTED_VALUE;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	printf("\tsegment base address = 0x%lx\n", (unsigned long)target);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	struct user_desc desc = {
6362306a36Sopenharmony_ci		.entry_number    = 0,
6462306a36Sopenharmony_ci		.base_addr       = (unsigned int)(uintptr_t)target,
6562306a36Sopenharmony_ci		.limit           = sizeof(unsigned int) - 1,
6662306a36Sopenharmony_ci		.seg_32bit       = 1,
6762306a36Sopenharmony_ci		.contents        = 0, /* Data, grow-up */
6862306a36Sopenharmony_ci		.read_exec_only  = 0,
6962306a36Sopenharmony_ci		.limit_in_pages  = 0,
7062306a36Sopenharmony_ci		.seg_not_present = 0,
7162306a36Sopenharmony_ci		.useable         = 0
7262306a36Sopenharmony_ci	};
7362306a36Sopenharmony_ci	if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
7462306a36Sopenharmony_ci		printf("\tusing LDT slot 0\n");
7562306a36Sopenharmony_ci		asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7));
7662306a36Sopenharmony_ci	} else {
7762306a36Sopenharmony_ci		/* No modify_ldt for us (configured out, perhaps) */
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		struct user_desc *low_desc = mmap(
8062306a36Sopenharmony_ci			NULL, sizeof(desc),
8162306a36Sopenharmony_ci			PROT_READ | PROT_WRITE,
8262306a36Sopenharmony_ci			MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
8362306a36Sopenharmony_ci		memcpy(low_desc, &desc, sizeof(desc));
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci		low_desc->entry_number = -1;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci		/* 32-bit set_thread_area */
8862306a36Sopenharmony_ci		long ret;
8962306a36Sopenharmony_ci		asm volatile ("int $0x80"
9062306a36Sopenharmony_ci			      : "=a" (ret), "+m" (*low_desc)
9162306a36Sopenharmony_ci			      : "a" (243), "b" (low_desc)
9262306a36Sopenharmony_ci#ifdef __x86_64__
9362306a36Sopenharmony_ci			      : "r8", "r9", "r10", "r11"
9462306a36Sopenharmony_ci#endif
9562306a36Sopenharmony_ci			);
9662306a36Sopenharmony_ci		memcpy(&desc, low_desc, sizeof(desc));
9762306a36Sopenharmony_ci		munmap(low_desc, sizeof(desc));
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci		if (ret != 0) {
10062306a36Sopenharmony_ci			printf("[NOTE]\tcould not create a segment -- can't test anything\n");
10162306a36Sopenharmony_ci			exit(0);
10262306a36Sopenharmony_ci		}
10362306a36Sopenharmony_ci		printf("\tusing GDT slot %d\n", desc.entry_number);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci		unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3);
10662306a36Sopenharmony_ci		asm volatile ("mov %0, %" SEG :: "rm" (sel));
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic void tracee_zap_segment(void)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	/*
11362306a36Sopenharmony_ci	 * The tracer will redirect execution here.  This is meant to
11462306a36Sopenharmony_ci	 * work like gdb's 'p func()' feature.  The tricky bit is that
11562306a36Sopenharmony_ci	 * we modify a segment register in order to make sure that ptrace
11662306a36Sopenharmony_ci	 * can correctly restore segment registers.
11762306a36Sopenharmony_ci	 */
11862306a36Sopenharmony_ci	printf("\tTracee: in tracee_zap_segment()\n");
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/*
12162306a36Sopenharmony_ci	 * Write a nonzero selector with base zero to the segment register.
12262306a36Sopenharmony_ci	 * Using a null selector would defeat the test on AMD pre-Zen2
12362306a36Sopenharmony_ci	 * CPUs, as such CPUs don't clear the base when loading a null
12462306a36Sopenharmony_ci	 * selector.
12562306a36Sopenharmony_ci	 */
12662306a36Sopenharmony_ci	unsigned short sel;
12762306a36Sopenharmony_ci	asm volatile ("mov %%ss, %0\n\t"
12862306a36Sopenharmony_ci		      "mov %0, %" SEG
12962306a36Sopenharmony_ci		      : "=rm" (sel));
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	pid_t pid = getpid(), tid = syscall(SYS_gettid);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	printf("\tTracee is going back to sleep\n");
13462306a36Sopenharmony_ci	syscall(SYS_tgkill, pid, tid, SIGSTOP);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/* Should not get here. */
13762306a36Sopenharmony_ci	while (true) {
13862306a36Sopenharmony_ci		printf("[FAIL]\tTracee hit unreachable code\n");
13962306a36Sopenharmony_ci		pause();
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ciint main()
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	printf("\tSetting up a segment\n");
14662306a36Sopenharmony_ci	init_seg();
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	unsigned int val = dereference_seg_base();
14962306a36Sopenharmony_ci	if (val != EXPECTED_VALUE) {
15062306a36Sopenharmony_ci		printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
15162306a36Sopenharmony_ci		return 1;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci	printf("[OK]\tThe segment points to the right place.\n");
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	pid_t chld = fork();
15662306a36Sopenharmony_ci	if (chld < 0)
15762306a36Sopenharmony_ci		err(1, "fork");
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (chld == 0) {
16062306a36Sopenharmony_ci		prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0, 0);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci		if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
16362306a36Sopenharmony_ci			err(1, "PTRACE_TRACEME");
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci		pid_t pid = getpid(), tid = syscall(SYS_gettid);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci		printf("\tTracee will take a nap until signaled\n");
16862306a36Sopenharmony_ci		syscall(SYS_tgkill, pid, tid, SIGSTOP);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci		printf("\tTracee was resumed.  Will re-check segment.\n");
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		val = dereference_seg_base();
17362306a36Sopenharmony_ci		if (val != EXPECTED_VALUE) {
17462306a36Sopenharmony_ci			printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
17562306a36Sopenharmony_ci			exit(1);
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		printf("[OK]\tThe segment points to the right place.\n");
17962306a36Sopenharmony_ci		exit(0);
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	int status;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	/* Wait for SIGSTOP. */
18562306a36Sopenharmony_ci	if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
18662306a36Sopenharmony_ci		err(1, "waitpid");
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	struct user_regs_struct regs;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (ptrace(PTRACE_GETREGS, chld, NULL, &regs) != 0)
19162306a36Sopenharmony_ci		err(1, "PTRACE_GETREGS");
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci#ifdef __x86_64__
19462306a36Sopenharmony_ci	printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs.gs, (unsigned long)regs.gs_base);
19562306a36Sopenharmony_ci#else
19662306a36Sopenharmony_ci	printf("\tChild FS=0x%lx\n", (unsigned long)regs.xfs);
19762306a36Sopenharmony_ci#endif
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	struct user_regs_struct regs2 = regs;
20062306a36Sopenharmony_ci#ifdef __x86_64__
20162306a36Sopenharmony_ci	regs2.rip = (unsigned long)tracee_zap_segment;
20262306a36Sopenharmony_ci	regs2.rsp -= 128;	/* Don't clobber the redzone. */
20362306a36Sopenharmony_ci#else
20462306a36Sopenharmony_ci	regs2.eip = (unsigned long)tracee_zap_segment;
20562306a36Sopenharmony_ci#endif
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	printf("\tTracer: redirecting tracee to tracee_zap_segment()\n");
20862306a36Sopenharmony_ci	if (ptrace(PTRACE_SETREGS, chld, NULL, &regs2) != 0)
20962306a36Sopenharmony_ci		err(1, "PTRACE_GETREGS");
21062306a36Sopenharmony_ci	if (ptrace(PTRACE_CONT, chld, NULL, NULL) != 0)
21162306a36Sopenharmony_ci		err(1, "PTRACE_GETREGS");
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	/* Wait for SIGSTOP. */
21462306a36Sopenharmony_ci	if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
21562306a36Sopenharmony_ci		err(1, "waitpid");
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	printf("\tTracer: restoring tracee state\n");
21862306a36Sopenharmony_ci	if (ptrace(PTRACE_SETREGS, chld, NULL, &regs) != 0)
21962306a36Sopenharmony_ci		err(1, "PTRACE_GETREGS");
22062306a36Sopenharmony_ci	if (ptrace(PTRACE_DETACH, chld, NULL, NULL) != 0)
22162306a36Sopenharmony_ci		err(1, "PTRACE_GETREGS");
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	/* Wait for SIGSTOP. */
22462306a36Sopenharmony_ci	if (waitpid(chld, &status, 0) != chld)
22562306a36Sopenharmony_ci		err(1, "waitpid");
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	if (WIFSIGNALED(status)) {
22862306a36Sopenharmony_ci		printf("[FAIL]\tTracee crashed\n");
22962306a36Sopenharmony_ci		return 1;
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	if (!WIFEXITED(status)) {
23362306a36Sopenharmony_ci		printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status);
23462306a36Sopenharmony_ci		return 1;
23562306a36Sopenharmony_ci	}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	int exitcode = WEXITSTATUS(status);
23862306a36Sopenharmony_ci	if (exitcode != 0) {
23962306a36Sopenharmony_ci		printf("[FAIL]\tTracee reported failure\n");
24062306a36Sopenharmony_ci		return 1;
24162306a36Sopenharmony_ci	}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	printf("[OK]\tAll is well.\n");
24462306a36Sopenharmony_ci	return 0;
24562306a36Sopenharmony_ci}
246