brintos

brintos / linux-shallow public Read only

0
0
Text · 16.3 KiB · 50cf32d Raw
681 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * fsgsbase.c, an fsgsbase test4 * Copyright (c) 2014-2016 Andy Lutomirski5 */6 7#define _GNU_SOURCE8#include <stdio.h>9#include <stdlib.h>10#include <stdbool.h>11#include <string.h>12#include <sys/syscall.h>13#include <unistd.h>14#include <err.h>15#include <sys/user.h>16#include <asm/prctl.h>17#include <sys/prctl.h>18#include <signal.h>19#include <limits.h>20#include <sys/ucontext.h>21#include <sched.h>22#include <linux/futex.h>23#include <pthread.h>24#include <asm/ldt.h>25#include <sys/mman.h>26#include <stddef.h>27#include <sys/ptrace.h>28#include <sys/wait.h>29#include <setjmp.h>30 31#ifndef __x86_64__32# error This test is 64-bit only33#endif34 35static volatile sig_atomic_t want_segv;36static volatile unsigned long segv_addr;37 38static unsigned short *shared_scratch;39 40static int nerrs;41 42static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),43		       int flags)44{45	struct sigaction sa;46	memset(&sa, 0, sizeof(sa));47	sa.sa_sigaction = handler;48	sa.sa_flags = SA_SIGINFO | flags;49	sigemptyset(&sa.sa_mask);50	if (sigaction(sig, &sa, 0))51		err(1, "sigaction");52}53 54static void clearhandler(int sig)55{56	struct sigaction sa;57	memset(&sa, 0, sizeof(sa));58	sa.sa_handler = SIG_DFL;59	sigemptyset(&sa.sa_mask);60	if (sigaction(sig, &sa, 0))61		err(1, "sigaction");62}63 64static void sigsegv(int sig, siginfo_t *si, void *ctx_void)65{66	ucontext_t *ctx = (ucontext_t*)ctx_void;67 68	if (!want_segv) {69		clearhandler(SIGSEGV);70		return;  /* Crash cleanly. */71	}72 73	want_segv = false;74	segv_addr = (unsigned long)si->si_addr;75 76	ctx->uc_mcontext.gregs[REG_RIP] += 4;	/* Skip the faulting mov */77 78}79 80static jmp_buf jmpbuf;81 82static void sigill(int sig, siginfo_t *si, void *ctx_void)83{84	siglongjmp(jmpbuf, 1);85}86 87static bool have_fsgsbase;88 89static inline unsigned long rdgsbase(void)90{91	unsigned long gsbase;92 93	asm volatile("rdgsbase %0" : "=r" (gsbase) :: "memory");94 95	return gsbase;96}97 98static inline unsigned long rdfsbase(void)99{100	unsigned long fsbase;101 102	asm volatile("rdfsbase %0" : "=r" (fsbase) :: "memory");103 104	return fsbase;105}106 107static inline void wrgsbase(unsigned long gsbase)108{109	asm volatile("wrgsbase %0" :: "r" (gsbase) : "memory");110}111 112enum which_base { FS, GS };113 114static unsigned long read_base(enum which_base which)115{116	unsigned long offset;117	/*118	 * Unless we have FSGSBASE, there's no direct way to do this from119	 * user mode.  We can get at it indirectly using signals, though.120	 */121 122	want_segv = true;123 124	offset = 0;125	if (which == FS) {126		/* Use a constant-length instruction here. */127		asm volatile ("mov %%fs:(%%rcx), %%rax" : : "c" (offset) : "rax");128	} else {129		asm volatile ("mov %%gs:(%%rcx), %%rax" : : "c" (offset) : "rax");130	}131	if (!want_segv)132		return segv_addr + offset;133 134	/*135	 * If that didn't segfault, try the other end of the address space.136	 * Unless we get really unlucky and run into the vsyscall page, this137	 * is guaranteed to segfault.138	 */139 140	offset = (ULONG_MAX >> 1) + 1;141	if (which == FS) {142		asm volatile ("mov %%fs:(%%rcx), %%rax"143			      : : "c" (offset) : "rax");144	} else {145		asm volatile ("mov %%gs:(%%rcx), %%rax"146			      : : "c" (offset) : "rax");147	}148	if (!want_segv)149		return segv_addr + offset;150 151	abort();152}153 154static void check_gs_value(unsigned long value)155{156	unsigned long base;157	unsigned short sel;158 159	printf("[RUN]\tARCH_SET_GS to 0x%lx\n", value);160	if (syscall(SYS_arch_prctl, ARCH_SET_GS, value) != 0)161		err(1, "ARCH_SET_GS");162 163	asm volatile ("mov %%gs, %0" : "=rm" (sel));164	base = read_base(GS);165	if (base == value) {166		printf("[OK]\tGSBASE was set as expected (selector 0x%hx)\n",167		       sel);168	} else {169		nerrs++;170		printf("[FAIL]\tGSBASE was not as expected: got 0x%lx (selector 0x%hx)\n",171		       base, sel);172	}173 174	if (syscall(SYS_arch_prctl, ARCH_GET_GS, &base) != 0)175		err(1, "ARCH_GET_GS");176	if (base == value) {177		printf("[OK]\tARCH_GET_GS worked as expected (selector 0x%hx)\n",178		       sel);179	} else {180		nerrs++;181		printf("[FAIL]\tARCH_GET_GS was not as expected: got 0x%lx (selector 0x%hx)\n",182		       base, sel);183	}184}185 186static void mov_0_gs(unsigned long initial_base, bool schedule)187{188	unsigned long base, arch_base;189 190	printf("[RUN]\tARCH_SET_GS to 0x%lx then mov 0 to %%gs%s\n", initial_base, schedule ? " and schedule " : "");191	if (syscall(SYS_arch_prctl, ARCH_SET_GS, initial_base) != 0)192		err(1, "ARCH_SET_GS");193 194	if (schedule)195		usleep(10);196 197	asm volatile ("mov %0, %%gs" : : "rm" (0));198	base = read_base(GS);199	if (syscall(SYS_arch_prctl, ARCH_GET_GS, &arch_base) != 0)200		err(1, "ARCH_GET_GS");201	if (base == arch_base) {202		printf("[OK]\tGSBASE is 0x%lx\n", base);203	} else {204		nerrs++;205		printf("[FAIL]\tGSBASE changed to 0x%lx but kernel reports 0x%lx\n", base, arch_base);206	}207}208 209static volatile unsigned long remote_base;210static volatile unsigned int ftx;211 212/*213 * ARCH_SET_FS/GS(0) may or may not program a selector of zero.  HARD_ZERO214 * means to force the selector to zero to improve test coverage.215 */216#define HARD_ZERO 0xa1fa5f343cb85fa4217 218static void do_remote_base()219{220	unsigned long to_set = remote_base;221	bool hard_zero = false;222	if (to_set == HARD_ZERO) {223		to_set = 0;224		hard_zero = true;225	}226 227	if (syscall(SYS_arch_prctl, ARCH_SET_GS, to_set) != 0)228		err(1, "ARCH_SET_GS");229 230	if (hard_zero)231		asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));232 233	unsigned short sel;234	asm volatile ("mov %%gs, %0" : "=rm" (sel));235	printf("\tother thread: ARCH_SET_GS(0x%lx)%s -- sel is 0x%hx\n",236	       to_set, hard_zero ? " and clear gs" : "", sel);237}238 239static __thread int set_thread_area_entry_number = -1;240 241static unsigned short load_gs(void)242{243	/*244	 * Sets GS != 0 and GSBASE != 0 but arranges for the kernel to think245	 * that GSBASE == 0 (i.e. thread.gsbase == 0).246	 */247 248	/* Step 1: tell the kernel that we have GSBASE == 0. */249	if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)250		err(1, "ARCH_SET_GS");251 252	/* Step 2: change GSBASE without telling the kernel. */253	struct user_desc desc = {254		.entry_number    = 0,255		.base_addr       = 0xBAADF00D,256		.limit           = 0xfffff,257		.seg_32bit       = 1,258		.contents        = 0, /* Data, grow-up */259		.read_exec_only  = 0,260		.limit_in_pages  = 1,261		.seg_not_present = 0,262		.useable         = 0263	};264	if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {265		printf("\tusing LDT slot 0\n");266		asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0x7));267		return 0x7;268	} else {269		/* No modify_ldt for us (configured out, perhaps) */270 271		struct user_desc *low_desc = mmap(272			NULL, sizeof(desc),273			PROT_READ | PROT_WRITE,274			MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);275		memcpy(low_desc, &desc, sizeof(desc));276 277		low_desc->entry_number = set_thread_area_entry_number;278 279		/* 32-bit set_thread_area */280		long ret;281		asm volatile ("int $0x80"282			      : "=a" (ret), "+m" (*low_desc)283			      : "a" (243), "b" (low_desc)284			      : "r8", "r9", "r10", "r11");285		memcpy(&desc, low_desc, sizeof(desc));286		munmap(low_desc, sizeof(desc));287 288		if (ret != 0) {289			printf("[NOTE]\tcould not create a segment -- test won't do anything\n");290			return 0;291		}292		printf("\tusing GDT slot %d\n", desc.entry_number);293		set_thread_area_entry_number = desc.entry_number;294 295		unsigned short gs = (unsigned short)((desc.entry_number << 3) | 0x3);296		asm volatile ("mov %0, %%gs" : : "rm" (gs));297		return gs;298	}299}300 301void test_wrbase(unsigned short index, unsigned long base)302{303	unsigned short newindex;304	unsigned long newbase;305 306	printf("[RUN]\tGS = 0x%hx, GSBASE = 0x%lx\n", index, base);307 308	asm volatile ("mov %0, %%gs" : : "rm" (index));309	wrgsbase(base);310 311	remote_base = 0;312	ftx = 1;313	syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);314	while (ftx != 0)315		syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);316 317	asm volatile ("mov %%gs, %0" : "=rm" (newindex));318	newbase = rdgsbase();319 320	if (newindex == index && newbase == base) {321		printf("[OK]\tIndex and base were preserved\n");322	} else {323		printf("[FAIL]\tAfter switch, GS = 0x%hx and GSBASE = 0x%lx\n",324		       newindex, newbase);325		nerrs++;326	}327}328 329static void *threadproc(void *ctx)330{331	while (1) {332		while (ftx == 0)333			syscall(SYS_futex, &ftx, FUTEX_WAIT, 0, NULL, NULL, 0);334		if (ftx == 3)335			return NULL;336 337		if (ftx == 1) {338			do_remote_base();339		} else if (ftx == 2) {340			/*341			 * On AMD chips, this causes GSBASE != 0, GS == 0, and342			 * thread.gsbase == 0.343			 */344 345			load_gs();346			asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));347		} else {348			errx(1, "helper thread got bad command");349		}350 351		ftx = 0;352		syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);353	}354}355 356static void set_gs_and_switch_to(unsigned long local,357				 unsigned short force_sel,358				 unsigned long remote)359{360	unsigned long base;361	unsigned short sel_pre_sched, sel_post_sched;362 363	bool hard_zero = false;364	if (local == HARD_ZERO) {365		hard_zero = true;366		local = 0;367	}368 369	printf("[RUN]\tARCH_SET_GS(0x%lx)%s, then schedule to 0x%lx\n",370	       local, hard_zero ? " and clear gs" : "", remote);371	if (force_sel)372		printf("\tBefore schedule, set selector to 0x%hx\n", force_sel);373	if (syscall(SYS_arch_prctl, ARCH_SET_GS, local) != 0)374		err(1, "ARCH_SET_GS");375	if (hard_zero)376		asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));377 378	if (read_base(GS) != local) {379		nerrs++;380		printf("[FAIL]\tGSBASE wasn't set as expected\n");381	}382 383	if (force_sel) {384		asm volatile ("mov %0, %%gs" : : "rm" (force_sel));385		sel_pre_sched = force_sel;386		local = read_base(GS);387 388		/*389		 * Signal delivery is quite likely to change a selector390		 * of 1, 2, or 3 back to 0 due to IRET being defective.391		 */392		asm volatile ("mov %0, %%gs" : : "rm" (force_sel));393	} else {394		asm volatile ("mov %%gs, %0" : "=rm" (sel_pre_sched));395	}396 397	remote_base = remote;398	ftx = 1;399	syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);400	while (ftx != 0)401		syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);402 403	asm volatile ("mov %%gs, %0" : "=rm" (sel_post_sched));404	base = read_base(GS);405	if (base == local && sel_pre_sched == sel_post_sched) {406		printf("[OK]\tGS/BASE remained 0x%hx/0x%lx\n",407		       sel_pre_sched, local);408	} else if (base == local && sel_pre_sched >= 1 && sel_pre_sched <= 3 &&409		   sel_post_sched == 0) {410		/*411		 * IRET is misdesigned and will squash selectors 1, 2, or 3412		 * to zero.  Don't fail the test just because this happened.413		 */414		printf("[OK]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx because IRET is defective\n",415		       sel_pre_sched, local, sel_post_sched, base);416	} else {417		nerrs++;418		printf("[FAIL]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx\n",419		       sel_pre_sched, local, sel_post_sched, base);420	}421}422 423static void test_unexpected_base(void)424{425	unsigned long base;426 427	printf("[RUN]\tARCH_SET_GS(0), clear gs, then manipulate GSBASE in a different thread\n");428	if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)429		err(1, "ARCH_SET_GS");430	asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));431 432	ftx = 2;433	syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);434	while (ftx != 0)435		syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);436 437	base = read_base(GS);438	if (base == 0) {439		printf("[OK]\tGSBASE remained 0\n");440	} else {441		nerrs++;442		printf("[FAIL]\tGSBASE changed to 0x%lx\n", base);443	}444}445 446#define USER_REGS_OFFSET(r) offsetof(struct user_regs_struct, r)447 448static void test_ptrace_write_gs_read_base(void)449{450	int status;451	pid_t child = fork();452 453	if (child < 0)454		err(1, "fork");455 456	if (child == 0) {457		printf("[RUN]\tPTRACE_POKE GS, read GSBASE back\n");458 459		printf("[RUN]\tARCH_SET_GS to 1\n");460		if (syscall(SYS_arch_prctl, ARCH_SET_GS, 1) != 0)461			err(1, "ARCH_SET_GS");462 463		if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)464			err(1, "PTRACE_TRACEME");465 466		raise(SIGTRAP);467		_exit(0);468	}469 470	wait(&status);471 472	if (WSTOPSIG(status) == SIGTRAP) {473		unsigned long base;474		unsigned long gs_offset = USER_REGS_OFFSET(gs);475		unsigned long base_offset = USER_REGS_OFFSET(gs_base);476 477		/* Read the initial base.  It should be 1. */478		base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);479		if (base == 1) {480			printf("[OK]\tGSBASE started at 1\n");481		} else {482			nerrs++;483			printf("[FAIL]\tGSBASE started at 0x%lx\n", base);484		}485 486		printf("[RUN]\tSet GS = 0x7, read GSBASE\n");487 488		/* Poke an LDT selector into GS. */489		if (ptrace(PTRACE_POKEUSER, child, gs_offset, 0x7) != 0)490			err(1, "PTRACE_POKEUSER");491 492		/* And read the base. */493		base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);494 495		if (base == 0 || base == 1) {496			printf("[OK]\tGSBASE reads as 0x%lx with invalid GS\n", base);497		} else {498			nerrs++;499			printf("[FAIL]\tGSBASE=0x%lx (should be 0 or 1)\n", base);500		}501	}502 503	ptrace(PTRACE_CONT, child, NULL, NULL);504 505	wait(&status);506	if (!WIFEXITED(status))507		printf("[WARN]\tChild didn't exit cleanly.\n");508}509 510static void test_ptrace_write_gsbase(void)511{512	int status;513	pid_t child = fork();514 515	if (child < 0)516		err(1, "fork");517 518	if (child == 0) {519		printf("[RUN]\tPTRACE_POKE(), write GSBASE from ptracer\n");520 521		*shared_scratch = load_gs();522 523		if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)524			err(1, "PTRACE_TRACEME");525 526		raise(SIGTRAP);527		_exit(0);528	}529 530	wait(&status);531 532	if (WSTOPSIG(status) == SIGTRAP) {533		unsigned long gs, base;534		unsigned long gs_offset = USER_REGS_OFFSET(gs);535		unsigned long base_offset = USER_REGS_OFFSET(gs_base);536 537		gs = ptrace(PTRACE_PEEKUSER, child, gs_offset, NULL);538 539		if (gs != *shared_scratch) {540			nerrs++;541			printf("[FAIL]\tGS is not prepared with nonzero\n");542			goto END;543		}544 545		if (ptrace(PTRACE_POKEUSER, child, base_offset, 0xFF) != 0)546			err(1, "PTRACE_POKEUSER");547 548		gs = ptrace(PTRACE_PEEKUSER, child, gs_offset, NULL);549		base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);550 551		/*552		 * In a non-FSGSBASE system, the nonzero selector will load553		 * GSBASE (again). But what is tested here is whether the554		 * selector value is changed or not by the GSBASE write in555		 * a ptracer.556		 */557		if (gs != *shared_scratch) {558			nerrs++;559			printf("[FAIL]\tGS changed to %lx\n", gs);560 561			/*562			 * On older kernels, poking a nonzero value into the563			 * base would zero the selector.  On newer kernels,564			 * this behavior has changed -- poking the base565			 * changes only the base and, if FSGSBASE is not566			 * available, this may have no effect once the tracee567			 * is resumed.568			 */569			if (gs == 0)570				printf("\tNote: this is expected behavior on older kernels.\n");571		} else if (have_fsgsbase && (base != 0xFF)) {572			nerrs++;573			printf("[FAIL]\tGSBASE changed to %lx\n", base);574		} else {575			printf("[OK]\tGS remained 0x%hx", *shared_scratch);576			if (have_fsgsbase)577				printf(" and GSBASE changed to 0xFF");578			printf("\n");579		}580	}581 582END:583	ptrace(PTRACE_CONT, child, NULL, NULL);584	wait(&status);585	if (!WIFEXITED(status))586		printf("[WARN]\tChild didn't exit cleanly.\n");587}588 589int main()590{591	pthread_t thread;592 593	shared_scratch = mmap(NULL, 4096, PROT_READ | PROT_WRITE,594			      MAP_ANONYMOUS | MAP_SHARED, -1, 0);595 596	/* Do these tests before we have an LDT. */597	test_ptrace_write_gs_read_base();598 599	/* Probe FSGSBASE */600	sethandler(SIGILL, sigill, 0);601	if (sigsetjmp(jmpbuf, 1) == 0) {602		rdfsbase();603		have_fsgsbase = true;604		printf("\tFSGSBASE instructions are enabled\n");605	} else {606		printf("\tFSGSBASE instructions are disabled\n");607	}608	clearhandler(SIGILL);609 610	sethandler(SIGSEGV, sigsegv, 0);611 612	check_gs_value(0);613	check_gs_value(1);614	check_gs_value(0x200000000);615	check_gs_value(0);616	check_gs_value(0x200000000);617	check_gs_value(1);618 619	for (int sched = 0; sched < 2; sched++) {620		mov_0_gs(0, !!sched);621		mov_0_gs(1, !!sched);622		mov_0_gs(0x200000000, !!sched);623	}624 625	/* Set up for multithreading. */626 627	cpu_set_t cpuset;628	CPU_ZERO(&cpuset);629	CPU_SET(0, &cpuset);630	if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)631		err(1, "sched_setaffinity to CPU 0");	/* should never fail */632 633	if (pthread_create(&thread, 0, threadproc, 0) != 0)634		err(1, "pthread_create");635 636	static unsigned long bases_with_hard_zero[] = {637		0, HARD_ZERO, 1, 0x200000000,638	};639 640	for (int local = 0; local < 4; local++) {641		for (int remote = 0; remote < 4; remote++) {642			for (unsigned short s = 0; s < 5; s++) {643				unsigned short sel = s;644				if (s == 4)645					asm ("mov %%ss, %0" : "=rm" (sel));646				set_gs_and_switch_to(647					bases_with_hard_zero[local],648					sel,649					bases_with_hard_zero[remote]);650			}651		}652	}653 654	test_unexpected_base();655 656	if (have_fsgsbase) {657		unsigned short ss;658 659		asm volatile ("mov %%ss, %0" : "=rm" (ss));660 661		test_wrbase(0, 0);662		test_wrbase(0, 1);663		test_wrbase(0, 0x200000000);664		test_wrbase(0, 0xffffffffffffffff);665		test_wrbase(ss, 0);666		test_wrbase(ss, 1);667		test_wrbase(ss, 0x200000000);668		test_wrbase(ss, 0xffffffffffffffff);669	}670 671	ftx = 3;  /* Kill the thread. */672	syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);673 674	if (pthread_join(thread, NULL) != 0)675		err(1, "pthread_join");676 677	test_ptrace_write_gsbase();678 679	return nerrs == 0 ? 0 : 1;680}681