xref: /linux/tools/testing/selftests/x86/fsgsbase.c (revision 46e6acfe3501fa938af9c5bd730f0020235b08a2)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * fsgsbase.c, an fsgsbase test
4  * Copyright (c) 2014-2016 Andy Lutomirski
5  */
6 
7 #define _GNU_SOURCE
8 #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 only
33 #endif
34 
35 static volatile sig_atomic_t want_segv;
36 static volatile unsigned long segv_addr;
37 
38 static unsigned short *shared_scratch;
39 
40 static int nerrs;
41 
42 static 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 
54 static 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 
64 static 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 
80 static jmp_buf jmpbuf;
81 
82 static void sigill(int sig, siginfo_t *si, void *ctx_void)
83 {
84 	siglongjmp(jmpbuf, 1);
85 }
86 
87 static bool have_fsgsbase;
88 
89 static 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 
98 static 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 
107 static inline void wrgsbase(unsigned long gsbase)
108 {
109 	asm volatile("wrgsbase %0" :: "r" (gsbase) : "memory");
110 }
111 
112 enum which_base { FS, GS };
113 
114 static 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 from
119 	 * 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, this
137 	 * 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 
154 static 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 
186 static 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 
209 static volatile unsigned long remote_base;
210 static volatile unsigned int ftx;
211 
212 /*
213  * ARCH_SET_FS/GS(0) may or may not program a selector of zero.  HARD_ZERO
214  * means to force the selector to zero to improve test coverage.
215  */
216 #define HARD_ZERO 0xa1fa5f343cb85fa4
217 
218 static 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 
239 static __thread int set_thread_area_entry_number = -1;
240 
241 static unsigned short load_gs(void)
242 {
243 	/*
244 	 * Sets GS != 0 and GSBASE != 0 but arranges for the kernel to think
245 	 * 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         = 0
263 	};
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 
301 void 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 
329 static 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, and
342 			 * 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 
356 static 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 selector
390 		 * 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 3
412 		 * 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 
423 static 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 
448 static 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 
510 static 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 load
553 		 * GSBASE (again). But what is tested here is whether the
554 		 * selector value is changed or not by the GSBASE write in
555 		 * 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 the
563 			 * base would zero the selector.  On newer kernels,
564 			 * this behavior has changed -- poking the base
565 			 * changes only the base and, if FSGSBASE is not
566 			 * available, this may have no effect once the tracee
567 			 * 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 
582 END:
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 
589 int 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