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