sysret_rip.c 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. #include "helpers.h"
  23. /*
  24. * These items are in clang_helpers_64.S, in order to avoid clang inline asm
  25. * limitations:
  26. */
  27. void test_syscall_ins(void);
  28. extern const char test_page[];
  29. static const void *current_test_page_addr = test_page;
  30. /* State used by our signal handlers. */
  31. static gregset_t initial_regs;
  32. static volatile unsigned long rip;
  33. static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
  34. {
  35. ucontext_t *ctx = (ucontext_t *)ctx_void;
  36. if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
  37. printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
  38. rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
  39. fflush(stdout);
  40. _exit(1);
  41. }
  42. memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
  43. printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
  44. }
  45. static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
  46. {
  47. ucontext_t *ctx = (ucontext_t *)ctx_void;
  48. memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
  49. /* Set IP and CX to match so that SYSRET can happen. */
  50. ctx->uc_mcontext.gregs[REG_RIP] = rip;
  51. ctx->uc_mcontext.gregs[REG_RCX] = rip;
  52. /* R11 and EFLAGS should already match. */
  53. assert(ctx->uc_mcontext.gregs[REG_EFL] ==
  54. ctx->uc_mcontext.gregs[REG_R11]);
  55. sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
  56. }
  57. static void test_sigreturn_to(unsigned long ip)
  58. {
  59. rip = ip;
  60. printf("[RUN]\tsigreturn to 0x%lx\n", ip);
  61. raise(SIGUSR1);
  62. }
  63. static jmp_buf jmpbuf;
  64. static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
  65. {
  66. ucontext_t *ctx = (ucontext_t *)ctx_void;
  67. if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
  68. printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
  69. rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
  70. fflush(stdout);
  71. _exit(1);
  72. }
  73. siglongjmp(jmpbuf, 1);
  74. }
  75. static void test_syscall_fallthrough_to(unsigned long ip)
  76. {
  77. void *new_address = (void *)(ip - 4096);
  78. void *ret;
  79. printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
  80. ret = mremap((void *)current_test_page_addr, 4096, 4096,
  81. MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
  82. if (ret == MAP_FAILED) {
  83. if (ip <= (1UL << 47) - PAGE_SIZE) {
  84. err(1, "mremap to %p", new_address);
  85. } else {
  86. printf("[OK]\tmremap to %p failed\n", new_address);
  87. return;
  88. }
  89. }
  90. if (ret != new_address)
  91. errx(1, "mremap malfunctioned: asked for %p but got %p\n",
  92. new_address, ret);
  93. current_test_page_addr = new_address;
  94. rip = ip;
  95. if (sigsetjmp(jmpbuf, 1) == 0) {
  96. asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
  97. [syscall_insn] "rm" (ip - 2));
  98. errx(1, "[FAIL]\tSyscall trampoline returned");
  99. }
  100. printf("[OK]\tWe survived\n");
  101. }
  102. int main(void)
  103. {
  104. /*
  105. * When the kernel returns from a slow-path syscall, it will
  106. * detect whether SYSRET is appropriate. If it incorrectly
  107. * thinks that SYSRET is appropriate when RIP is noncanonical,
  108. * it'll crash on Intel CPUs.
  109. */
  110. sethandler(SIGUSR1, sigusr1, 0);
  111. for (int i = 47; i < 64; i++)
  112. test_sigreturn_to(1UL<<i);
  113. clearhandler(SIGUSR1);
  114. sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
  115. /* One extra test to check that we didn't screw up the mremap logic. */
  116. test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
  117. /* These are the interesting cases. */
  118. for (int i = 47; i < 64; i++) {
  119. test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
  120. test_syscall_fallthrough_to(1UL<<i);
  121. }
  122. return 0;
  123. }