single_step_syscall.c 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * single_step_syscall.c - single-steps various x86 syscalls
  4. * Copyright (c) 2014-2015 Andrew Lutomirski
  5. *
  6. * This is a very simple series of tests that makes system calls with
  7. * the TF flag set. This exercises some nasty kernel code in the
  8. * SYSENTER case: SYSENTER does not clear TF, so SYSENTER with TF set
  9. * immediately issues #DB from CPL 0. This requires special handling in
  10. * the kernel.
  11. */
  12. #define _GNU_SOURCE
  13. #include <sys/time.h>
  14. #include <time.h>
  15. #include <stdlib.h>
  16. #include <sys/syscall.h>
  17. #include <unistd.h>
  18. #include <stdio.h>
  19. #include <string.h>
  20. #include <inttypes.h>
  21. #include <sys/mman.h>
  22. #include <sys/signal.h>
  23. #include <sys/ucontext.h>
  24. #include <asm/ldt.h>
  25. #include <err.h>
  26. #include <setjmp.h>
  27. #include <stddef.h>
  28. #include <stdbool.h>
  29. #include <sys/ptrace.h>
  30. #include <sys/user.h>
  31. #include "helpers.h"
  32. static volatile sig_atomic_t sig_traps, sig_eflags;
  33. sigjmp_buf jmpbuf;
  34. #ifdef __x86_64__
  35. # define REG_IP REG_RIP
  36. # define WIDTH "q"
  37. # define INT80_CLOBBERS "r8", "r9", "r10", "r11"
  38. #else
  39. # define REG_IP REG_EIP
  40. # define WIDTH "l"
  41. # define INT80_CLOBBERS
  42. #endif
  43. static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
  44. {
  45. ucontext_t *ctx = (ucontext_t*)ctx_void;
  46. if (get_eflags() & X86_EFLAGS_TF) {
  47. set_eflags(get_eflags() & ~X86_EFLAGS_TF);
  48. printf("[WARN]\tSIGTRAP handler had TF set\n");
  49. _exit(1);
  50. }
  51. sig_traps++;
  52. if (sig_traps == 10000 || sig_traps == 10001) {
  53. printf("[WARN]\tHit %d SIGTRAPs with si_addr 0x%lx, ip 0x%lx\n",
  54. (int)sig_traps,
  55. (unsigned long)info->si_addr,
  56. (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
  57. }
  58. }
  59. static char const * const signames[] = {
  60. [SIGSEGV] = "SIGSEGV",
  61. [SIGBUS] = "SIBGUS",
  62. [SIGTRAP] = "SIGTRAP",
  63. [SIGILL] = "SIGILL",
  64. };
  65. static void print_and_longjmp(int sig, siginfo_t *si, void *ctx_void)
  66. {
  67. ucontext_t *ctx = ctx_void;
  68. printf("\tGot %s with RIP=%lx, TF=%ld\n", signames[sig],
  69. (unsigned long)ctx->uc_mcontext.gregs[REG_IP],
  70. (unsigned long)ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_TF);
  71. sig_eflags = (unsigned long)ctx->uc_mcontext.gregs[REG_EFL];
  72. siglongjmp(jmpbuf, 1);
  73. }
  74. static void check_result(void)
  75. {
  76. unsigned long new_eflags = get_eflags();
  77. set_eflags(new_eflags & ~X86_EFLAGS_TF);
  78. if (!sig_traps) {
  79. printf("[FAIL]\tNo SIGTRAP\n");
  80. exit(1);
  81. }
  82. if (!(new_eflags & X86_EFLAGS_TF)) {
  83. printf("[FAIL]\tTF was cleared\n");
  84. exit(1);
  85. }
  86. printf("[OK]\tSurvived with TF set and %d traps\n", (int)sig_traps);
  87. sig_traps = 0;
  88. }
  89. static void fast_syscall_no_tf(void)
  90. {
  91. sig_traps = 0;
  92. printf("[RUN]\tFast syscall with TF cleared\n");
  93. fflush(stdout); /* Force a syscall */
  94. if (get_eflags() & X86_EFLAGS_TF) {
  95. printf("[FAIL]\tTF is now set\n");
  96. exit(1);
  97. }
  98. if (sig_traps) {
  99. printf("[FAIL]\tGot SIGTRAP\n");
  100. exit(1);
  101. }
  102. printf("[OK]\tNothing unexpected happened\n");
  103. }
  104. int main()
  105. {
  106. #ifdef CAN_BUILD_32
  107. int tmp;
  108. #endif
  109. sethandler(SIGTRAP, sigtrap, 0);
  110. printf("[RUN]\tSet TF and check nop\n");
  111. set_eflags(get_eflags() | X86_EFLAGS_TF);
  112. asm volatile ("nop");
  113. check_result();
  114. #ifdef __x86_64__
  115. printf("[RUN]\tSet TF and check syscall-less opportunistic sysret\n");
  116. set_eflags(get_eflags() | X86_EFLAGS_TF);
  117. extern unsigned char post_nop[];
  118. asm volatile ("pushf" WIDTH "\n\t"
  119. "pop" WIDTH " %%r11\n\t"
  120. "nop\n\t"
  121. "post_nop:"
  122. : : "c" (post_nop) : "r11");
  123. check_result();
  124. #endif
  125. #ifdef CAN_BUILD_32
  126. printf("[RUN]\tSet TF and check int80\n");
  127. set_eflags(get_eflags() | X86_EFLAGS_TF);
  128. asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid)
  129. : INT80_CLOBBERS);
  130. check_result();
  131. #endif
  132. /*
  133. * This test is particularly interesting if fast syscalls use
  134. * SYSENTER: it triggers a nasty design flaw in SYSENTER.
  135. * Specifically, SYSENTER does not clear TF, so either SYSENTER
  136. * or the next instruction traps at CPL0. (Of course, Intel
  137. * mostly forgot to document exactly what happens here.) So we
  138. * get a CPL0 fault with usergs (on 64-bit kernels) and possibly
  139. * no stack. The only sane way the kernel can possibly handle
  140. * it is to clear TF on return from the #DB handler, but this
  141. * happens way too early to set TF in the saved pt_regs, so the
  142. * kernel has to do something clever to avoid losing track of
  143. * the TF bit.
  144. *
  145. * Needless to say, we've had bugs in this area.
  146. */
  147. syscall(SYS_getpid); /* Force symbol binding without TF set. */
  148. printf("[RUN]\tSet TF and check a fast syscall\n");
  149. set_eflags(get_eflags() | X86_EFLAGS_TF);
  150. syscall(SYS_getpid);
  151. check_result();
  152. /* Now make sure that another fast syscall doesn't set TF again. */
  153. fast_syscall_no_tf();
  154. /*
  155. * And do a forced SYSENTER to make sure that this works even if
  156. * fast syscalls don't use SYSENTER.
  157. *
  158. * Invoking SYSENTER directly breaks all the rules. Just handle
  159. * the SIGSEGV.
  160. */
  161. if (sigsetjmp(jmpbuf, 1) == 0) {
  162. unsigned long nr = SYS_getpid;
  163. printf("[RUN]\tSet TF and check SYSENTER\n");
  164. stack_t stack = {
  165. .ss_sp = malloc(sizeof(char) * SIGSTKSZ),
  166. .ss_size = SIGSTKSZ,
  167. };
  168. if (sigaltstack(&stack, NULL) != 0)
  169. err(1, "sigaltstack");
  170. sethandler(SIGSEGV, print_and_longjmp,
  171. SA_RESETHAND | SA_ONSTACK);
  172. sethandler(SIGILL, print_and_longjmp, SA_RESETHAND);
  173. set_eflags(get_eflags() | X86_EFLAGS_TF);
  174. free(stack.ss_sp);
  175. /* Clear EBP first to make sure we segfault cleanly. */
  176. asm volatile ("xorl %%ebp, %%ebp; SYSENTER" : "+a" (nr) :: "flags", "rcx"
  177. #ifdef __x86_64__
  178. , "r11"
  179. #endif
  180. );
  181. /* We're unreachable here. SYSENTER forgets RIP. */
  182. }
  183. clearhandler(SIGSEGV);
  184. clearhandler(SIGILL);
  185. if (!(sig_eflags & X86_EFLAGS_TF)) {
  186. printf("[FAIL]\tTF was cleared\n");
  187. exit(1);
  188. }
  189. /* Now make sure that another fast syscall doesn't set TF again. */
  190. fast_syscall_no_tf();
  191. return 0;
  192. }