]> git.ozlabs.org Git - ccan/blob - ccan/ptr_valid/ptr_valid.c
ptr_valid: fix spurious SIGINT under lldb on MacOS
[ccan] / ccan / ptr_valid / ptr_valid.c
1 // Licensed under BSD-MIT: See LICENSE.
2 #include "ptr_valid.h"
3 #include <sys/types.h>
4 #include <sys/wait.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7 #include <ccan/noerr/noerr.h>
8 #include <unistd.h>
9 #include <errno.h>
10 #include <assert.h>
11 #include <string.h>
12
13 #if HAVE_PROC_SELF_MAPS
14 static char *grab(const char *filename)
15 {
16         int ret, fd;
17         size_t max = 16384, s = 0;
18         char *buffer;
19
20         fd = open(filename, O_RDONLY);
21         if (fd < 0)
22                 return NULL;
23
24         buffer = malloc(max+1);
25         if (!buffer)
26                 goto close;
27
28         while ((ret = read(fd, buffer + s, max - s)) > 0) {
29                 s += ret;
30                 if (s == max) {
31                         buffer = realloc(buffer, max*2+1);
32                         if (!buffer)
33                                 goto close;
34                         max *= 2;
35                 }
36         }
37         if (ret < 0)
38                 goto free;
39
40         close(fd);
41         buffer[s] = '\0';
42         return buffer;
43
44 free:
45         free(buffer);
46 close:
47         close_noerr(fd);
48         return NULL;
49 }
50
51 static char *skip_line(char *p)
52 {
53         char *nl = strchr(p, '\n');
54         if (!nl)
55                 return NULL;
56         return nl + 1;
57 }
58
59 static struct ptr_valid_map *add_map(struct ptr_valid_map *map,
60                                      unsigned int *num,
61                                      unsigned int *max,
62                                      unsigned long start, unsigned long end, bool is_write)
63 {
64         if (*num == *max) {
65                 *max *= 2;
66                 map = realloc(map, sizeof(*map) * *max);
67                 if (!map)
68                         return NULL;
69         }
70         map[*num].start = (void *)start;
71         map[*num].end = (void *)end;
72         map[*num].is_write = is_write;
73         (*num)++;
74         return map;
75 }
76
77 static struct ptr_valid_map *get_proc_maps(unsigned int *num)
78 {
79         char *buf, *p;
80         struct ptr_valid_map *map;
81         unsigned int max = 16;
82
83         buf = grab("/proc/self/maps");
84         if (!buf) {
85                 *num = 0;
86                 return NULL;
87         }
88
89         map = malloc(sizeof(*map) * max);
90         if (!map)
91                 goto free_buf;
92
93         *num = 0;
94         for (p = buf; p && *p; p = skip_line(p)) {
95                 unsigned long start, end;
96                 char *endp;
97
98                 /* Expect '<start-in-hex>-<end-in-hex> rw... */
99                 start = strtoul(p, &endp, 16);
100                 if (*endp != '-')
101                         goto malformed;
102                 end = strtoul(endp+1, &endp, 16);
103                 if (*endp != ' ')
104                         goto malformed;
105
106                 endp++;
107                 if (endp[0] != 'r' && endp[0] != '-')
108                         goto malformed;
109                 if (endp[1] != 'w' && endp[1] != '-')
110                         goto malformed;
111
112                 /* We only add readable mappings. */
113                 if (endp[0] == 'r') {
114                         map = add_map(map, num, &max, start, end,
115                                       endp[1] == 'w');
116                         if (!map)
117                                 goto free_buf;
118                 }
119         }
120
121         free(buf);
122         return map;
123
124
125 malformed:
126         free(map);
127 free_buf:
128         free(buf);
129         *num = 0;
130         return NULL;
131 }
132 #else
133 static struct ptr_valid_map *get_proc_maps(unsigned int *num)
134 {
135         *num = 0;
136         return NULL;
137 }
138 #endif
139
140 static bool check_with_maps(struct ptr_valid_batch *batch,
141                             const char *p, size_t size, bool is_write)
142 {
143         unsigned int i;
144
145         for (i = 0; i < batch->num_maps; i++) {
146                 if (p >= batch->maps[i].start && p < batch->maps[i].end) {
147                         /* Overlap into other maps?  Recurse with remainder. */
148                         if (p + size > batch->maps[i].end) {
149                                 size_t len = p + size - batch->maps[i].end;
150                                 if (!check_with_maps(batch, batch->maps[i].end,
151                                                      len, is_write))
152                                         return false;
153                         }
154                         return !is_write || batch->maps[i].is_write;
155                 }
156         }
157         return false;
158 }
159
160 static void finish_child(struct ptr_valid_batch *batch)
161 {
162         close(batch->to_child);
163         close(batch->from_child);
164         while (waitpid(batch->child_pid, NULL, 0) < 0 && errno == EINTR);
165         batch->child_pid = 0;
166 }
167
168 static bool child_alive(struct ptr_valid_batch *batch)
169 {
170         return batch->child_pid != 0;
171 }
172
173 static void run_child(int infd, int outfd)
174 {
175         volatile char *p;
176
177         /* This is how we expect to exit. */
178         while (read(infd, &p, sizeof(p)) == sizeof(p)) {
179                 size_t i, size;
180                 bool is_write;
181                 char ret = 0;
182
183                 /* This is weird. */
184                 if (read(infd, &size, sizeof(size)) != sizeof(size))
185                         exit(1);
186                 if (read(infd, &is_write, sizeof(is_write)) != sizeof(is_write))
187                         exit(2);
188
189                 for (i = 0; i < size; i++) {
190                         ret = p[i];
191                         if (is_write)
192                                 p[i] = ret;
193                 }
194
195                 /* If we're still here, the answer is "yes". */
196                 if (write(outfd, &ret, 1) != 1)
197                         exit(3);
198         }
199         exit(0);
200 }
201
202 static bool create_child(struct ptr_valid_batch *batch)
203 {
204         int outpipe[2], inpipe[2];
205
206         if (pipe(outpipe) != 0)
207                 return false;
208         if (pipe(inpipe) != 0)
209                 goto close_outpipe;
210
211         fflush(stdout);
212         batch->child_pid = fork();
213         if (batch->child_pid == 0) {
214                 close(outpipe[1]);
215                 close(inpipe[0]);
216                 run_child(outpipe[0], inpipe[1]);
217         }
218
219         if (batch->child_pid == -1)
220                 goto cleanup_pid;
221
222         close(outpipe[0]);
223         close(inpipe[1]);
224
225         batch->to_child = outpipe[1];
226         batch->from_child = inpipe[0];
227         return true;
228
229 cleanup_pid:
230         batch->child_pid = 0;
231         close_noerr(inpipe[0]);
232         close_noerr(inpipe[1]);
233 close_outpipe:
234         close_noerr(outpipe[0]);
235         close_noerr(outpipe[1]);
236         return false;
237 }
238
239 static bool check_with_child(struct ptr_valid_batch *batch,
240                              const void *p, size_t size, bool is_write)
241 {
242         char ret;
243
244         if (!child_alive(batch)) {
245                 if (!create_child(batch))
246                         return false;
247         }
248
249         if (write(batch->to_child, &p, sizeof(p))
250             + write(batch->to_child, &size, sizeof(size))
251             + write(batch->to_child, &is_write, sizeof(is_write))
252             != sizeof(p) + sizeof(size) + sizeof(is_write)) {
253                 finish_child(batch);
254                 errno = EFAULT;
255                 return false;
256         }
257
258         if (read(batch->from_child, &ret, sizeof(ret)) != sizeof(ret)) {
259                 finish_child(batch);
260                 errno = EFAULT;
261                 return false;
262         }
263         return true;
264 }
265
266 /* msync seems most well-defined test, but page could be mapped with
267  * no permissions, and can't distiguish readonly from writable. */
268 bool ptr_valid_batch(struct ptr_valid_batch *batch,
269                      const void *p, size_t alignment, size_t size, bool write)
270 {
271         char *start, *end;
272         bool ret;
273
274         if ((intptr_t)p & (alignment - 1))
275                 return false;
276
277         start = (void *)((intptr_t)p & ~(getpagesize() - 1));
278         end = (void *)(((intptr_t)p + size - 1) & ~(getpagesize() - 1));
279
280         /* We cache single page hits. */
281         if (start == end) {
282                 if (batch->last && batch->last == start)
283                         return batch->last_ok;
284         }
285
286         if (batch->num_maps)
287                 ret = check_with_maps(batch, p, size, write);
288         else
289                 ret = check_with_child(batch, p, size, write);
290
291         if (start == end) {
292                 batch->last = start;
293                 batch->last_ok = ret;
294         }
295
296         return ret;
297 }
298
299 bool ptr_valid_batch_string(struct ptr_valid_batch *batch, const char *p)
300 {
301         while (ptr_valid_batch(batch, p, 1, 1, false)) {
302                 if (*p == '\0')
303                         return true;
304                 p++;
305         }
306         return false;
307 }
308
309 bool ptr_valid(const void *p, size_t alignment, size_t size, bool write)
310 {
311         bool ret;
312         struct ptr_valid_batch batch;
313         if (!ptr_valid_batch_start(&batch))
314                 return false;
315         ret = ptr_valid_batch(&batch, p, alignment, size, write);
316         ptr_valid_batch_end(&batch);
317         return ret;
318 }
319
320 bool ptr_valid_string(const char *p)
321 {
322         bool ret;
323         struct ptr_valid_batch batch;
324         if (!ptr_valid_batch_start(&batch))
325                 return false;
326         ret = ptr_valid_batch_string(&batch, p);
327         ptr_valid_batch_end(&batch);
328         return ret;
329 }
330
331 bool ptr_valid_batch_start(struct ptr_valid_batch *batch)
332 {
333         batch->child_pid = 0;
334         batch->maps = get_proc_maps(&batch->num_maps);
335         batch->last = NULL;
336         return true;
337 }
338
339 void ptr_valid_batch_end(struct ptr_valid_batch *batch)
340 {
341         if (child_alive(batch))
342                 finish_child(batch);
343         free(batch->maps);
344 }