67f457bce3b90dd6cdf406b33e0ec4ffd8de0834
[ccan] / ccan / altstack / altstack.c
1 /* Licensed under Apache License v2.0 - see LICENSE file for details */
2 #include "config.h"
3 #include "altstack.h"
4
5 #include <assert.h>
6 #include <errno.h>
7 #include <setjmp.h>
8 #include <signal.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <sys/mman.h>
13
14 static __thread char ebuf[ALTSTACK_ERR_MAXLEN];
15 static __thread unsigned elen;
16
17 #define bang(x)                                                 \
18         (elen += snprintf(ebuf + elen, sizeof(ebuf) - elen,     \
19                  "%s(altstack@%d) %s%s%s",                      \
20                  elen  ? "; " : "", __LINE__, (x),              \
21                  errno ? ": " : "", errno ? strerror(errno) : ""))
22
23 void altstack_perror(void)
24 {
25         fprintf(stderr, "%s\n", ebuf);
26 }
27
28 char *altstack_geterr(void)
29 {
30         return ebuf;
31 }
32
33 static __thread jmp_buf jmp;
34
35 static void segvjmp(int signum)
36 {
37         longjmp(jmp, 1);
38 }
39
40 static __thread void *rsp_save_[2];
41 static __thread rlim_t max_;
42
43 rlim_t altstack_max(void) {
44         return max_;
45 }
46
47 static ptrdiff_t rsp_save(unsigned i) {
48         assert(i < 2);
49         asm volatile ("movq %%rsp, %0" : "=g" (rsp_save_[i]));
50         return (char *) rsp_save_[0] - (char *) rsp_save_[i];
51 }
52
53 void altstack_rsp_save(void) {
54         rsp_save(0);
55 }
56
57 ptrdiff_t altstack_used(void) {
58         return rsp_save(1);
59 }
60
61 static __thread void *(*fn_)(void *);
62 static __thread void *arg_, *out_;
63
64 int altstack(rlim_t max, void *(*fn)(void *), void *arg, void **out)
65 {
66         long pgsz = sysconf(_SC_PAGESIZE);
67         int ret = -1, undo = 0;
68         char *m;
69         struct rlimit rl_save;
70         struct sigaction sa_save;
71         int errno_save;
72
73         assert(max > 0 && fn);
74         #define ok(x, y) ({ long __r = (long) (x); if (__r == -1) { bang(#x); if (y) goto out; } __r; })
75
76         fn_  = fn;
77         arg_ = arg;
78         out_ = 0;
79         max_ = max;
80         ebuf[elen = 0] = '\0';
81         if (out) *out = 0;
82
83         // if the first page below the mapping is in use, we get max-pgsz usable bytes
84         // add pgsz to max to guarantee at least max usable bytes
85         max += pgsz;
86
87         ok(getrlimit(RLIMIT_STACK, &rl_save), 1);
88         ok(setrlimit(RLIMIT_STACK, &(struct rlimit) { max_, rl_save.rlim_max }), 1);
89         undo++;
90
91         ok(m = mmap(0, max, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN|MAP_NORESERVE, -1, 0), 1);
92         undo++;
93
94         if (setjmp(jmp) == 0) {
95                 unsigned char sigstk[MINSIGSTKSZ];
96                 stack_t ss = { .ss_sp = sigstk, .ss_size = sizeof(sigstk) };
97                 struct sigaction sa = { .sa_handler = segvjmp, .sa_flags = SA_NODEFER|SA_RESETHAND|SA_ONSTACK };
98
99                 ok(sigaltstack(&ss, 0), 1);
100                 undo++;
101
102                 sigemptyset(&sa.sa_mask);
103                 ok(sigaction(SIGSEGV, &sa, &sa_save), 1);
104                 undo++;
105
106                 asm volatile (
107                         "mov %%rsp, %%r10\n\t"
108                         "mov %1, %%rsp\n\t"
109                         "sub $8, %%rsp\n\t"
110                         "push %%r10"
111                         : "=r" (rsp_save_[0]) : "0" (m + max) : "r10");
112                 out_ = fn_(arg_);
113                 asm volatile ("pop %rsp");
114                 ret = 0;
115                 if (out) *out = out_;
116         }
117         else {
118                 errno = 0;
119                 bang("SIGSEGV caught");
120                 errno = EOVERFLOW;
121         }
122
123 out:
124         errno_save = errno;
125
126         switch (undo) {
127         case 4:
128                 ok(sigaction(SIGSEGV, &sa_save, 0), 0);
129         case 3:
130                 ok(sigaltstack(&(stack_t) { .ss_flags = SS_DISABLE }, 0), 0);
131         case 2:
132                 ok(munmap(m, max), 0);
133         case 1:
134                 ok(setrlimit(RLIMIT_STACK, &rl_save), 0);
135         }
136
137         if (errno_save)
138                 errno = errno_save;
139         return !ret && elen ? 1 : ret;
140 }