fsgsbase.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  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. : "r8", "r9", "r10", "r11");
  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,
  244. unsigned short force_sel,
  245. unsigned long remote)
  246. {
  247. unsigned long base;
  248. unsigned short sel_pre_sched, sel_post_sched;
  249. bool hard_zero = false;
  250. if (local == HARD_ZERO) {
  251. hard_zero = true;
  252. local = 0;
  253. }
  254. printf("[RUN]\tARCH_SET_GS(0x%lx)%s, then schedule to 0x%lx\n",
  255. local, hard_zero ? " and clear gs" : "", remote);
  256. if (force_sel)
  257. printf("\tBefore schedule, set selector to 0x%hx\n", force_sel);
  258. if (syscall(SYS_arch_prctl, ARCH_SET_GS, local) != 0)
  259. err(1, "ARCH_SET_GS");
  260. if (hard_zero)
  261. asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
  262. if (read_base(GS) != local) {
  263. nerrs++;
  264. printf("[FAIL]\tGSBASE wasn't set as expected\n");
  265. }
  266. if (force_sel) {
  267. asm volatile ("mov %0, %%gs" : : "rm" (force_sel));
  268. sel_pre_sched = force_sel;
  269. local = read_base(GS);
  270. /*
  271. * Signal delivery seems to mess up weird selectors. Put it
  272. * back.
  273. */
  274. asm volatile ("mov %0, %%gs" : : "rm" (force_sel));
  275. } else {
  276. asm volatile ("mov %%gs, %0" : "=rm" (sel_pre_sched));
  277. }
  278. remote_base = remote;
  279. ftx = 1;
  280. syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
  281. while (ftx != 0)
  282. syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
  283. asm volatile ("mov %%gs, %0" : "=rm" (sel_post_sched));
  284. base = read_base(GS);
  285. if (base == local && sel_pre_sched == sel_post_sched) {
  286. printf("[OK]\tGS/BASE remained 0x%hx/0x%lx\n",
  287. sel_pre_sched, local);
  288. } else {
  289. nerrs++;
  290. printf("[FAIL]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx\n",
  291. sel_pre_sched, local, sel_post_sched, base);
  292. }
  293. }
  294. static void test_unexpected_base(void)
  295. {
  296. unsigned long base;
  297. printf("[RUN]\tARCH_SET_GS(0), clear gs, then manipulate GSBASE in a different thread\n");
  298. if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
  299. err(1, "ARCH_SET_GS");
  300. asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
  301. ftx = 2;
  302. syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
  303. while (ftx != 0)
  304. syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
  305. base = read_base(GS);
  306. if (base == 0) {
  307. printf("[OK]\tGSBASE remained 0\n");
  308. } else {
  309. nerrs++;
  310. printf("[FAIL]\tGSBASE changed to 0x%lx\n", base);
  311. }
  312. }
  313. int main()
  314. {
  315. pthread_t thread;
  316. sethandler(SIGSEGV, sigsegv, 0);
  317. check_gs_value(0);
  318. check_gs_value(1);
  319. check_gs_value(0x200000000);
  320. check_gs_value(0);
  321. check_gs_value(0x200000000);
  322. check_gs_value(1);
  323. for (int sched = 0; sched < 2; sched++) {
  324. mov_0_gs(0, !!sched);
  325. mov_0_gs(1, !!sched);
  326. mov_0_gs(0x200000000, !!sched);
  327. }
  328. /* Set up for multithreading. */
  329. cpu_set_t cpuset;
  330. CPU_ZERO(&cpuset);
  331. CPU_SET(0, &cpuset);
  332. if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
  333. err(1, "sched_setaffinity to CPU 0"); /* should never fail */
  334. if (pthread_create(&thread, 0, threadproc, 0) != 0)
  335. err(1, "pthread_create");
  336. static unsigned long bases_with_hard_zero[] = {
  337. 0, HARD_ZERO, 1, 0x200000000,
  338. };
  339. for (int local = 0; local < 4; local++) {
  340. for (int remote = 0; remote < 4; remote++) {
  341. for (unsigned short s = 0; s < 5; s++) {
  342. unsigned short sel = s;
  343. if (s == 4)
  344. asm ("mov %%ss, %0" : "=rm" (sel));
  345. set_gs_and_switch_to(
  346. bases_with_hard_zero[local],
  347. sel,
  348. bases_with_hard_zero[remote]);
  349. }
  350. }
  351. }
  352. test_unexpected_base();
  353. ftx = 3; /* Kill the thread. */
  354. syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
  355. if (pthread_join(thread, NULL) != 0)
  356. err(1, "pthread_join");
  357. return nerrs == 0 ? 0 : 1;
  358. }