1 /* $OpenBSD: noexec.c,v 1.20 2019/05/10 15:57:39 visa Exp $ */ 2 3 /* 4 * Copyright (c) 2002,2003 Michael Shalayeff 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR OR HIS RELATIVES BE LIABLE FOR ANY DIRECT, 22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 * SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 27 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 28 * THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include <sys/param.h> 32 #include <sys/mman.h> 33 #include <unistd.h> 34 #include <stdio.h> 35 #include <signal.h> 36 #include <string.h> 37 #include <stdlib.h> 38 #include <limits.h> 39 #include <errno.h> 40 #include <err.h> 41 42 volatile sig_atomic_t fail; 43 int page_size; 44 char label[64] = "non-exec "; 45 46 #define PAD 64*1024 47 #define MAXPAGESIZE 16384 48 #define TESTSZ 256 /* assuming the testfly() will fit */ 49 u_int64_t data[(PAD + TESTSZ + PAD + MAXPAGESIZE) / 8] = { 0 }; 50 u_int64_t bss[(PAD + TESTSZ + PAD + MAXPAGESIZE) / 8]; 51 52 void testfly(void); 53 54 static void 55 fdcache(void *p, size_t size) 56 { 57 #ifdef __hppa__ 58 __asm volatile( /* XXX this hardcodes the TESTSZ */ 59 "fdc,m %1(%0)\n\t" 60 "fdc,m %1(%0)\n\t" 61 "fdc,m %1(%0)\n\t" 62 "fdc,m %1(%0)\n\t" 63 "fdc,m %1(%0)\n\t" 64 "fdc,m %1(%0)\n\t" 65 "fdc,m %1(%0)\n\t" 66 "fdc,m %1(%0)" 67 : "+r" (p) : "r" (32)); 68 #endif 69 #ifdef __sparc64__ 70 char *s = p; 71 int i; 72 73 for (i = 0; i < TESTSZ; i += 8) 74 __asm volatile("flush %0" : : "r" (s + i) : "memory"); 75 #endif 76 } 77 78 static void 79 sigsegv(int sig, siginfo_t *sip, void *scp) 80 { 81 _exit(fail); 82 } 83 84 static int 85 noexec(void *p, size_t size) 86 { 87 fail = 0; 88 printf("%s: execute\n", label); 89 fflush(stdout); 90 ((void (*)(void))p)(); 91 92 return (1); 93 } 94 95 static int 96 noexec_mprotect(void *p, size_t size) 97 { 98 99 /* here we must fail on segv since we said it gets executable */ 100 fail = 1; 101 if (mprotect(p, size, PROT_READ|PROT_EXEC) < 0) 102 err(1, "mprotect 1"); 103 printf("%s: execute\n", label); 104 fflush(stdout); 105 ((void (*)(void))p)(); 106 107 /* here we are successful on segv and fail if it still executes */ 108 fail = 0; 109 if (mprotect(p, size, PROT_READ) < 0) 110 err(1, "mprotect 2"); 111 printf("%s: catch a signal\n", label); 112 fflush(stdout); 113 ((void (*)(void))p)(); 114 115 return (1); 116 } 117 118 static void * 119 getaddr(void *a) 120 { 121 void *ret; 122 123 /* 124 * Compile with -fno-inline to get reasonable result when comparing 125 * local variable address with caller's stack. 126 */ 127 if ((void *)&ret < a) 128 ret = (void *)((u_long)&ret - 4 * page_size); 129 else 130 ret = (void *)((u_long)&ret + 4 * page_size); 131 132 return (void *)((u_long)ret & ~(page_size - 1)); 133 } 134 135 static int 136 noexec_mmap(void *p, size_t size) 137 { 138 memcpy(p + page_size * 1, p, page_size); 139 memcpy(p + page_size * 2, p, page_size); 140 fdcache(p + page_size * 1, TESTSZ); 141 fdcache(p + page_size * 2, TESTSZ); 142 if (mprotect(p, size + 2 * page_size, PROT_READ|PROT_EXEC) != 0) 143 err(1, "mprotect"); 144 145 /* here we must fail on segv since we said it gets executable */ 146 fail = 1; 147 148 printf("%s: execute #1\n", label); 149 fflush(stdout); 150 ((void (*)(void))p)(); 151 152 /* unmap the first page to see that the higher page is still exec */ 153 if (munmap(p, page_size) < 0) 154 err(1, "munmap"); 155 156 p += page_size; 157 printf("%s: execute #2\n", label); 158 fflush(stdout); 159 ((void (*)(void))p)(); 160 161 /* unmap the last page to see that the lower page is still exec */ 162 if (munmap(p + page_size, page_size) < 0) 163 err(1, "munmap"); 164 165 printf("%s: execute #3\n", label); 166 fflush(stdout); 167 ((void (*)(void))p)(); 168 169 return (0); 170 } 171 172 static void 173 usage(void) 174 { 175 extern char *__progname; 176 fprintf(stderr, "Usage: %s [-s <size>] -[TDBHS] [-p] [-m]\n", 177 __progname); 178 exit(2); 179 } 180 181 int 182 main(int argc, char *argv[]) 183 { 184 u_int64_t stack[TESTSZ/8]; /* assuming the testfly() will fit */ 185 struct sigaction sa; 186 int (*func)(void *, size_t); 187 size_t size; 188 char *ep; 189 void *p, *ptr; 190 int pflags, ch; 191 192 if ((page_size = sysconf(_SC_PAGESIZE)) < 0) 193 err(1, "sysconf"); 194 195 setvbuf(stdout, NULL, _IONBF, 0); 196 setvbuf(stderr, NULL, _IONBF, 0); 197 198 p = NULL; 199 pflags = MAP_PRIVATE|MAP_ANON|MAP_FIXED; 200 func = &noexec; 201 size = TESTSZ; 202 while ((ch = getopt(argc, argv, "TDBHSmps:")) != -1) { 203 if (p == NULL) { 204 switch (ch) { 205 case 'T': 206 p = &testfly; 207 pflags &=~ MAP_FIXED; 208 (void) strlcat(label, "text", sizeof(label)); 209 continue; 210 case 'D': 211 p = &data[(PAD + page_size) / 8]; 212 p = (void *)((long)p & ~(page_size - 1)); 213 (void) strlcat(label, "data", sizeof(label)); 214 continue; 215 case 'B': 216 p = &bss[(PAD + page_size) / 8]; 217 p = (void *)((long)p & ~(page_size - 1)); 218 (void) strlcat(label, "bss", sizeof(label)); 219 continue; 220 case 'H': 221 p = malloc(size + 2 * page_size); 222 if (p == NULL) 223 err(2, "malloc"); 224 p += page_size; 225 p = (void *)((long)p & ~(page_size - 1)); 226 (void) strlcat(label, "heap", sizeof(label)); 227 continue; 228 case 'S': 229 p = getaddr(&stack); 230 pflags |= MAP_STACK; 231 (void) strlcat(label, "stack", sizeof(label)); 232 continue; 233 case 's': /* only valid for heap and size */ 234 size = strtoul(optarg, &ep, 0); 235 if (size > ULONG_MAX) 236 errno = ERANGE; 237 if (errno) 238 err(1, "invalid size: %s", optarg); 239 if (*ep) 240 errx(1, "invalid size: %s", optarg); 241 continue; 242 } 243 } 244 switch (ch) { 245 case 'm': 246 if (p) { 247 (void) strlcat(label, "-mmap", sizeof(label)); 248 } else { 249 pflags = MAP_ANON; 250 func = &noexec_mmap; 251 (void) strlcat(label, "mmap", sizeof(label)); 252 } 253 ptr = mmap(p, size + 2 * page_size, 254 PROT_READ|PROT_WRITE, pflags, -1, 0LL); 255 if (ptr == MAP_FAILED) { 256 err(1, "mmap: addr %p, len %zu, prot %d, " 257 "flags %d, fd %d, offset %lld", 258 p, size + 2 * page_size, 259 PROT_READ|PROT_WRITE, pflags, -1, 0LL); 260 } 261 p = ptr; 262 break; 263 case 'p': 264 func = &noexec_mprotect; 265 (void) strlcat(label, "-mprotect", sizeof(label)); 266 break; 267 default: 268 usage(); 269 } 270 } 271 argc -= optind; 272 argv += optind; 273 274 if (argc > 0) 275 usage(); 276 277 if (p == NULL) 278 exit(2); 279 280 sa.sa_sigaction = &sigsegv; 281 sa.sa_flags = SA_SIGINFO; 282 sigemptyset(&sa.sa_mask); 283 sigaction(SIGSEGV, &sa, NULL); 284 285 if (p != &testfly) { 286 memcpy(p, &testfly, TESTSZ); 287 fdcache(p, size); 288 } 289 290 exit((*func)(p, size)); 291 } 292