lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame] | 1 | /* Test program for making nonexecutable stacks executable |
| 2 | on load of a DSO that requires executable stacks. */ |
| 3 | |
| 4 | #include <dlfcn.h> |
| 5 | #include <stdbool.h> |
| 6 | #include <stdio.h> |
| 7 | #include <string.h> |
| 8 | #include <unistd.h> |
| 9 | #include <error.h> |
| 10 | #include <stackinfo.h> |
| 11 | |
| 12 | static void |
| 13 | print_maps (void) |
| 14 | { |
| 15 | #if 0 |
| 16 | char *cmd = NULL; |
| 17 | asprintf (&cmd, "cat /proc/%d/maps", getpid ()); |
| 18 | system (cmd); |
| 19 | free (cmd); |
| 20 | #endif |
| 21 | } |
| 22 | |
| 23 | static void deeper (void (*f) (void)); |
| 24 | |
| 25 | #if USE_PTHREADS |
| 26 | # include <pthread.h> |
| 27 | |
| 28 | static void * |
| 29 | tryme_thread (void *f) |
| 30 | { |
| 31 | (*((void (*) (void)) f)) (); |
| 32 | |
| 33 | return 0; |
| 34 | } |
| 35 | |
| 36 | static pthread_barrier_t startup_barrier, go_barrier; |
| 37 | static void * |
| 38 | waiter_thread (void *arg) |
| 39 | { |
| 40 | void **f = arg; |
| 41 | pthread_barrier_wait (&startup_barrier); |
| 42 | pthread_barrier_wait (&go_barrier); |
| 43 | |
| 44 | (*((void (*) (void)) *f)) (); |
| 45 | |
| 46 | return 0; |
| 47 | } |
| 48 | #endif |
| 49 | |
| 50 | static bool allow_execstack = true; |
| 51 | |
| 52 | |
| 53 | static int |
| 54 | do_test (void) |
| 55 | { |
| 56 | /* Check whether SELinux is enabled and disallows executable stacks. */ |
| 57 | FILE *fp = fopen ("/selinux/enforce", "r"); |
| 58 | if (fp != NULL) |
| 59 | { |
| 60 | char *line = NULL; |
| 61 | size_t linelen = 0; |
| 62 | |
| 63 | bool enabled = false; |
| 64 | ssize_t n = getline (&line, &linelen, fp); |
| 65 | if (n > 0 && line[0] != '0') |
| 66 | enabled = true; |
| 67 | |
| 68 | fclose (fp); |
| 69 | |
| 70 | if (enabled) |
| 71 | { |
| 72 | fp = fopen ("/selinux/booleans/allow_execstack", "r"); |
| 73 | if (fp != NULL) |
| 74 | { |
| 75 | n = getline (&line, &linelen, fp); |
| 76 | if (n > 0 && line[0] == '0') |
| 77 | allow_execstack = false; |
| 78 | } |
| 79 | |
| 80 | fclose (fp); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | printf ("executable stacks %sallowed\n", allow_execstack ? "" : "not "); |
| 85 | |
| 86 | static void *f; /* Address of this is used in other threads. */ |
| 87 | |
| 88 | #if USE_PTHREADS |
| 89 | /* Create some threads while stacks are nonexecutable. */ |
| 90 | #define N 5 |
| 91 | pthread_t thr[N]; |
| 92 | |
| 93 | pthread_barrier_init (&startup_barrier, NULL, N + 1); |
| 94 | pthread_barrier_init (&go_barrier, NULL, N + 1); |
| 95 | |
| 96 | for (int i = 0; i < N; ++i) |
| 97 | { |
| 98 | int rc = pthread_create (&thr[i], NULL, &waiter_thread, &f); |
| 99 | if (rc) |
| 100 | error (1, rc, "pthread_create"); |
| 101 | } |
| 102 | |
| 103 | /* Make sure they are all there using their stacks. */ |
| 104 | pthread_barrier_wait (&startup_barrier); |
| 105 | puts ("threads waiting"); |
| 106 | #endif |
| 107 | |
| 108 | print_maps (); |
| 109 | |
| 110 | #if USE_PTHREADS |
| 111 | void *old_stack_addr, *new_stack_addr; |
| 112 | size_t stack_size; |
| 113 | pthread_t me = pthread_self (); |
| 114 | pthread_attr_t attr; |
| 115 | int ret = 0; |
| 116 | |
| 117 | ret = pthread_getattr_np (me, &attr); |
| 118 | if (ret) |
| 119 | { |
| 120 | printf ("before execstack: pthread_getattr_np returned error: %s\n", |
| 121 | strerror (ret)); |
| 122 | return 1; |
| 123 | } |
| 124 | |
| 125 | ret = pthread_attr_getstack (&attr, &old_stack_addr, &stack_size); |
| 126 | if (ret) |
| 127 | { |
| 128 | printf ("before execstack: pthread_attr_getstack returned error: %s\n", |
| 129 | strerror (ret)); |
| 130 | return 1; |
| 131 | } |
| 132 | # if _STACK_GROWS_DOWN |
| 133 | old_stack_addr += stack_size; |
| 134 | # else |
| 135 | old_stack_addr -= stack_size; |
| 136 | # endif |
| 137 | #endif |
| 138 | |
| 139 | /* Loading this module should force stacks to become executable. */ |
| 140 | void *h = dlopen ("tst-execstack-mod.so", RTLD_LAZY); |
| 141 | if (h == NULL) |
| 142 | { |
| 143 | printf ("cannot load: %s\n", dlerror ()); |
| 144 | return allow_execstack; |
| 145 | } |
| 146 | |
| 147 | f = dlsym (h, "tryme"); |
| 148 | if (f == NULL) |
| 149 | { |
| 150 | printf ("symbol not found: %s\n", dlerror ()); |
| 151 | return 1; |
| 152 | } |
| 153 | |
| 154 | /* Test if that really made our stack executable. |
| 155 | The `tryme' function should crash if not. */ |
| 156 | |
| 157 | (*((void (*) (void)) f)) (); |
| 158 | |
| 159 | print_maps (); |
| 160 | |
| 161 | #if USE_PTHREADS |
| 162 | ret = pthread_getattr_np (me, &attr); |
| 163 | if (ret) |
| 164 | { |
| 165 | printf ("after execstack: pthread_getattr_np returned error: %s\n", |
| 166 | strerror (ret)); |
| 167 | return 1; |
| 168 | } |
| 169 | |
| 170 | ret = pthread_attr_getstack (&attr, &new_stack_addr, &stack_size); |
| 171 | if (ret) |
| 172 | { |
| 173 | printf ("after execstack: pthread_attr_getstack returned error: %s\n", |
| 174 | strerror (ret)); |
| 175 | return 1; |
| 176 | } |
| 177 | |
| 178 | # if _STACK_GROWS_DOWN |
| 179 | new_stack_addr += stack_size; |
| 180 | # else |
| 181 | new_stack_addr -= stack_size; |
| 182 | # endif |
| 183 | |
| 184 | /* It is possible that the dlopen'd module may have been mmapped just below |
| 185 | the stack. The stack size is taken as MIN(stack rlimit size, end of last |
| 186 | vma) in pthread_getattr_np. If rlimit is set high enough, it is possible |
| 187 | that the size may have changed. A subsequent call to |
| 188 | pthread_attr_getstack returns the size and (bottom - size) as the |
| 189 | stacksize and stackaddr respectively. If the size changes due to the |
| 190 | above, then both stacksize and stackaddr can change, but the stack bottom |
| 191 | should remain the same, which is computed as stackaddr + stacksize. */ |
| 192 | if (old_stack_addr != new_stack_addr) |
| 193 | { |
| 194 | printf ("Stack end changed, old: %p, new: %p\n", |
| 195 | old_stack_addr, new_stack_addr); |
| 196 | return 1; |
| 197 | } |
| 198 | printf ("Stack address remains the same: %p\n", old_stack_addr); |
| 199 | #endif |
| 200 | |
| 201 | /* Test that growing the stack region gets new executable pages too. */ |
| 202 | deeper ((void (*) (void)) f); |
| 203 | |
| 204 | print_maps (); |
| 205 | |
| 206 | #if USE_PTHREADS |
| 207 | /* Test that a fresh thread now gets an executable stack. */ |
| 208 | { |
| 209 | pthread_t th; |
| 210 | int rc = pthread_create (&th, NULL, &tryme_thread, f); |
| 211 | if (rc) |
| 212 | error (1, rc, "pthread_create"); |
| 213 | } |
| 214 | |
| 215 | puts ("threads go"); |
| 216 | /* The existing threads' stacks should have been changed. |
| 217 | Let them run to test it. */ |
| 218 | pthread_barrier_wait (&go_barrier); |
| 219 | |
| 220 | pthread_exit ((void *) (long int) (! allow_execstack)); |
| 221 | #endif |
| 222 | |
| 223 | return ! allow_execstack; |
| 224 | } |
| 225 | |
| 226 | static void |
| 227 | deeper (void (*f) (void)) |
| 228 | { |
| 229 | char stack[1100 * 1024]; |
| 230 | memfrob (stack, sizeof stack); |
| 231 | (*f) (); |
| 232 | memfrob (stack, sizeof stack); |
| 233 | } |
| 234 | |
| 235 | |
| 236 | #define TEST_FUNCTION do_test () |
| 237 | #include "../test-skeleton.c" |