~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/tools/testing/selftests/x86/fsgsbase.c

Version: ~ [ linux-5.2-rc4 ] ~ [ linux-5.1.9 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.50 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.125 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.181 ] ~ [ linux-4.8.17 ] ~ [ linux-4.7.10 ] ~ [ linux-4.6.7 ] ~ [ linux-4.5.7 ] ~ [ linux-4.4.181 ] ~ [ linux-4.3.6 ] ~ [ linux-4.2.8 ] ~ [ linux-4.1.52 ] ~ [ linux-4.0.9 ] ~ [ linux-3.19.8 ] ~ [ linux-3.18.140 ] ~ [ linux-3.17.8 ] ~ [ linux-3.16.68 ] ~ [ linux-3.15.10 ] ~ [ linux-3.14.79 ] ~ [ linux-3.13.11 ] ~ [ linux-3.12.74 ] ~ [ linux-3.11.10 ] ~ [ linux-3.10.108 ] ~ [ linux-3.9.11 ] ~ [ linux-3.8.13 ] ~ [ linux-3.7.10 ] ~ [ linux-3.6.11 ] ~ [ linux-3.5.7 ] ~ [ linux-3.4.113 ] ~ [ linux-3.3.8 ] ~ [ linux-3.2.102 ] ~ [ linux-3.1.10 ] ~ [ linux-3.0.101 ] ~ [ linux-2.6.39.4 ] ~ [ linux-2.6.38.8 ] ~ [ linux-2.6.37.6 ] ~ [ linux-2.6.36.4 ] ~ [ linux-2.6.35.14 ] ~ [ linux-2.6.34.15 ] ~ [ linux-2.6.33.20 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.5 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  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 
 31 static volatile sig_atomic_t want_segv;
 32 static volatile unsigned long segv_addr;
 33 
 34 static int nerrs;
 35 
 36 static 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 
 48 static 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 
 58 static 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 
 74 enum which_base { FS, GS };
 75 
 76 static 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 
116 static 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 
148 static 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 
171 static volatile unsigned long remote_base;
172 static volatile bool remote_hard_zero;
173 static 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 
181 static 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 
202 void 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                               : "flags");
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 
268 static 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 
288 static void set_gs_and_switch_to(unsigned long local, unsigned long remote)
289 {
290         unsigned long base;
291 
292         bool hard_zero = false;
293         if (local == HARD_ZERO) {
294                 hard_zero = true;
295                 local = 0;
296         }
297 
298         printf("[RUN]\tARCH_SET_GS(0x%lx)%s, then schedule to 0x%lx\n",
299                local, hard_zero ? " and clear gs" : "", remote);
300         if (syscall(SYS_arch_prctl, ARCH_SET_GS, local) != 0)
301                 err(1, "ARCH_SET_GS");
302         if (hard_zero)
303                 asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
304 
305         if (read_base(GS) != local) {
306                 nerrs++;
307                 printf("[FAIL]\tGSBASE wasn't set as expected\n");
308         }
309 
310         remote_base = remote;
311         ftx = 1;
312         syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
313         while (ftx != 0)
314                 syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
315 
316         base = read_base(GS);
317         if (base == local) {
318                 printf("[OK]\tGSBASE remained 0x%lx\n", local);
319         } else {
320                 nerrs++;
321                 printf("[FAIL]\tGSBASE changed to 0x%lx\n", base);
322         }
323 }
324 
325 static void test_unexpected_base(void)
326 {
327         unsigned long base;
328 
329         printf("[RUN]\tARCH_SET_GS(0), clear gs, then manipulate GSBASE in a different thread\n");
330         if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
331                 err(1, "ARCH_SET_GS");
332         asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
333 
334         ftx = 2;
335         syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
336         while (ftx != 0)
337                 syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
338 
339         base = read_base(GS);
340         if (base == 0) {
341                 printf("[OK]\tGSBASE remained 0\n");
342         } else {
343                 nerrs++;
344                 printf("[FAIL]\tGSBASE changed to 0x%lx\n", base);
345         }
346 }
347 
348 int main()
349 {
350         pthread_t thread;
351 
352         sethandler(SIGSEGV, sigsegv, 0);
353 
354         check_gs_value(0);
355         check_gs_value(1);
356         check_gs_value(0x200000000);
357         check_gs_value(0);
358         check_gs_value(0x200000000);
359         check_gs_value(1);
360 
361         for (int sched = 0; sched < 2; sched++) {
362                 mov_0_gs(0, !!sched);
363                 mov_0_gs(1, !!sched);
364                 mov_0_gs(0x200000000, !!sched);
365         }
366 
367         /* Set up for multithreading. */
368 
369         cpu_set_t cpuset;
370         CPU_ZERO(&cpuset);
371         CPU_SET(0, &cpuset);
372         if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
373                 err(1, "sched_setaffinity to CPU 0");   /* should never fail */
374 
375         if (pthread_create(&thread, 0, threadproc, 0) != 0)
376                 err(1, "pthread_create");
377 
378         static unsigned long bases_with_hard_zero[] = {
379                 0, HARD_ZERO, 1, 0x200000000,
380         };
381 
382         for (int local = 0; local < 4; local++) {
383                 for (int remote = 0; remote < 4; remote++) {
384                         set_gs_and_switch_to(bases_with_hard_zero[local],
385                                              bases_with_hard_zero[remote]);
386                 }
387         }
388 
389         test_unexpected_base();
390 
391         ftx = 3;  /* Kill the thread. */
392         syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
393 
394         if (pthread_join(thread, NULL) != 0)
395                 err(1, "pthread_join");
396 
397         return nerrs == 0 ? 0 : 1;
398 }
399 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | Wiki (Japanese) | Wiki (English) | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

osdn.jp