fsgsbase.c 9.5 KB

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