linux/tools/testing/selftests/x86/fsgsbase.c
<<
>>
Prefs
   1/*
   2 * fsgsbase.c, an fsgsbase test
   3 * Copyright (c) 2014-2016 Andy Lutomirski
   4 * GPL v2
   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
  27#ifndef __x86_64__
  28# error This test is 64-bit only
  29#endif
  30
  31static volatile sig_atomic_t want_segv;
  32static volatile unsigned long segv_addr;
  33
  34static int nerrs;
  35
  36static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
  37                       int flags)
  38{
  39        struct sigaction sa;
  40        memset(&sa, 0, sizeof(sa));
  41        sa.sa_sigaction = handler;
  42        sa.sa_flags = SA_SIGINFO | flags;
  43        sigemptyset(&sa.sa_mask);
  44        if (sigaction(sig, &sa, 0))
  45                err(1, "sigaction");
  46}
  47
  48static void clearhandler(int sig)
  49{
  50        struct sigaction sa;
  51        memset(&sa, 0, sizeof(sa));
  52        sa.sa_handler = SIG_DFL;
  53        sigemptyset(&sa.sa_mask);
  54        if (sigaction(sig, &sa, 0))
  55                err(1, "sigaction");
  56}
  57
  58static void sigsegv(int sig, siginfo_t *si, void *ctx_void)
  59{
  60        ucontext_t *ctx = (ucontext_t*)ctx_void;
  61
  62        if (!want_segv) {
  63                clearhandler(SIGSEGV);
  64                return;  /* Crash cleanly. */
  65        }
  66
  67        want_segv = false;
  68        segv_addr = (unsigned long)si->si_addr;
  69
  70        ctx->uc_mcontext.gregs[REG_RIP] += 4;   /* Skip the faulting mov */
  71
  72}
  73
  74enum which_base { FS, GS };
  75
  76static unsigned long read_base(enum which_base which)
  77{
  78        unsigned long offset;
  79        /*
  80         * Unless we have FSGSBASE, there's no direct way to do this from
  81         * user mode.  We can get at it indirectly using signals, though.
  82         */
  83
  84        want_segv = true;
  85
  86        offset = 0;
  87        if (which == FS) {
  88                /* Use a constant-length instruction here. */
  89                asm volatile ("mov %%fs:(%%rcx), %%rax" : : "c" (offset) : "rax");
  90        } else {
  91                asm volatile ("mov %%gs:(%%rcx), %%rax" : : "c" (offset) : "rax");
  92        }
  93        if (!want_segv)
  94                return segv_addr + offset;
  95
  96        /*
  97         * If that didn't segfault, try the other end of the address space.
  98         * Unless we get really unlucky and run into the vsyscall page, this
  99         * is guaranteed to segfault.
 100         */
 101
 102        offset = (ULONG_MAX >> 1) + 1;
 103        if (which == FS) {
 104                asm volatile ("mov %%fs:(%%rcx), %%rax"
 105                              : : "c" (offset) : "rax");
 106        } else {
 107                asm volatile ("mov %%gs:(%%rcx), %%rax"
 108                              : : "c" (offset) : "rax");
 109        }
 110        if (!want_segv)
 111                return segv_addr + offset;
 112
 113        abort();
 114}
 115
 116static void check_gs_value(unsigned long value)
 117{
 118        unsigned long base;
 119        unsigned short sel;
 120
 121        printf("[RUN]\tARCH_SET_GS to 0x%lx\n", value);
 122        if (syscall(SYS_arch_prctl, ARCH_SET_GS, value) != 0)
 123                err(1, "ARCH_SET_GS");
 124
 125        asm volatile ("mov %%gs, %0" : "=rm" (sel));
 126        base = read_base(GS);
 127        if (base == value) {
 128                printf("[OK]\tGSBASE was set as expected (selector 0x%hx)\n",
 129                       sel);
 130        } else {
 131                nerrs++;
 132                printf("[FAIL]\tGSBASE was not as expected: got 0x%lx (selector 0x%hx)\n",
 133                       base, sel);
 134        }
 135
 136        if (syscall(SYS_arch_prctl, ARCH_GET_GS, &base) != 0)
 137                err(1, "ARCH_GET_GS");
 138        if (base == value) {
 139                printf("[OK]\tARCH_GET_GS worked as expected (selector 0x%hx)\n",
 140                       sel);
 141        } else {
 142                nerrs++;
 143                printf("[FAIL]\tARCH_GET_GS was not as expected: got 0x%lx (selector 0x%hx)\n",
 144                       base, sel);
 145        }
 146}
 147
 148static void mov_0_gs(unsigned long initial_base, bool schedule)
 149{
 150        unsigned long base, arch_base;
 151
 152        printf("[RUN]\tARCH_SET_GS to 0x%lx then mov 0 to %%gs%s\n", initial_base, schedule ? " and schedule " : "");
 153        if (syscall(SYS_arch_prctl, ARCH_SET_GS, initial_base) != 0)
 154                err(1, "ARCH_SET_GS");
 155
 156        if (schedule)
 157                usleep(10);
 158
 159        asm volatile ("mov %0, %%gs" : : "rm" (0));
 160        base = read_base(GS);
 161        if (syscall(SYS_arch_prctl, ARCH_GET_GS, &arch_base) != 0)
 162                err(1, "ARCH_GET_GS");
 163        if (base == arch_base) {
 164                printf("[OK]\tGSBASE is 0x%lx\n", base);
 165        } else {
 166                nerrs++;
 167                printf("[FAIL]\tGSBASE changed to 0x%lx but kernel reports 0x%lx\n", base, arch_base);
 168        }
 169}
 170
 171static volatile unsigned long remote_base;
 172static volatile bool remote_hard_zero;
 173static volatile unsigned int ftx;
 174
 175/*
 176 * ARCH_SET_FS/GS(0) may or may not program a selector of zero.  HARD_ZERO
 177 * means to force the selector to zero to improve test coverage.
 178 */
 179#define HARD_ZERO 0xa1fa5f343cb85fa4
 180
 181static void do_remote_base()
 182{
 183        unsigned long to_set = remote_base;
 184        bool hard_zero = false;
 185        if (to_set == HARD_ZERO) {
 186                to_set = 0;
 187                hard_zero = true;
 188        }
 189
 190        if (syscall(SYS_arch_prctl, ARCH_SET_GS, to_set) != 0)
 191                err(1, "ARCH_SET_GS");
 192
 193        if (hard_zero)
 194                asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
 195
 196        unsigned short sel;
 197        asm volatile ("mov %%gs, %0" : "=rm" (sel));
 198        printf("\tother thread: ARCH_SET_GS(0x%lx)%s -- sel is 0x%hx\n",
 199               to_set, hard_zero ? " and clear gs" : "", sel);
 200}
 201
 202void do_unexpected_base(void)
 203{
 204        /*
 205         * The goal here is to try to arrange for GS == 0, GSBASE !=
 206         * 0, and for the the kernel the think that GSBASE == 0.
 207         *
 208         * To make the test as reliable as possible, this uses
 209         * explicit descriptorss.  (This is not the only way.  This
 210         * could use ARCH_SET_GS with a low, nonzero base, but the
 211         * relevant side effect of ARCH_SET_GS could change.)
 212         */
 213
 214        /* Step 1: tell the kernel that we have GSBASE == 0. */
 215        if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
 216                err(1, "ARCH_SET_GS");
 217
 218        /* Step 2: change GSBASE without telling the kernel. */
 219        struct user_desc desc = {
 220                .entry_number    = 0,
 221                .base_addr       = 0xBAADF00D,
 222                .limit           = 0xfffff,
 223                .seg_32bit       = 1,
 224                .contents        = 0, /* Data, grow-up */
 225                .read_exec_only  = 0,
 226                .limit_in_pages  = 1,
 227                .seg_not_present = 0,
 228                .useable         = 0
 229        };
 230        if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
 231                printf("\tother thread: using LDT slot 0\n");
 232                asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0x7));
 233        } else {
 234                /* No modify_ldt for us (configured out, perhaps) */
 235
 236                struct user_desc *low_desc = mmap(
 237                        NULL, sizeof(desc),
 238                        PROT_READ | PROT_WRITE,
 239                        MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
 240                memcpy(low_desc, &desc, sizeof(desc));
 241
 242                low_desc->entry_number = -1;
 243
 244                /* 32-bit set_thread_area */
 245                long ret;
 246                asm volatile ("int $0x80"
 247                              : "=a" (ret) : "a" (243), "b" (low_desc)
 248                              : "r8", "r9", "r10", "r11");
 249                memcpy(&desc, low_desc, sizeof(desc));
 250                munmap(low_desc, sizeof(desc));
 251
 252                if (ret != 0) {
 253                        printf("[NOTE]\tcould not create a segment -- test won't do anything\n");
 254                        return;
 255                }
 256                printf("\tother thread: using GDT slot %d\n", desc.entry_number);
 257                asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)((desc.entry_number << 3) | 0x3)));
 258        }
 259
 260        /*
 261         * Step 3: set the selector back to zero.  On AMD chips, this will
 262         * preserve GSBASE.
 263         */
 264
 265        asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
 266}
 267
 268static void *threadproc(void *ctx)
 269{
 270        while (1) {
 271                while (ftx == 0)
 272                        syscall(SYS_futex, &ftx, FUTEX_WAIT, 0, NULL, NULL, 0);
 273                if (ftx == 3)
 274                        return NULL;
 275
 276                if (ftx == 1)
 277                        do_remote_base();
 278                else if (ftx == 2)
 279                        do_unexpected_base();
 280                else
 281                        errx(1, "helper thread got bad command");
 282
 283                ftx = 0;
 284                syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
 285        }
 286}
 287
 288static void set_gs_and_switch_to(unsigned long local,
 289                                 unsigned short force_sel,
 290                                 unsigned long remote)
 291{
 292        unsigned long base;
 293        unsigned short sel_pre_sched, sel_post_sched;
 294
 295        bool hard_zero = false;
 296        if (local == HARD_ZERO) {
 297                hard_zero = true;
 298                local = 0;
 299        }
 300
 301        printf("[RUN]\tARCH_SET_GS(0x%lx)%s, then schedule to 0x%lx\n",
 302               local, hard_zero ? " and clear gs" : "", remote);
 303        if (force_sel)
 304                printf("\tBefore schedule, set selector to 0x%hx\n", force_sel);
 305        if (syscall(SYS_arch_prctl, ARCH_SET_GS, local) != 0)
 306                err(1, "ARCH_SET_GS");
 307        if (hard_zero)
 308                asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
 309
 310        if (read_base(GS) != local) {
 311                nerrs++;
 312                printf("[FAIL]\tGSBASE wasn't set as expected\n");
 313        }
 314
 315        if (force_sel) {
 316                asm volatile ("mov %0, %%gs" : : "rm" (force_sel));
 317                sel_pre_sched = force_sel;
 318                local = read_base(GS);
 319
 320                /*
 321                 * Signal delivery seems to mess up weird selectors.  Put it
 322                 * back.
 323                 */
 324                asm volatile ("mov %0, %%gs" : : "rm" (force_sel));
 325        } else {
 326                asm volatile ("mov %%gs, %0" : "=rm" (sel_pre_sched));
 327        }
 328
 329        remote_base = remote;
 330        ftx = 1;
 331        syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
 332        while (ftx != 0)
 333                syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
 334
 335        asm volatile ("mov %%gs, %0" : "=rm" (sel_post_sched));
 336        base = read_base(GS);
 337        if (base == local && sel_pre_sched == sel_post_sched) {
 338                printf("[OK]\tGS/BASE remained 0x%hx/0x%lx\n",
 339                       sel_pre_sched, local);
 340        } else {
 341                nerrs++;
 342                printf("[FAIL]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx\n",
 343                       sel_pre_sched, local, sel_post_sched, base);
 344        }
 345}
 346
 347static void test_unexpected_base(void)
 348{
 349        unsigned long base;
 350
 351        printf("[RUN]\tARCH_SET_GS(0), clear gs, then manipulate GSBASE in a different thread\n");
 352        if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
 353                err(1, "ARCH_SET_GS");
 354        asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
 355
 356        ftx = 2;
 357        syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
 358        while (ftx != 0)
 359                syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
 360
 361        base = read_base(GS);
 362        if (base == 0) {
 363                printf("[OK]\tGSBASE remained 0\n");
 364        } else {
 365                nerrs++;
 366                printf("[FAIL]\tGSBASE changed to 0x%lx\n", base);
 367        }
 368}
 369
 370int main()
 371{
 372        pthread_t thread;
 373
 374        sethandler(SIGSEGV, sigsegv, 0);
 375
 376        check_gs_value(0);
 377        check_gs_value(1);
 378        check_gs_value(0x200000000);
 379        check_gs_value(0);
 380        check_gs_value(0x200000000);
 381        check_gs_value(1);
 382
 383        for (int sched = 0; sched < 2; sched++) {
 384                mov_0_gs(0, !!sched);
 385                mov_0_gs(1, !!sched);
 386                mov_0_gs(0x200000000, !!sched);
 387        }
 388
 389        /* Set up for multithreading. */
 390
 391        cpu_set_t cpuset;
 392        CPU_ZERO(&cpuset);
 393        CPU_SET(0, &cpuset);
 394        if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
 395                err(1, "sched_setaffinity to CPU 0");   /* should never fail */
 396
 397        if (pthread_create(&thread, 0, threadproc, 0) != 0)
 398                err(1, "pthread_create");
 399
 400        static unsigned long bases_with_hard_zero[] = {
 401                0, HARD_ZERO, 1, 0x200000000,
 402        };
 403
 404        for (int local = 0; local < 4; local++) {
 405                for (int remote = 0; remote < 4; remote++) {
 406                        for (unsigned short s = 0; s < 5; s++) {
 407                                unsigned short sel = s;
 408                                if (s == 4)
 409                                        asm ("mov %%ss, %0" : "=rm" (sel));
 410                                set_gs_and_switch_to(
 411                                        bases_with_hard_zero[local],
 412                                        sel,
 413                                        bases_with_hard_zero[remote]);
 414                        }
 415                }
 416        }
 417
 418        test_unexpected_base();
 419
 420        ftx = 3;  /* Kill the thread. */
 421        syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
 422
 423        if (pthread_join(thread, NULL) != 0)
 424                err(1, "pthread_join");
 425
 426        return nerrs == 0 ? 0 : 1;
 427}
 428