162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * fsgsbase.c, an fsgsbase test
462306a36Sopenharmony_ci * Copyright (c) 2014-2016 Andy Lutomirski
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#define _GNU_SOURCE
862306a36Sopenharmony_ci#include <stdio.h>
962306a36Sopenharmony_ci#include <stdlib.h>
1062306a36Sopenharmony_ci#include <stdbool.h>
1162306a36Sopenharmony_ci#include <string.h>
1262306a36Sopenharmony_ci#include <sys/syscall.h>
1362306a36Sopenharmony_ci#include <unistd.h>
1462306a36Sopenharmony_ci#include <err.h>
1562306a36Sopenharmony_ci#include <sys/user.h>
1662306a36Sopenharmony_ci#include <asm/prctl.h>
1762306a36Sopenharmony_ci#include <sys/prctl.h>
1862306a36Sopenharmony_ci#include <signal.h>
1962306a36Sopenharmony_ci#include <limits.h>
2062306a36Sopenharmony_ci#include <sys/ucontext.h>
2162306a36Sopenharmony_ci#include <sched.h>
2262306a36Sopenharmony_ci#include <linux/futex.h>
2362306a36Sopenharmony_ci#include <pthread.h>
2462306a36Sopenharmony_ci#include <asm/ldt.h>
2562306a36Sopenharmony_ci#include <sys/mman.h>
2662306a36Sopenharmony_ci#include <stddef.h>
2762306a36Sopenharmony_ci#include <sys/ptrace.h>
2862306a36Sopenharmony_ci#include <sys/wait.h>
2962306a36Sopenharmony_ci#include <setjmp.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#ifndef __x86_64__
3262306a36Sopenharmony_ci# error This test is 64-bit only
3362306a36Sopenharmony_ci#endif
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic volatile sig_atomic_t want_segv;
3662306a36Sopenharmony_cistatic volatile unsigned long segv_addr;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic unsigned short *shared_scratch;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic int nerrs;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
4362306a36Sopenharmony_ci		       int flags)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct sigaction sa;
4662306a36Sopenharmony_ci	memset(&sa, 0, sizeof(sa));
4762306a36Sopenharmony_ci	sa.sa_sigaction = handler;
4862306a36Sopenharmony_ci	sa.sa_flags = SA_SIGINFO | flags;
4962306a36Sopenharmony_ci	sigemptyset(&sa.sa_mask);
5062306a36Sopenharmony_ci	if (sigaction(sig, &sa, 0))
5162306a36Sopenharmony_ci		err(1, "sigaction");
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic void clearhandler(int sig)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	struct sigaction sa;
5762306a36Sopenharmony_ci	memset(&sa, 0, sizeof(sa));
5862306a36Sopenharmony_ci	sa.sa_handler = SIG_DFL;
5962306a36Sopenharmony_ci	sigemptyset(&sa.sa_mask);
6062306a36Sopenharmony_ci	if (sigaction(sig, &sa, 0))
6162306a36Sopenharmony_ci		err(1, "sigaction");
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic void sigsegv(int sig, siginfo_t *si, void *ctx_void)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	ucontext_t *ctx = (ucontext_t*)ctx_void;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (!want_segv) {
6962306a36Sopenharmony_ci		clearhandler(SIGSEGV);
7062306a36Sopenharmony_ci		return;  /* Crash cleanly. */
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	want_segv = false;
7462306a36Sopenharmony_ci	segv_addr = (unsigned long)si->si_addr;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	ctx->uc_mcontext.gregs[REG_RIP] += 4;	/* Skip the faulting mov */
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic jmp_buf jmpbuf;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic void sigill(int sig, siginfo_t *si, void *ctx_void)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	siglongjmp(jmpbuf, 1);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic bool have_fsgsbase;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic inline unsigned long rdgsbase(void)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	unsigned long gsbase;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	asm volatile("rdgsbase %0" : "=r" (gsbase) :: "memory");
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	return gsbase;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic inline unsigned long rdfsbase(void)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	unsigned long fsbase;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	asm volatile("rdfsbase %0" : "=r" (fsbase) :: "memory");
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return fsbase;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic inline void wrgsbase(unsigned long gsbase)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	asm volatile("wrgsbase %0" :: "r" (gsbase) : "memory");
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic inline void wrfsbase(unsigned long fsbase)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	asm volatile("wrfsbase %0" :: "r" (fsbase) : "memory");
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cienum which_base { FS, GS };
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic unsigned long read_base(enum which_base which)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	unsigned long offset;
12262306a36Sopenharmony_ci	/*
12362306a36Sopenharmony_ci	 * Unless we have FSGSBASE, there's no direct way to do this from
12462306a36Sopenharmony_ci	 * user mode.  We can get at it indirectly using signals, though.
12562306a36Sopenharmony_ci	 */
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	want_segv = true;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	offset = 0;
13062306a36Sopenharmony_ci	if (which == FS) {
13162306a36Sopenharmony_ci		/* Use a constant-length instruction here. */
13262306a36Sopenharmony_ci		asm volatile ("mov %%fs:(%%rcx), %%rax" : : "c" (offset) : "rax");
13362306a36Sopenharmony_ci	} else {
13462306a36Sopenharmony_ci		asm volatile ("mov %%gs:(%%rcx), %%rax" : : "c" (offset) : "rax");
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci	if (!want_segv)
13762306a36Sopenharmony_ci		return segv_addr + offset;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/*
14062306a36Sopenharmony_ci	 * If that didn't segfault, try the other end of the address space.
14162306a36Sopenharmony_ci	 * Unless we get really unlucky and run into the vsyscall page, this
14262306a36Sopenharmony_ci	 * is guaranteed to segfault.
14362306a36Sopenharmony_ci	 */
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	offset = (ULONG_MAX >> 1) + 1;
14662306a36Sopenharmony_ci	if (which == FS) {
14762306a36Sopenharmony_ci		asm volatile ("mov %%fs:(%%rcx), %%rax"
14862306a36Sopenharmony_ci			      : : "c" (offset) : "rax");
14962306a36Sopenharmony_ci	} else {
15062306a36Sopenharmony_ci		asm volatile ("mov %%gs:(%%rcx), %%rax"
15162306a36Sopenharmony_ci			      : : "c" (offset) : "rax");
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci	if (!want_segv)
15462306a36Sopenharmony_ci		return segv_addr + offset;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	abort();
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic void check_gs_value(unsigned long value)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	unsigned long base;
16262306a36Sopenharmony_ci	unsigned short sel;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	printf("[RUN]\tARCH_SET_GS to 0x%lx\n", value);
16562306a36Sopenharmony_ci	if (syscall(SYS_arch_prctl, ARCH_SET_GS, value) != 0)
16662306a36Sopenharmony_ci		err(1, "ARCH_SET_GS");
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	asm volatile ("mov %%gs, %0" : "=rm" (sel));
16962306a36Sopenharmony_ci	base = read_base(GS);
17062306a36Sopenharmony_ci	if (base == value) {
17162306a36Sopenharmony_ci		printf("[OK]\tGSBASE was set as expected (selector 0x%hx)\n",
17262306a36Sopenharmony_ci		       sel);
17362306a36Sopenharmony_ci	} else {
17462306a36Sopenharmony_ci		nerrs++;
17562306a36Sopenharmony_ci		printf("[FAIL]\tGSBASE was not as expected: got 0x%lx (selector 0x%hx)\n",
17662306a36Sopenharmony_ci		       base, sel);
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (syscall(SYS_arch_prctl, ARCH_GET_GS, &base) != 0)
18062306a36Sopenharmony_ci		err(1, "ARCH_GET_GS");
18162306a36Sopenharmony_ci	if (base == value) {
18262306a36Sopenharmony_ci		printf("[OK]\tARCH_GET_GS worked as expected (selector 0x%hx)\n",
18362306a36Sopenharmony_ci		       sel);
18462306a36Sopenharmony_ci	} else {
18562306a36Sopenharmony_ci		nerrs++;
18662306a36Sopenharmony_ci		printf("[FAIL]\tARCH_GET_GS was not as expected: got 0x%lx (selector 0x%hx)\n",
18762306a36Sopenharmony_ci		       base, sel);
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic void mov_0_gs(unsigned long initial_base, bool schedule)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	unsigned long base, arch_base;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	printf("[RUN]\tARCH_SET_GS to 0x%lx then mov 0 to %%gs%s\n", initial_base, schedule ? " and schedule " : "");
19662306a36Sopenharmony_ci	if (syscall(SYS_arch_prctl, ARCH_SET_GS, initial_base) != 0)
19762306a36Sopenharmony_ci		err(1, "ARCH_SET_GS");
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	if (schedule)
20062306a36Sopenharmony_ci		usleep(10);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	asm volatile ("mov %0, %%gs" : : "rm" (0));
20362306a36Sopenharmony_ci	base = read_base(GS);
20462306a36Sopenharmony_ci	if (syscall(SYS_arch_prctl, ARCH_GET_GS, &arch_base) != 0)
20562306a36Sopenharmony_ci		err(1, "ARCH_GET_GS");
20662306a36Sopenharmony_ci	if (base == arch_base) {
20762306a36Sopenharmony_ci		printf("[OK]\tGSBASE is 0x%lx\n", base);
20862306a36Sopenharmony_ci	} else {
20962306a36Sopenharmony_ci		nerrs++;
21062306a36Sopenharmony_ci		printf("[FAIL]\tGSBASE changed to 0x%lx but kernel reports 0x%lx\n", base, arch_base);
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic volatile unsigned long remote_base;
21562306a36Sopenharmony_cistatic volatile bool remote_hard_zero;
21662306a36Sopenharmony_cistatic volatile unsigned int ftx;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci/*
21962306a36Sopenharmony_ci * ARCH_SET_FS/GS(0) may or may not program a selector of zero.  HARD_ZERO
22062306a36Sopenharmony_ci * means to force the selector to zero to improve test coverage.
22162306a36Sopenharmony_ci */
22262306a36Sopenharmony_ci#define HARD_ZERO 0xa1fa5f343cb85fa4
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic void do_remote_base()
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	unsigned long to_set = remote_base;
22762306a36Sopenharmony_ci	bool hard_zero = false;
22862306a36Sopenharmony_ci	if (to_set == HARD_ZERO) {
22962306a36Sopenharmony_ci		to_set = 0;
23062306a36Sopenharmony_ci		hard_zero = true;
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	if (syscall(SYS_arch_prctl, ARCH_SET_GS, to_set) != 0)
23462306a36Sopenharmony_ci		err(1, "ARCH_SET_GS");
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	if (hard_zero)
23762306a36Sopenharmony_ci		asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	unsigned short sel;
24062306a36Sopenharmony_ci	asm volatile ("mov %%gs, %0" : "=rm" (sel));
24162306a36Sopenharmony_ci	printf("\tother thread: ARCH_SET_GS(0x%lx)%s -- sel is 0x%hx\n",
24262306a36Sopenharmony_ci	       to_set, hard_zero ? " and clear gs" : "", sel);
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic __thread int set_thread_area_entry_number = -1;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_cistatic unsigned short load_gs(void)
24862306a36Sopenharmony_ci{
24962306a36Sopenharmony_ci	/*
25062306a36Sopenharmony_ci	 * Sets GS != 0 and GSBASE != 0 but arranges for the kernel to think
25162306a36Sopenharmony_ci	 * that GSBASE == 0 (i.e. thread.gsbase == 0).
25262306a36Sopenharmony_ci	 */
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	/* Step 1: tell the kernel that we have GSBASE == 0. */
25562306a36Sopenharmony_ci	if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
25662306a36Sopenharmony_ci		err(1, "ARCH_SET_GS");
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	/* Step 2: change GSBASE without telling the kernel. */
25962306a36Sopenharmony_ci	struct user_desc desc = {
26062306a36Sopenharmony_ci		.entry_number    = 0,
26162306a36Sopenharmony_ci		.base_addr       = 0xBAADF00D,
26262306a36Sopenharmony_ci		.limit           = 0xfffff,
26362306a36Sopenharmony_ci		.seg_32bit       = 1,
26462306a36Sopenharmony_ci		.contents        = 0, /* Data, grow-up */
26562306a36Sopenharmony_ci		.read_exec_only  = 0,
26662306a36Sopenharmony_ci		.limit_in_pages  = 1,
26762306a36Sopenharmony_ci		.seg_not_present = 0,
26862306a36Sopenharmony_ci		.useable         = 0
26962306a36Sopenharmony_ci	};
27062306a36Sopenharmony_ci	if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
27162306a36Sopenharmony_ci		printf("\tusing LDT slot 0\n");
27262306a36Sopenharmony_ci		asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0x7));
27362306a36Sopenharmony_ci		return 0x7;
27462306a36Sopenharmony_ci	} else {
27562306a36Sopenharmony_ci		/* No modify_ldt for us (configured out, perhaps) */
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci		struct user_desc *low_desc = mmap(
27862306a36Sopenharmony_ci			NULL, sizeof(desc),
27962306a36Sopenharmony_ci			PROT_READ | PROT_WRITE,
28062306a36Sopenharmony_ci			MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
28162306a36Sopenharmony_ci		memcpy(low_desc, &desc, sizeof(desc));
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci		low_desc->entry_number = set_thread_area_entry_number;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci		/* 32-bit set_thread_area */
28662306a36Sopenharmony_ci		long ret;
28762306a36Sopenharmony_ci		asm volatile ("int $0x80"
28862306a36Sopenharmony_ci			      : "=a" (ret), "+m" (*low_desc)
28962306a36Sopenharmony_ci			      : "a" (243), "b" (low_desc)
29062306a36Sopenharmony_ci			      : "r8", "r9", "r10", "r11");
29162306a36Sopenharmony_ci		memcpy(&desc, low_desc, sizeof(desc));
29262306a36Sopenharmony_ci		munmap(low_desc, sizeof(desc));
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci		if (ret != 0) {
29562306a36Sopenharmony_ci			printf("[NOTE]\tcould not create a segment -- test won't do anything\n");
29662306a36Sopenharmony_ci			return 0;
29762306a36Sopenharmony_ci		}
29862306a36Sopenharmony_ci		printf("\tusing GDT slot %d\n", desc.entry_number);
29962306a36Sopenharmony_ci		set_thread_area_entry_number = desc.entry_number;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci		unsigned short gs = (unsigned short)((desc.entry_number << 3) | 0x3);
30262306a36Sopenharmony_ci		asm volatile ("mov %0, %%gs" : : "rm" (gs));
30362306a36Sopenharmony_ci		return gs;
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_civoid test_wrbase(unsigned short index, unsigned long base)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	unsigned short newindex;
31062306a36Sopenharmony_ci	unsigned long newbase;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	printf("[RUN]\tGS = 0x%hx, GSBASE = 0x%lx\n", index, base);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	asm volatile ("mov %0, %%gs" : : "rm" (index));
31562306a36Sopenharmony_ci	wrgsbase(base);
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	remote_base = 0;
31862306a36Sopenharmony_ci	ftx = 1;
31962306a36Sopenharmony_ci	syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
32062306a36Sopenharmony_ci	while (ftx != 0)
32162306a36Sopenharmony_ci		syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	asm volatile ("mov %%gs, %0" : "=rm" (newindex));
32462306a36Sopenharmony_ci	newbase = rdgsbase();
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	if (newindex == index && newbase == base) {
32762306a36Sopenharmony_ci		printf("[OK]\tIndex and base were preserved\n");
32862306a36Sopenharmony_ci	} else {
32962306a36Sopenharmony_ci		printf("[FAIL]\tAfter switch, GS = 0x%hx and GSBASE = 0x%lx\n",
33062306a36Sopenharmony_ci		       newindex, newbase);
33162306a36Sopenharmony_ci		nerrs++;
33262306a36Sopenharmony_ci	}
33362306a36Sopenharmony_ci}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_cistatic void *threadproc(void *ctx)
33662306a36Sopenharmony_ci{
33762306a36Sopenharmony_ci	while (1) {
33862306a36Sopenharmony_ci		while (ftx == 0)
33962306a36Sopenharmony_ci			syscall(SYS_futex, &ftx, FUTEX_WAIT, 0, NULL, NULL, 0);
34062306a36Sopenharmony_ci		if (ftx == 3)
34162306a36Sopenharmony_ci			return NULL;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci		if (ftx == 1) {
34462306a36Sopenharmony_ci			do_remote_base();
34562306a36Sopenharmony_ci		} else if (ftx == 2) {
34662306a36Sopenharmony_ci			/*
34762306a36Sopenharmony_ci			 * On AMD chips, this causes GSBASE != 0, GS == 0, and
34862306a36Sopenharmony_ci			 * thread.gsbase == 0.
34962306a36Sopenharmony_ci			 */
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci			load_gs();
35262306a36Sopenharmony_ci			asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
35362306a36Sopenharmony_ci		} else {
35462306a36Sopenharmony_ci			errx(1, "helper thread got bad command");
35562306a36Sopenharmony_ci		}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci		ftx = 0;
35862306a36Sopenharmony_ci		syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_cistatic void set_gs_and_switch_to(unsigned long local,
36362306a36Sopenharmony_ci				 unsigned short force_sel,
36462306a36Sopenharmony_ci				 unsigned long remote)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	unsigned long base;
36762306a36Sopenharmony_ci	unsigned short sel_pre_sched, sel_post_sched;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	bool hard_zero = false;
37062306a36Sopenharmony_ci	if (local == HARD_ZERO) {
37162306a36Sopenharmony_ci		hard_zero = true;
37262306a36Sopenharmony_ci		local = 0;
37362306a36Sopenharmony_ci	}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	printf("[RUN]\tARCH_SET_GS(0x%lx)%s, then schedule to 0x%lx\n",
37662306a36Sopenharmony_ci	       local, hard_zero ? " and clear gs" : "", remote);
37762306a36Sopenharmony_ci	if (force_sel)
37862306a36Sopenharmony_ci		printf("\tBefore schedule, set selector to 0x%hx\n", force_sel);
37962306a36Sopenharmony_ci	if (syscall(SYS_arch_prctl, ARCH_SET_GS, local) != 0)
38062306a36Sopenharmony_ci		err(1, "ARCH_SET_GS");
38162306a36Sopenharmony_ci	if (hard_zero)
38262306a36Sopenharmony_ci		asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	if (read_base(GS) != local) {
38562306a36Sopenharmony_ci		nerrs++;
38662306a36Sopenharmony_ci		printf("[FAIL]\tGSBASE wasn't set as expected\n");
38762306a36Sopenharmony_ci	}
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	if (force_sel) {
39062306a36Sopenharmony_ci		asm volatile ("mov %0, %%gs" : : "rm" (force_sel));
39162306a36Sopenharmony_ci		sel_pre_sched = force_sel;
39262306a36Sopenharmony_ci		local = read_base(GS);
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci		/*
39562306a36Sopenharmony_ci		 * Signal delivery is quite likely to change a selector
39662306a36Sopenharmony_ci		 * of 1, 2, or 3 back to 0 due to IRET being defective.
39762306a36Sopenharmony_ci		 */
39862306a36Sopenharmony_ci		asm volatile ("mov %0, %%gs" : : "rm" (force_sel));
39962306a36Sopenharmony_ci	} else {
40062306a36Sopenharmony_ci		asm volatile ("mov %%gs, %0" : "=rm" (sel_pre_sched));
40162306a36Sopenharmony_ci	}
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	remote_base = remote;
40462306a36Sopenharmony_ci	ftx = 1;
40562306a36Sopenharmony_ci	syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
40662306a36Sopenharmony_ci	while (ftx != 0)
40762306a36Sopenharmony_ci		syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	asm volatile ("mov %%gs, %0" : "=rm" (sel_post_sched));
41062306a36Sopenharmony_ci	base = read_base(GS);
41162306a36Sopenharmony_ci	if (base == local && sel_pre_sched == sel_post_sched) {
41262306a36Sopenharmony_ci		printf("[OK]\tGS/BASE remained 0x%hx/0x%lx\n",
41362306a36Sopenharmony_ci		       sel_pre_sched, local);
41462306a36Sopenharmony_ci	} else if (base == local && sel_pre_sched >= 1 && sel_pre_sched <= 3 &&
41562306a36Sopenharmony_ci		   sel_post_sched == 0) {
41662306a36Sopenharmony_ci		/*
41762306a36Sopenharmony_ci		 * IRET is misdesigned and will squash selectors 1, 2, or 3
41862306a36Sopenharmony_ci		 * to zero.  Don't fail the test just because this happened.
41962306a36Sopenharmony_ci		 */
42062306a36Sopenharmony_ci		printf("[OK]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx because IRET is defective\n",
42162306a36Sopenharmony_ci		       sel_pre_sched, local, sel_post_sched, base);
42262306a36Sopenharmony_ci	} else {
42362306a36Sopenharmony_ci		nerrs++;
42462306a36Sopenharmony_ci		printf("[FAIL]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx\n",
42562306a36Sopenharmony_ci		       sel_pre_sched, local, sel_post_sched, base);
42662306a36Sopenharmony_ci	}
42762306a36Sopenharmony_ci}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_cistatic void test_unexpected_base(void)
43062306a36Sopenharmony_ci{
43162306a36Sopenharmony_ci	unsigned long base;
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	printf("[RUN]\tARCH_SET_GS(0), clear gs, then manipulate GSBASE in a different thread\n");
43462306a36Sopenharmony_ci	if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
43562306a36Sopenharmony_ci		err(1, "ARCH_SET_GS");
43662306a36Sopenharmony_ci	asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	ftx = 2;
43962306a36Sopenharmony_ci	syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
44062306a36Sopenharmony_ci	while (ftx != 0)
44162306a36Sopenharmony_ci		syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	base = read_base(GS);
44462306a36Sopenharmony_ci	if (base == 0) {
44562306a36Sopenharmony_ci		printf("[OK]\tGSBASE remained 0\n");
44662306a36Sopenharmony_ci	} else {
44762306a36Sopenharmony_ci		nerrs++;
44862306a36Sopenharmony_ci		printf("[FAIL]\tGSBASE changed to 0x%lx\n", base);
44962306a36Sopenharmony_ci	}
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci#define USER_REGS_OFFSET(r) offsetof(struct user_regs_struct, r)
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_cistatic void test_ptrace_write_gs_read_base(void)
45562306a36Sopenharmony_ci{
45662306a36Sopenharmony_ci	int status;
45762306a36Sopenharmony_ci	pid_t child = fork();
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	if (child < 0)
46062306a36Sopenharmony_ci		err(1, "fork");
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	if (child == 0) {
46362306a36Sopenharmony_ci		printf("[RUN]\tPTRACE_POKE GS, read GSBASE back\n");
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci		printf("[RUN]\tARCH_SET_GS to 1\n");
46662306a36Sopenharmony_ci		if (syscall(SYS_arch_prctl, ARCH_SET_GS, 1) != 0)
46762306a36Sopenharmony_ci			err(1, "ARCH_SET_GS");
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci		if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
47062306a36Sopenharmony_ci			err(1, "PTRACE_TRACEME");
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci		raise(SIGTRAP);
47362306a36Sopenharmony_ci		_exit(0);
47462306a36Sopenharmony_ci	}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	wait(&status);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	if (WSTOPSIG(status) == SIGTRAP) {
47962306a36Sopenharmony_ci		unsigned long base;
48062306a36Sopenharmony_ci		unsigned long gs_offset = USER_REGS_OFFSET(gs);
48162306a36Sopenharmony_ci		unsigned long base_offset = USER_REGS_OFFSET(gs_base);
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci		/* Read the initial base.  It should be 1. */
48462306a36Sopenharmony_ci		base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);
48562306a36Sopenharmony_ci		if (base == 1) {
48662306a36Sopenharmony_ci			printf("[OK]\tGSBASE started at 1\n");
48762306a36Sopenharmony_ci		} else {
48862306a36Sopenharmony_ci			nerrs++;
48962306a36Sopenharmony_ci			printf("[FAIL]\tGSBASE started at 0x%lx\n", base);
49062306a36Sopenharmony_ci		}
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci		printf("[RUN]\tSet GS = 0x7, read GSBASE\n");
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci		/* Poke an LDT selector into GS. */
49562306a36Sopenharmony_ci		if (ptrace(PTRACE_POKEUSER, child, gs_offset, 0x7) != 0)
49662306a36Sopenharmony_ci			err(1, "PTRACE_POKEUSER");
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci		/* And read the base. */
49962306a36Sopenharmony_ci		base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci		if (base == 0 || base == 1) {
50262306a36Sopenharmony_ci			printf("[OK]\tGSBASE reads as 0x%lx with invalid GS\n", base);
50362306a36Sopenharmony_ci		} else {
50462306a36Sopenharmony_ci			nerrs++;
50562306a36Sopenharmony_ci			printf("[FAIL]\tGSBASE=0x%lx (should be 0 or 1)\n", base);
50662306a36Sopenharmony_ci		}
50762306a36Sopenharmony_ci	}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	ptrace(PTRACE_CONT, child, NULL, NULL);
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	wait(&status);
51262306a36Sopenharmony_ci	if (!WIFEXITED(status))
51362306a36Sopenharmony_ci		printf("[WARN]\tChild didn't exit cleanly.\n");
51462306a36Sopenharmony_ci}
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cistatic void test_ptrace_write_gsbase(void)
51762306a36Sopenharmony_ci{
51862306a36Sopenharmony_ci	int status;
51962306a36Sopenharmony_ci	pid_t child = fork();
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	if (child < 0)
52262306a36Sopenharmony_ci		err(1, "fork");
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	if (child == 0) {
52562306a36Sopenharmony_ci		printf("[RUN]\tPTRACE_POKE(), write GSBASE from ptracer\n");
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci		*shared_scratch = load_gs();
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci		if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
53062306a36Sopenharmony_ci			err(1, "PTRACE_TRACEME");
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci		raise(SIGTRAP);
53362306a36Sopenharmony_ci		_exit(0);
53462306a36Sopenharmony_ci	}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	wait(&status);
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	if (WSTOPSIG(status) == SIGTRAP) {
53962306a36Sopenharmony_ci		unsigned long gs, base;
54062306a36Sopenharmony_ci		unsigned long gs_offset = USER_REGS_OFFSET(gs);
54162306a36Sopenharmony_ci		unsigned long base_offset = USER_REGS_OFFSET(gs_base);
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci		gs = ptrace(PTRACE_PEEKUSER, child, gs_offset, NULL);
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci		if (gs != *shared_scratch) {
54662306a36Sopenharmony_ci			nerrs++;
54762306a36Sopenharmony_ci			printf("[FAIL]\tGS is not prepared with nonzero\n");
54862306a36Sopenharmony_ci			goto END;
54962306a36Sopenharmony_ci		}
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci		if (ptrace(PTRACE_POKEUSER, child, base_offset, 0xFF) != 0)
55262306a36Sopenharmony_ci			err(1, "PTRACE_POKEUSER");
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci		gs = ptrace(PTRACE_PEEKUSER, child, gs_offset, NULL);
55562306a36Sopenharmony_ci		base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci		/*
55862306a36Sopenharmony_ci		 * In a non-FSGSBASE system, the nonzero selector will load
55962306a36Sopenharmony_ci		 * GSBASE (again). But what is tested here is whether the
56062306a36Sopenharmony_ci		 * selector value is changed or not by the GSBASE write in
56162306a36Sopenharmony_ci		 * a ptracer.
56262306a36Sopenharmony_ci		 */
56362306a36Sopenharmony_ci		if (gs != *shared_scratch) {
56462306a36Sopenharmony_ci			nerrs++;
56562306a36Sopenharmony_ci			printf("[FAIL]\tGS changed to %lx\n", gs);
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci			/*
56862306a36Sopenharmony_ci			 * On older kernels, poking a nonzero value into the
56962306a36Sopenharmony_ci			 * base would zero the selector.  On newer kernels,
57062306a36Sopenharmony_ci			 * this behavior has changed -- poking the base
57162306a36Sopenharmony_ci			 * changes only the base and, if FSGSBASE is not
57262306a36Sopenharmony_ci			 * available, this may have no effect once the tracee
57362306a36Sopenharmony_ci			 * is resumed.
57462306a36Sopenharmony_ci			 */
57562306a36Sopenharmony_ci			if (gs == 0)
57662306a36Sopenharmony_ci				printf("\tNote: this is expected behavior on older kernels.\n");
57762306a36Sopenharmony_ci		} else if (have_fsgsbase && (base != 0xFF)) {
57862306a36Sopenharmony_ci			nerrs++;
57962306a36Sopenharmony_ci			printf("[FAIL]\tGSBASE changed to %lx\n", base);
58062306a36Sopenharmony_ci		} else {
58162306a36Sopenharmony_ci			printf("[OK]\tGS remained 0x%hx", *shared_scratch);
58262306a36Sopenharmony_ci			if (have_fsgsbase)
58362306a36Sopenharmony_ci				printf(" and GSBASE changed to 0xFF");
58462306a36Sopenharmony_ci			printf("\n");
58562306a36Sopenharmony_ci		}
58662306a36Sopenharmony_ci	}
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ciEND:
58962306a36Sopenharmony_ci	ptrace(PTRACE_CONT, child, NULL, NULL);
59062306a36Sopenharmony_ci	wait(&status);
59162306a36Sopenharmony_ci	if (!WIFEXITED(status))
59262306a36Sopenharmony_ci		printf("[WARN]\tChild didn't exit cleanly.\n");
59362306a36Sopenharmony_ci}
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ciint main()
59662306a36Sopenharmony_ci{
59762306a36Sopenharmony_ci	pthread_t thread;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	shared_scratch = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
60062306a36Sopenharmony_ci			      MAP_ANONYMOUS | MAP_SHARED, -1, 0);
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	/* Do these tests before we have an LDT. */
60362306a36Sopenharmony_ci	test_ptrace_write_gs_read_base();
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	/* Probe FSGSBASE */
60662306a36Sopenharmony_ci	sethandler(SIGILL, sigill, 0);
60762306a36Sopenharmony_ci	if (sigsetjmp(jmpbuf, 1) == 0) {
60862306a36Sopenharmony_ci		rdfsbase();
60962306a36Sopenharmony_ci		have_fsgsbase = true;
61062306a36Sopenharmony_ci		printf("\tFSGSBASE instructions are enabled\n");
61162306a36Sopenharmony_ci	} else {
61262306a36Sopenharmony_ci		printf("\tFSGSBASE instructions are disabled\n");
61362306a36Sopenharmony_ci	}
61462306a36Sopenharmony_ci	clearhandler(SIGILL);
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	sethandler(SIGSEGV, sigsegv, 0);
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	check_gs_value(0);
61962306a36Sopenharmony_ci	check_gs_value(1);
62062306a36Sopenharmony_ci	check_gs_value(0x200000000);
62162306a36Sopenharmony_ci	check_gs_value(0);
62262306a36Sopenharmony_ci	check_gs_value(0x200000000);
62362306a36Sopenharmony_ci	check_gs_value(1);
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	for (int sched = 0; sched < 2; sched++) {
62662306a36Sopenharmony_ci		mov_0_gs(0, !!sched);
62762306a36Sopenharmony_ci		mov_0_gs(1, !!sched);
62862306a36Sopenharmony_ci		mov_0_gs(0x200000000, !!sched);
62962306a36Sopenharmony_ci	}
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	/* Set up for multithreading. */
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	cpu_set_t cpuset;
63462306a36Sopenharmony_ci	CPU_ZERO(&cpuset);
63562306a36Sopenharmony_ci	CPU_SET(0, &cpuset);
63662306a36Sopenharmony_ci	if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
63762306a36Sopenharmony_ci		err(1, "sched_setaffinity to CPU 0");	/* should never fail */
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	if (pthread_create(&thread, 0, threadproc, 0) != 0)
64062306a36Sopenharmony_ci		err(1, "pthread_create");
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	static unsigned long bases_with_hard_zero[] = {
64362306a36Sopenharmony_ci		0, HARD_ZERO, 1, 0x200000000,
64462306a36Sopenharmony_ci	};
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	for (int local = 0; local < 4; local++) {
64762306a36Sopenharmony_ci		for (int remote = 0; remote < 4; remote++) {
64862306a36Sopenharmony_ci			for (unsigned short s = 0; s < 5; s++) {
64962306a36Sopenharmony_ci				unsigned short sel = s;
65062306a36Sopenharmony_ci				if (s == 4)
65162306a36Sopenharmony_ci					asm ("mov %%ss, %0" : "=rm" (sel));
65262306a36Sopenharmony_ci				set_gs_and_switch_to(
65362306a36Sopenharmony_ci					bases_with_hard_zero[local],
65462306a36Sopenharmony_ci					sel,
65562306a36Sopenharmony_ci					bases_with_hard_zero[remote]);
65662306a36Sopenharmony_ci			}
65762306a36Sopenharmony_ci		}
65862306a36Sopenharmony_ci	}
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	test_unexpected_base();
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	if (have_fsgsbase) {
66362306a36Sopenharmony_ci		unsigned short ss;
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci		asm volatile ("mov %%ss, %0" : "=rm" (ss));
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci		test_wrbase(0, 0);
66862306a36Sopenharmony_ci		test_wrbase(0, 1);
66962306a36Sopenharmony_ci		test_wrbase(0, 0x200000000);
67062306a36Sopenharmony_ci		test_wrbase(0, 0xffffffffffffffff);
67162306a36Sopenharmony_ci		test_wrbase(ss, 0);
67262306a36Sopenharmony_ci		test_wrbase(ss, 1);
67362306a36Sopenharmony_ci		test_wrbase(ss, 0x200000000);
67462306a36Sopenharmony_ci		test_wrbase(ss, 0xffffffffffffffff);
67562306a36Sopenharmony_ci	}
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci	ftx = 3;  /* Kill the thread. */
67862306a36Sopenharmony_ci	syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci	if (pthread_join(thread, NULL) != 0)
68162306a36Sopenharmony_ci		err(1, "pthread_join");
68262306a36Sopenharmony_ci
68362306a36Sopenharmony_ci	test_ptrace_write_gsbase();
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	return nerrs == 0 ? 0 : 1;
68662306a36Sopenharmony_ci}
687