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