nx_stack.c 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /*
  2. * Copyright (c) 2023 Alexey Dobriyan <adobriyan@gmail.com>
  3. *
  4. * Permission to use, copy, modify, and distribute this software for any
  5. * purpose with or without fee is hereby granted, provided that the above
  6. * copyright notice and this permission notice appear in all copies.
  7. *
  8. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. /*
  17. * Test that userspace stack is NX. Requires linking with -Wl,-z,noexecstack
  18. * because I don't want to bother with PT_GNU_STACK detection.
  19. *
  20. * Fill the stack with INT3's and then try to execute some of them:
  21. * SIGSEGV -- good, SIGTRAP -- bad.
  22. *
  23. * Regular stack is completely overwritten before testing.
  24. * Test doesn't exit SIGSEGV handler after first fault at INT3.
  25. */
  26. #undef _GNU_SOURCE
  27. #define _GNU_SOURCE
  28. #undef NDEBUG
  29. #include <assert.h>
  30. #include <signal.h>
  31. #include <stdint.h>
  32. #include <stdio.h>
  33. #include <stdlib.h>
  34. #include <sys/mman.h>
  35. #include <sys/resource.h>
  36. #include <unistd.h>
  37. #define PAGE_SIZE 4096
  38. /*
  39. * This is memset(rsp, 0xcc, -1); but down.
  40. * It will SIGSEGV when bottom of the stack is reached.
  41. * Byte-size access is important! (see rdi tweak in the signal handler).
  42. */
  43. void make_stack1(void);
  44. asm(
  45. ".pushsection .text\n"
  46. ".globl make_stack1\n"
  47. ".align 16\n"
  48. "make_stack1:\n"
  49. "mov $0xcc, %al\n"
  50. #if defined __amd64__
  51. "mov %rsp, %rdi\n"
  52. "mov $-1, %rcx\n"
  53. #elif defined __i386__
  54. "mov %esp, %edi\n"
  55. "mov $-1, %ecx\n"
  56. #else
  57. #error
  58. #endif
  59. "std\n"
  60. "rep stosb\n"
  61. /* unreachable */
  62. "hlt\n"
  63. ".type make_stack1,@function\n"
  64. ".size make_stack1,.-make_stack1\n"
  65. ".popsection\n"
  66. );
  67. /*
  68. * memset(p, 0xcc, -1);
  69. * It will SIGSEGV when top of the stack is reached.
  70. */
  71. void make_stack2(uint64_t p);
  72. asm(
  73. ".pushsection .text\n"
  74. ".globl make_stack2\n"
  75. ".align 16\n"
  76. "make_stack2:\n"
  77. "mov $0xcc, %al\n"
  78. #if defined __amd64__
  79. "mov $-1, %rcx\n"
  80. #elif defined __i386__
  81. "mov $-1, %ecx\n"
  82. #else
  83. #error
  84. #endif
  85. "cld\n"
  86. "rep stosb\n"
  87. /* unreachable */
  88. "hlt\n"
  89. ".type make_stack2,@function\n"
  90. ".size make_stack2,.-make_stack2\n"
  91. ".popsection\n"
  92. );
  93. static volatile int test_state = 0;
  94. static volatile unsigned long stack_min_addr;
  95. #if defined __amd64__
  96. #define RDI REG_RDI
  97. #define RIP REG_RIP
  98. #define RIP_STRING "rip"
  99. #elif defined __i386__
  100. #define RDI REG_EDI
  101. #define RIP REG_EIP
  102. #define RIP_STRING "eip"
  103. #else
  104. #error
  105. #endif
  106. static void sigsegv(int _, siginfo_t *__, void *uc_)
  107. {
  108. /*
  109. * Some Linux versions didn't clear DF before entering signal
  110. * handler. make_stack1() doesn't have a chance to clear DF
  111. * either so we clear it by hand here.
  112. */
  113. asm volatile ("cld" ::: "memory");
  114. ucontext_t *uc = uc_;
  115. if (test_state == 0) {
  116. /* Stack is faulted and cleared from RSP to the lowest address. */
  117. stack_min_addr = ++uc->uc_mcontext.gregs[RDI];
  118. if (1) {
  119. printf("stack min %lx\n", stack_min_addr);
  120. }
  121. uc->uc_mcontext.gregs[RIP] = (uintptr_t)&make_stack2;
  122. test_state = 1;
  123. } else if (test_state == 1) {
  124. /* Stack has been cleared from top to bottom. */
  125. unsigned long stack_max_addr = uc->uc_mcontext.gregs[RDI];
  126. if (1) {
  127. printf("stack max %lx\n", stack_max_addr);
  128. }
  129. /* Start faulting pages on stack and see what happens. */
  130. uc->uc_mcontext.gregs[RIP] = stack_max_addr - PAGE_SIZE;
  131. test_state = 2;
  132. } else if (test_state == 2) {
  133. /* Stack page is NX -- good, test next page. */
  134. uc->uc_mcontext.gregs[RIP] -= PAGE_SIZE;
  135. if (uc->uc_mcontext.gregs[RIP] == stack_min_addr) {
  136. /* One more SIGSEGV and test ends. */
  137. test_state = 3;
  138. }
  139. } else {
  140. printf("PASS\tAll stack pages are NX\n");
  141. _exit(EXIT_SUCCESS);
  142. }
  143. }
  144. static void sigtrap(int _, siginfo_t *__, void *uc_)
  145. {
  146. const ucontext_t *uc = uc_;
  147. unsigned long rip = uc->uc_mcontext.gregs[RIP];
  148. printf("FAIL\texecutable page on the stack: " RIP_STRING " %lx\n", rip);
  149. _exit(EXIT_FAILURE);
  150. }
  151. int main(void)
  152. {
  153. {
  154. struct sigaction act = {};
  155. sigemptyset(&act.sa_mask);
  156. act.sa_flags = SA_SIGINFO;
  157. act.sa_sigaction = &sigsegv;
  158. int rv = sigaction(SIGSEGV, &act, NULL);
  159. assert(rv == 0);
  160. }
  161. {
  162. struct sigaction act = {};
  163. sigemptyset(&act.sa_mask);
  164. act.sa_flags = SA_SIGINFO;
  165. act.sa_sigaction = &sigtrap;
  166. int rv = sigaction(SIGTRAP, &act, NULL);
  167. assert(rv == 0);
  168. }
  169. {
  170. struct rlimit rlim;
  171. int rv = getrlimit(RLIMIT_STACK, &rlim);
  172. assert(rv == 0);
  173. /* Cap stack at time-honored 8 MiB value. */
  174. rlim.rlim_max = rlim.rlim_cur;
  175. if (rlim.rlim_max > 8 * 1024 * 1024) {
  176. rlim.rlim_max = 8 * 1024 * 1024;
  177. }
  178. rv = setrlimit(RLIMIT_STACK, &rlim);
  179. assert(rv == 0);
  180. }
  181. {
  182. /*
  183. * We don't know now much stack SIGSEGV handler uses.
  184. * Bump this by 1 page every time someone complains,
  185. * or rewrite it in assembly.
  186. */
  187. const size_t len = SIGSTKSZ;
  188. void *p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
  189. assert(p != MAP_FAILED);
  190. stack_t ss = {};
  191. ss.ss_sp = p;
  192. ss.ss_size = len;
  193. int rv = sigaltstack(&ss, NULL);
  194. assert(rv == 0);
  195. }
  196. make_stack1();
  197. /*
  198. * Unreachable, but if _this_ INT3 is ever reached, it's a bug somewhere.
  199. * Fold it into main SIGTRAP pathway.
  200. */
  201. __builtin_trap();
  202. }