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