sysret_rip.c 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
  4. * Copyright (c) 2014-2016 Andrew Lutomirski
  5. */
  6. #define _GNU_SOURCE
  7. #include <stdlib.h>
  8. #include <unistd.h>
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include <inttypes.h>
  12. #include <sys/signal.h>
  13. #include <sys/ucontext.h>
  14. #include <sys/syscall.h>
  15. #include <err.h>
  16. #include <stddef.h>
  17. #include <stdbool.h>
  18. #include <setjmp.h>
  19. #include <sys/user.h>
  20. #include <sys/mman.h>
  21. #include <assert.h>
  22. /*
  23. * These items are in clang_helpers_64.S, in order to avoid clang inline asm
  24. * limitations:
  25. */
  26. void test_syscall_ins(void);
  27. extern const char test_page[];
  28. static void const *current_test_page_addr = test_page;
  29. static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
  30. int flags)
  31. {
  32. struct sigaction sa;
  33. memset(&sa, 0, sizeof(sa));
  34. sa.sa_sigaction = handler;
  35. sa.sa_flags = SA_SIGINFO | flags;
  36. sigemptyset(&sa.sa_mask);
  37. if (sigaction(sig, &sa, 0))
  38. err(1, "sigaction");
  39. }
  40. static void clearhandler(int sig)
  41. {
  42. struct sigaction sa;
  43. memset(&sa, 0, sizeof(sa));
  44. sa.sa_handler = SIG_DFL;
  45. sigemptyset(&sa.sa_mask);
  46. if (sigaction(sig, &sa, 0))
  47. err(1, "sigaction");
  48. }
  49. /* State used by our signal handlers. */
  50. static gregset_t initial_regs;
  51. static volatile unsigned long rip;
  52. static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
  53. {
  54. ucontext_t *ctx = (ucontext_t*)ctx_void;
  55. if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
  56. printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
  57. rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
  58. fflush(stdout);
  59. _exit(1);
  60. }
  61. memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
  62. printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
  63. }
  64. static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
  65. {
  66. ucontext_t *ctx = (ucontext_t*)ctx_void;
  67. memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
  68. /* Set IP and CX to match so that SYSRET can happen. */
  69. ctx->uc_mcontext.gregs[REG_RIP] = rip;
  70. ctx->uc_mcontext.gregs[REG_RCX] = rip;
  71. /* R11 and EFLAGS should already match. */
  72. assert(ctx->uc_mcontext.gregs[REG_EFL] ==
  73. ctx->uc_mcontext.gregs[REG_R11]);
  74. sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
  75. return;
  76. }
  77. static void test_sigreturn_to(unsigned long ip)
  78. {
  79. rip = ip;
  80. printf("[RUN]\tsigreturn to 0x%lx\n", ip);
  81. raise(SIGUSR1);
  82. }
  83. static jmp_buf jmpbuf;
  84. static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
  85. {
  86. ucontext_t *ctx = (ucontext_t*)ctx_void;
  87. if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
  88. printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
  89. rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
  90. fflush(stdout);
  91. _exit(1);
  92. }
  93. siglongjmp(jmpbuf, 1);
  94. }
  95. static void test_syscall_fallthrough_to(unsigned long ip)
  96. {
  97. void *new_address = (void *)(ip - 4096);
  98. void *ret;
  99. printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
  100. ret = mremap((void *)current_test_page_addr, 4096, 4096,
  101. MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
  102. if (ret == MAP_FAILED) {
  103. if (ip <= (1UL << 47) - PAGE_SIZE) {
  104. err(1, "mremap to %p", new_address);
  105. } else {
  106. printf("[OK]\tmremap to %p failed\n", new_address);
  107. return;
  108. }
  109. }
  110. if (ret != new_address)
  111. errx(1, "mremap malfunctioned: asked for %p but got %p\n",
  112. new_address, ret);
  113. current_test_page_addr = new_address;
  114. rip = ip;
  115. if (sigsetjmp(jmpbuf, 1) == 0) {
  116. asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
  117. [syscall_insn] "rm" (ip - 2));
  118. errx(1, "[FAIL]\tSyscall trampoline returned");
  119. }
  120. printf("[OK]\tWe survived\n");
  121. }
  122. int main()
  123. {
  124. /*
  125. * When the kernel returns from a slow-path syscall, it will
  126. * detect whether SYSRET is appropriate. If it incorrectly
  127. * thinks that SYSRET is appropriate when RIP is noncanonical,
  128. * it'll crash on Intel CPUs.
  129. */
  130. sethandler(SIGUSR1, sigusr1, 0);
  131. for (int i = 47; i < 64; i++)
  132. test_sigreturn_to(1UL<<i);
  133. clearhandler(SIGUSR1);
  134. sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
  135. /* One extra test to check that we didn't screw up the mremap logic. */
  136. test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
  137. /* These are the interesting cases. */
  138. for (int i = 47; i < 64; i++) {
  139. test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
  140. test_syscall_fallthrough_to(1UL<<i);
  141. }
  142. return 0;
  143. }