source: trunk/base/src/darwintracelib1.0/darwintrace.c @ 136621

Last change on this file since 136621 was 136398, checked in by cal@…, 5 years ago

base: darwintrace: don't use fread(3) and fwrite(3)

fread(3) and fwrite(3) can both be interrupted by signals, return a short
read/write or error and set errno=EINTR. Since there is no way to find out
whether the interrupted call has already read or written partial data, these
calls can never be re-called correctly without possible corruption. This is
a fundamental problem in the API and the implementation on OS X.

To avoid this, use write(2) and read(2) instead.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 30.4 KB
Line 
1/*
2 * Copyright (c) 2005 Apple Inc. All rights reserved.
3 * Copyright (c) 2005-2006 Paul Guyot <pguyot@kallisys.net>,
4 * All rights reserved.
5 * Copyright (c) 2006-2013 The MacPorts Project
6 *
7 * $Id: darwintrace.c 136398 2015-05-15 22:46:22Z cal@macports.org $
8 *
9 * @APPLE_BSD_LICENSE_HEADER_START@
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *
15 * 1.  Redistributions of source code must retain the above copyright
16 *     notice, this list of conditions and the following disclaimer.
17 * 2.  Redistributions in binary form must reproduce the above copyright
18 *     notice, this list of conditions and the following disclaimer in the
19 *     documentation and/or other materials provided with the distribution.
20 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
21 *     its contributors may be used to endorse or promote products derived
22 *     from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
25 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
28 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 * @APPLE_BSD_LICENSE_HEADER_END@
36 */
37
38#define DARWINTRACE_USE_PRIVATE_API 1
39#include "darwintrace.h"
40#include "sandbox_actions.h"
41
42#ifdef HAVE_LIBKERN_OSATOMIC_H
43#include <libkern/OSAtomic.h>
44#endif
45
46#include <errno.h>
47#include <inttypes.h>
48#include <pthread.h>
49#include <string.h>
50#include <sys/attr.h>
51#include <sys/param.h>
52#include <sys/socket.h>
53#include <sys/stat.h>
54#include <sys/syscall.h>
55#include <sys/types.h>
56#include <sys/un.h>
57#include <unistd.h>
58
59#if __DARWIN_64_BIT_INO_T
60#define STATSYSNUM SYS_stat64
61#define LSTATSYSNUM SYS_lstat64
62#else
63#define STATSYSNUM SYS_stat
64#define LSTATSYSNUM SYS_lstat
65#endif
66
67#ifndef HAVE_STRLCPY
68/* Define strlcpy if it's not available. */
69size_t strlcpy(char *dst, const char *src, size_t size) {
70        size_t result = strlen(src);
71        if (size > 0) {
72                size_t copylen = size - 1;
73                if (copylen > result) {
74                        copylen = result;
75                }
76                memcpy(dst, src, copylen);
77                dst[copylen] = 0;
78        }
79        return result;
80}
81#endif
82
83#include "../pextlib1.0/strlcat.c"
84
85// Global Variables
86/**
87 * PID of the process darwintrace was last used in. This is used to detect
88 * forking and opening a new connection to the control socket in the child
89 * process. Not doing so would potentially cause two processes writing to the
90 * same socket.
91 */
92pid_t __darwintrace_pid = (pid_t) - 1;
93
94/**
95 * Helper variable containing the number of the darwintrace socket, iff the
96 * close(2) syscall should be allowed to close it. Used by \c
97 * __darwintrace_close.
98 */
99volatile int __darwintrace_close_sock = -1;
100
101/**
102 * Debug socket. Will be initialized by a constructor function.
103 */
104FILE *__darwintrace_stderr = NULL;
105
106static inline void __darwintrace_log_op(const char *op, const char *path);
107static void __darwintrace_setup_tls() __attribute__((constructor));
108static char *__send(const char *buf, uint32_t len, int answer);
109
110/**
111 * pthread_key_ts for the pthread_t returned by pthread_self() and the
112 * darwintrace socket to ensure the socket is only used from a single thread.
113 */
114static pthread_key_t tid_key;
115// The sock key is needed in close(2) and dup2(2)
116pthread_key_t sock_key;
117
118/**
119 * size of the communication buffer
120 */
121#define BUFFER_SIZE 4096
122
123/**
124 * Variable holding the sandbox bounds in the following format:
125 *  <filemap>       :: (<spec> '\0')+ '\0'
126 *  <spec>          :: <path> '\0' <operation> <additional_data>?
127 *  <operation>     :: '0' | '1' | '2'
128 * where
129 *  0: allow
130 *  1: map the path to the one given in additional_data (currently unsupported)
131 *  2: check for a dependency using the socket
132 *  3: deny access to the path and stop processing
133 */
134static char *filemap;
135
136/**
137 * Setup method called as constructor to set up thread-local storage for the
138 * thread id and the darwintrace socket.
139 */
140static void __darwintrace_setup_tls() {
141        if (0 != (errno = pthread_key_create(&tid_key, NULL))) {
142                perror("darwintrace: pthread_key_create");
143                abort();
144        }
145        if (0 != (errno = pthread_key_create(&sock_key, NULL))) {
146                perror("darwintrace: pthread_key_create");
147                abort();
148        }
149}
150
151/**
152 * Convenience getter function for the thread ID
153 */
154/*
155static inline pthread_t __darwintrace_tid() {
156        return (pthread_t) pthread_getspecific(tid_key);
157}
158*/
159
160/**
161 * Convenience setter function for the thread-local darwintrace socket
162 */
163static inline void __darwintrace_tid_set() {
164        if (0 != (errno = pthread_setspecific(tid_key, (const void *) pthread_self()))) {
165                perror("darwintrace: pthread_setspecific");
166                abort();
167        }
168}
169
170/**
171 * Return false if str doesn't begin with prefix, true otherwise. Note that
172 * this is not a simple string comparison, but works on a path component level.
173 * A prefix of /var/tmp will not match a string of /var/tmpfoo.
174 */
175static inline bool __darwintrace_pathbeginswith(const char *str, const char *prefix) {
176        char s;
177        char p;
178
179        /* '/' is the allow all wildcard */
180        if (prefix[0] == '\0' || (prefix[0] == '/' && prefix[1] == '\0')) {
181                return 1;
182        }
183
184        do {
185                s = *str++;
186                p = *prefix++;
187        } while (p && (p == s));
188        return (p == '\0' && (s == '/' || s == '\0'));
189}
190
191/*
192 * Data structures and functions to iterate over the filemap received from
193 * tracelib code.
194 */
195
196/**
197 * \c filemap_iterator_t is an (opaque) iterator type that keeps the state
198 * required to iterate through the filemap. Create a new filemap_iterator_t on
199 * stack, initialize it using \c __darwintrace_filemap_iterator_init and pass
200 * it to \c __darwintrace_filemap_iter to iterate over the filemap.
201 */
202typedef struct filemap_iterator {
203        char *next;
204} filemap_iterator_t;
205
206/**
207 * Initialize a given \c filemap_iterator_t. Calling this function again will
208 * rewind the iterator.
209 *
210 * \param[in] it pointer to the iterator to be initialized
211 */
212static inline void __darwintrace_filemap_iterator_init(filemap_iterator_t *it) {
213        it->next = filemap;
214}
215
216/**
217 * Iterate through the filemap passed from tracelib code. Call this multiple
218 * times with the same iterator object until it returns \c NULL to iterate
219 * through the filemap.
220 *
221 * \param[out] command location for the command specified for this filemap
222 *                     entry
223 * \param[in]  it pointer to a \c filemap_iterator_t keeping the state of this
224 *                iteration
225 * \return string containing the path this filemap entry corresponds to, or \c
226 *         NULL if the end of the filemap was reached
227 */
228static inline char *__darwintrace_filemap_iter(char *command, filemap_iterator_t *it) {
229        enum { PATH, COMMAND, DONE } state = PATH;
230        char *t;
231        char *path;
232
233        if (it == NULL || it->next == NULL || *it->next == '\0') {
234                return NULL;
235        }
236
237        path = t = it->next;
238
239        /* advance the cursor: if the number after the string is not 1, there's no
240         * path behind it and we can advance by strlen(t) + 3. If it is 1, make
241         * sure to skip the path, too.
242         */
243        state = PATH;
244        while (state != DONE) {
245                switch (state) {
246                        case DONE:
247                                /* unreachable */
248                                break;
249                        case PATH:
250                                if (*t == '\0') {
251                                        state = COMMAND;
252                                }
253                                break;
254                        case COMMAND:
255                                *command = *t;
256                                if (*t == 1) {
257                                        fprintf(stderr, "darwintrace: unsupported state REPLACEPATH in dfa in " __FILE__ ":%d\n", __LINE__);
258                                        abort();
259                                }
260                                state = DONE;
261                                /* the byte after the status code is '\0', if the status code
262                                 * isn't 1 (which is no longer supported) */
263                                t++;
264                                break;
265                }
266                t++;
267        }
268
269        it->next = t;
270        return path;
271}
272
273/**
274 * Request sandbox boundaries from tracelib (the MacPorts base-controlled side
275 * of the trace setup) and store it.
276 */
277static void __darwintrace_get_filemap() {
278        char *newfilemap;
279#if DARWINTRACE_DEBUG && 0
280        filemap_iterator_t it;
281        char *path, command;
282#endif
283
284#if defined(HAVE_OSATOMICCOMPAREANDSWAPPTR)
285#       define CAS(old, new, mem) OSAtomicCompareAndSwapPtr(old, new, (void * volatile *) (mem))
286#elif defined(__LP64__)
287#       ifdef HAVE_OSATOMICCOMPAREANDSWAP64
288#               define CAS(old, new, mem) OSAtomicCompareAndSwap64((int64_t) (old), (int64_t) (new), (volatile int64_t *) (mem))
289#       else
290#               error "No 64-bit compare and swap primitive available on 64-bit OS."
291#       endif
292#else
293#       ifdef HAVE_OSATOMICCOMPAREANDSWAP32
294#               define CAS(old, new, mem) OSAtomicCompareAndSwap32((int32_t) (old), (int32_t) (new), (volatile int32_t *) (mem))
295#       else
296#               error "No 32-bit compare and swap primitive available."
297#       endif
298#endif
299
300        /*
301         * ensure we have a filemap present; this might be called simultanously
302         * from multiple threads and needs to work without leaking and in a way
303         * that ensures a filemap has been set before any of the calls return. We
304         * achieve that by using non-blocking synchronization. Blocking
305         * synchronization might be a bad idea, because we never know where this
306         * code is actually called in an application.
307         */
308        newfilemap = NULL;
309        do {
310                free(newfilemap);
311                if (filemap != NULL)
312                        break;
313                newfilemap = __send("filemap\t", 8, 1);
314        } while (!CAS(NULL, newfilemap, &filemap));
315
316#if DARWINTRACE_DEBUG && 0
317        for (__darwintrace_filemap_iterator_init(&it);
318                (path = __darwintrace_filemap_iter(&command, &it));) {
319                debug_printf("filemap: {cmd=%d, path=%s}\n", command, path);
320        }
321#endif
322}
323
324/**
325 * Close the darwintrace socket and set it to \c NULL. Since this uses \c
326 * fclose(3), which internally calls \c close(2), which is intercepted by this
327 * library and this library prevents closing the socket to MacPorts, we use \c
328 * __darwintrace_close_sock to allow closing specific FDs.
329 */
330void __darwintrace_close() {
331        FILE *dtsock = __darwintrace_sock();
332        if (dtsock) {
333                __darwintrace_close_sock = fileno(dtsock);
334                fclose(dtsock);
335                __darwintrace_close_sock = -1;
336                pthread_setspecific(sock_key, NULL);
337        }
338}
339
340/**
341 * Ensures darwintrace is correctly set up by opening a socket connection to
342 * the MacPorts-side of trace mode. Will close an re-open this connection when
343 * called after \c fork(2), i.e. when the current PID doesn't match the one
344 * stored when the function was called last.
345 */
346void __darwintrace_setup() {
347        /*
348         * Check whether this is a child process and we've inherited the socket. We
349         * want to avoid race conditions with our parent process when communicating
350         * with tracelib and thus re-open all sockets, if that's the case. Note
351         * this also applies to threads within the same process, since we really
352         * want to avoid mixing up the results from two calls in different threads
353         * when reading from the socket.
354         */
355
356        /*
357         * if the PID changed, close the current socket (which will force the
358         * following code to re-open it).
359         */
360        if (__darwintrace_pid != (pid_t) -1 && __darwintrace_pid != getpid()) {
361                __darwintrace_close();
362                __darwintrace_pid = (pid_t) -1;
363        }
364
365        /*
366         * We don't need to watch for TID changes, because each thread has thread
367         * local storage for the socket that will contain NULL when the socket has
368         * not been initialized.
369         */
370
371        if (__darwintrace_sock() == NULL) {
372                int sock;
373                FILE *stream;
374                struct sockaddr_un sun;
375
376                __darwintrace_pid = getpid();
377                __darwintrace_tid_set();
378                if (__env_darwintrace_log == NULL) {
379                        fprintf(stderr, "darwintrace: trace library loaded, but DARWINTRACE_LOG not set\n");
380                        abort();
381                }
382
383                if (-1 == (sock = socket(PF_LOCAL, SOCK_STREAM, 0))) {
384                        perror("darwintrace: socket");
385                        abort();
386                }
387
388                if (strlen(__env_darwintrace_log) > sizeof(sun.sun_path) - 1) {
389                        fprintf(stderr, "darwintrace: Can't connect to socket %s: name too long\n", __env_darwintrace_log);
390                        abort();
391                }
392                sun.sun_family = AF_UNIX;
393                strlcpy(sun.sun_path, __env_darwintrace_log, sizeof(sun.sun_path));
394
395                if (-1 == (connect(sock, (struct sockaddr *) &sun, sizeof(sun)))) {
396                        perror("darwintrace: connect");
397                        abort();
398                }
399
400                if (NULL == (stream = fdopen(sock, "a+"))) {
401                        perror("darwintrace: fdopen");
402                        abort();
403                }
404
405                /* store FILE * into thread local storage for the socket */
406                __darwintrace_sock_set(stream);
407
408                /* request sandbox bounds */
409                __darwintrace_get_filemap();
410        }
411}
412
413/**
414 * Send a path to tracelib either given a path, or an FD (where
415 * fcntl(F_GETPATH) will be used).
416 *
417 * \param[in] op the operation (sent as-is to tracelib, should be interpreted
418 *               as command)
419 * \param[in] path the (not necessarily absolute) path to send to tracelib
420 */
421static inline void __darwintrace_log_op(const char *op, const char *path) {
422        uint32_t size;
423        char logbuffer[BUFFER_SIZE];
424
425        size = snprintf(logbuffer, sizeof(logbuffer), "%s\t%s", op, path);
426        // Check if the buffer was short. If it was, discard the message silently,
427        // assuming it isn't important enough to error out.
428        if (size < BUFFER_SIZE) {
429                __send(logbuffer, size, 0);
430        }
431}
432
433/**
434 * Check whether the port currently being installed declares a dependency on
435 * a given file. Communicates with MacPorts tracelib, which uses the registry
436 * database to answer this question. Returns 1, if a dependency was declared,
437 * 0, if the file belongs to a port and no dependency was declared and -1 if
438 * the file isnt't registered to any port.
439 *
440 * \param[in] path the path to send to MacPorts for dependency info
441 * \return 1, if access should be granted, 0, if access should be denied, and
442 *         -1 if MacPorts doesn't know about the file.
443 */
444static int dependency_check(const char *path) {
445#define lstat(y, z) syscall(LSTATSYSNUM, (y), (z))
446        char buffer[BUFFER_SIZE], *p;
447        uint32_t len;
448        int result = 0;
449        struct stat st;
450
451        if (-1 == lstat(path, &st)) {
452                return 1;
453        }
454        if (S_ISDIR(st.st_mode)) {
455                debug_printf("%s is directory\n", path);
456                return 1;
457        }
458
459        len = snprintf(buffer, sizeof(buffer), "dep_check\t%s", path);
460        if (len >= sizeof(buffer)) {
461                fprintf(stderr, "darwintrace: truncating buffer length from %" PRIu32 " to %zu.", len, sizeof(buffer) - 1);
462                len = sizeof(buffer) - 1;
463        }
464        p = __send(buffer, len, 1);
465        if (!p) {
466                fprintf(stderr, "darwintrace: dependency check failed for %s\n", path);
467                abort();
468        }
469
470        switch (*p) {
471                case '+':
472                        result = 1;
473                        break;
474                case '!':
475                        result = 0;
476                        break;
477                case '?':
478                        result = -1;
479                        break;
480                default:
481                        fprintf(stderr, "darwintrace: unexpected answer from tracelib: '%c' (0x%x)\n", *p, *p);
482                        abort();
483                        /*NOTREACHED*/
484        }
485
486        debug_printf("dependency_check: %s returned %d\n", path, result);
487
488        free(p);
489        return result;
490#undef lstat
491}
492
493/**
494 * Helper function to receive a number of bytes from the tracelib communication
495 * socket and deal with any errors that might occur.
496 *
497 * \param[out] buf buffer to hold received data
498 * \param[in]  size number of bytes to read from the socket
499 */
500static void frecv(void *restrict buf, size_t size) {
501        /* We cannot safely use fread(3) here, because we're not in control of the
502         * application's signal handling settings (which means we must assume
503         * SA_RESTART isn't set) and fread(3) may return short without giving us
504         * a way to know how many bytes have actually been read, i.e. without a way
505         * to do the call again. Because of this great API design and
506         * implementation on OS X, we'll just use read(2) here. */
507        int fd = fileno(__darwintrace_sock());
508        size_t count = 0;
509        while (count < size) {
510                ssize_t res = read(fd, buf + count, size - count);
511                if (res < 0) {
512                        if (errno == EINTR) {
513                                continue;
514                        }
515                        perror("darwintrace: read");
516                        abort();
517                }
518
519                if (res == 0) {
520                        fprintf(stderr, "darwintrace: read: end-of-file\n");
521                        abort();
522                }
523
524                count += res;
525        }
526}
527
528/**
529 * Helper function to send a buffer to MacPorts using the tracelib
530 * communication socket and deal with any errors that might occur.
531 *
532 * \param[in] buf buffer to send
533 * \param[in] size number of bytes in the buffer
534 */
535static void fsend(const void *restrict buf, size_t size) {
536        /* We cannot safely use fwrite(3) here, because we're not in control of the
537         * application's signal handling settings (which means we must assume
538         * SA_RESTART isn't set) and fwrite(3) may return short without giving us
539         * a way to know how many bytes have actually been written, i.e. without
540         * a way to do the call again. Because of this great API design and
541         * implementation on OS X, we'll just use write(2) here. */
542        int fd = fileno(__darwintrace_sock());
543        size_t count = 0;
544        while (count < size) {
545                ssize_t res = write(fd, buf + count, size - count);
546                if (res < 0) {
547                        if (errno == EINTR) {
548                                continue;
549                        }
550                        perror("darwintrace: write");
551                        abort();
552                }
553
554                count += res;
555        }
556}
557
558/**
559 * Communication wrapper targeting tracelib. Automatically enforces the on-wire
560 * protocol and supports reading and returning an answer.
561 *
562 * \param[in] buf buffer to send to tracelib
563 * \param[in] len size of the buffer to send
564 * \param[in] answer boolean indicating whether an answer is expected and
565 *                   should be returned
566 * \return allocated answer buffer. Callers should free this buffer. If an
567 *         answer was not requested, \c NULL.
568 */
569static char *__send(const char *buf, uint32_t len, int answer) {
570        fsend(&len, sizeof(len));
571        fsend(buf, len);
572
573        if (!answer) {
574                return NULL;
575        }
576
577        uint32_t recv_len = 0;
578        char *recv_buf;
579
580        frecv(&recv_len, sizeof(recv_len));
581        if (recv_len == 0) {
582                return 0;
583        }
584
585        recv_buf = malloc(recv_len + 1);
586        recv_buf[recv_len] = '\0';
587        frecv(recv_buf, recv_len);
588
589        return recv_buf;
590}
591
592/**
593 * Check a fully normalized path against the current sandbox. Helper function
594 * for __darwintrace_is_in_sandbox; do not use directly.
595 *
596 * \param[in] path the path to be checked; must be absolute and normalized.
597 * \param[in] flags A binary or combination of the following flags:
598 *                  - DT_REPORT: If access to this path is being denied, report
599 *                    it as sandbox violation. Set this for all operations that
600 *                    read file contents or check file attributes. Omit this
601 *                    flag for operations that might only attempt to access
602 *                    a file by chance, such as readdir(3).
603 *                  - DT_ALLOWDIR: Whether to always allow access if the given
604 *                    path references an existing directory. Set this for
605 *                    read operations such as stat(2), omit this for operations
606 *                    that modify directories like rmdir(2) and mkdir(2).
607 * \return \c true if the file is within sandbox bounds, \c false if access
608 *         should be denied
609 */
610static inline bool __darwintrace_sandbox_check(const char *path, int flags) {
611#define lstat(x,y) syscall(LSTATSYSNUM, (x), (y))
612        filemap_iterator_t filemap_it;
613
614        char command;
615        char *t;
616
617        if (path[0] == '/' && path[1] == '\0') {
618                // Always allow access to /. Strange things start to happen if you deny this.
619                return true;
620        }
621
622        if ((flags & DT_ALLOWDIR) > 0) {
623                struct stat st;
624                if (-1 != lstat(path, &st) && S_ISDIR(st.st_mode)) {
625                        return true;
626                }
627        }
628
629        // Iterate over the sandbox bounds and try to find a directive matching this path
630        for (__darwintrace_filemap_iterator_init(&filemap_it);
631                (t = __darwintrace_filemap_iter(&command, &filemap_it));) {
632                if (__darwintrace_pathbeginswith(path, t)) {
633                        switch (command) {
634                                case FILEMAP_ALLOW:
635                                        return true;
636                                case FILEMAP_ASK:
637                                        // ask the socket whether this file is OK
638                                        switch (dependency_check(path)) {
639                                                case 1:
640                                                        return true;
641                                                case -1:
642                                                        // if the file isn't known to MacPorts, allow
643                                                        // access anyway, but report a sandbox violation.
644                                                        // TODO find a better solution
645                                                        if ((flags & DT_REPORT) > 0) {
646                                                                __darwintrace_log_op("sandbox_unknown", path);
647                                                        }
648                                                        return true;
649                                                case 0:
650                                                        // file belongs to a foreign port, deny access
651                                                        if ((flags & DT_REPORT) > 0) {
652                                                                __darwintrace_log_op("sandbox_violation", path);
653                                                        }
654                                                        return false;
655                                        }
656                                case FILEMAP_DENY:
657                                        if ((flags & DT_REPORT) > 0) {
658                                                __darwintrace_log_op("sandbox_violation", path);
659                                        }
660                                        return false;
661                                default:
662                                        fprintf(stderr, "darwintrace: error: unexpected byte in file map: `%x'\n", *t);
663                                        abort();
664                        }
665                }
666        }
667
668        if ((flags & DT_REPORT) > 0) {
669                __darwintrace_log_op("sandbox_violation", path);
670        }
671        return false;
672#undef lstat
673}
674
675/**
676 * Check a path against the current sandbox
677 *
678 * \param[in] path the path to be checked; not necessarily absolute
679 * \param[in] flags A binary or combination of the following flags:
680 *                  - DT_REPORT: If access to this path is being denied, report
681 *                    it as sandbox violation. Set this for all operations that
682 *                    read file contents or check file attributes. Omit this
683 *                    flag for operations that might only attempt to access
684 *                    a file by chance, such as readdir(3).
685 *                  - DT_ALLOWDIR: Whether to always allow access if the given
686 *                    path references an existing directory. Set this for
687 *                    read operations such as stat(2), omit this for operations
688 *                    that modify directories like rmdir(2) and mkdir(2).
689 * \return \c true if the file is within sandbox bounds, \c false if access
690 *         should be denied
691 */
692bool __darwintrace_is_in_sandbox(const char *path, int flags) {
693#define lstat(x, y) syscall(LSTATSYSNUM, (x), (y))
694#define readlink(x,y,z) syscall(SYS_readlink, (x), (y), (z))
695#define getattrlist(v,w,x,y,z) syscall(SYS_getattrlist, (v), (w), (x), (y), (z))
696        if (!filemap) {
697                return true;
698        }
699
700        typedef struct {
701                char *start;
702                size_t len;
703        } path_component_t;
704
705        char normPath[MAXPATHLEN];
706        normPath[0] = '/';
707        normPath[1] = '\0';
708
709        path_component_t pathComponents[MAXPATHLEN / 2 + 2];
710        size_t numComponents = 0;
711
712        // Make sure the path is absolute.
713        if (path == NULL || *path == '\0') {
714                // this is most certainly invalid, let the syscall deal with it
715                return true;
716        }
717
718        char *dst = NULL;
719        const char *token = NULL;
720        size_t idx;
721        if (*path != '/') {
722                /*
723                 * The path isn't absolute, start by populating pathcomponents with the
724                 * current working directory.
725                 *
726                 * However, we avoid getcwd(3) if we can and use getattrlist(2) with
727                 * ATTR_CMN_FULLPATH instead, because getcwd(3) will open all parent
728                 * directories, read them, search for the current component using its
729                 * inode obtained from lstat(., .., ../.., etc.) and build the path
730                 * this way, which is inefficient and will also call back into
731                 * darwintrace code.
732                 */
733#               ifdef ATTR_CMN_FULLPATH
734                struct attrlist attrlist;
735                attrlist.bitmapcount = ATTR_BIT_MAP_COUNT;
736                attrlist.reserved = 0;
737                attrlist.commonattr = ATTR_CMN_FULLPATH;
738                attrlist.volattr = 0;
739                attrlist.dirattr = 0;
740                attrlist.fileattr = 0;
741                attrlist.forkattr = 0;
742
743                char attrbuf[sizeof(uint32_t) + sizeof(attrreference_t) + (PATH_MAX + 1)];
744                /*           attrlength         attrref_t for the name     UTF-8 name up to PATH_MAX chars */
745
746                if (-1 == (getattrlist(".", &attrlist, attrbuf, sizeof(attrbuf), FSOPT_NOFOLLOW))) {
747                        perror("darwintrace: getattrlist");
748                        abort();
749                }
750                attrreference_t *nameAttrRef = (attrreference_t *) (attrbuf + sizeof(uint32_t));
751                strlcpy(normPath, ((char *) nameAttrRef) + nameAttrRef->attr_dataoffset, sizeof(normPath));
752#               else /* defined(ATTR_CMN_FULLPATH) */
753                if (getcwd(normPath, sizeof(normPath)) == NULL) {
754                        perror("darwintrace: getcwd");
755                        abort();
756                }
757#               endif /* defined(ATTR_CMN_FULLPATH) */
758
759                char *writableToken = normPath + 1;
760                while ((idx = strcspn(writableToken, "/")) > 0) {
761                        // found a token, tokenize and store it
762                        pathComponents[numComponents].start = writableToken;
763                        pathComponents[numComponents].len   = idx;
764                        numComponents++;
765
766                        bool final = writableToken[idx] == '\0';
767                        writableToken[idx] = '\0';
768                        if (final) {
769                                break;
770                        }
771                        // advance token
772                        writableToken += idx + 1;
773                }
774
775                // copy path after the CWD into the buffer and normalize it
776                if (numComponents > 0) {
777                        path_component_t *lastComponent = pathComponents + (numComponents - 1);
778                        dst = lastComponent->start + lastComponent->len + 1;
779                } else {
780                        dst = normPath + 1;
781                }
782
783                // continue parsing at the begin of path
784                token = path;
785        } else {
786                // skip leading '/'
787                dst = normPath + 1;
788                *dst = '\0';
789                token = path + 1;
790        }
791
792        /* Make sure the path is normalized. NOTE: Do _not_ use realpath(3) here.
793         * Doing so _will_ lead to problems. This is essentially a very simple
794         * re-implementation of realpath(3). */
795        while ((idx = strcspn(token, "/")) > 0) {
796                // found a token, process it
797
798                if (token[0] == '\0' || token[0] == '/') {
799                        // empty entry, ignore
800                } else if (token[0] == '.' && (token[1] == '\0' || token[1] == '/')) {
801                        // reference to current directory, ignore
802                } else if (token[0] == '.' && token[1] == '.' && (token[2] == '\0' || token[2] == '/')) {
803                        // walk up one directory, but not if it's the last one, because /.. -> /
804                        if (numComponents > 0) {
805                                numComponents--;
806                                if (numComponents > 0) {
807                                        // move dst back to the previous entry
808                                        path_component_t *lastComponent = pathComponents + (numComponents - 1);
809                                        dst = lastComponent->start + lastComponent->len + 1;
810                                } else {
811                                        // we're at the top, move dst back to the beginning
812                                        dst = normPath + 1;
813                                }
814                        }
815                } else {
816                        // copy token to normPath buffer (and null-terminate it)
817                        strlcpy(dst, token, idx + 1);
818                        dst[idx] = '\0';
819                        // add descriptor entry for new token
820                        pathComponents[numComponents].start = dst;
821                        pathComponents[numComponents].len   = idx;
822                        numComponents++;
823
824                        // advance destination
825                        dst += idx + 1;
826                }
827
828                if (token[idx] == '\0') {
829                        break;
830                }
831                token += idx + 1;
832        }
833
834        // strip off resource forks
835        if (numComponents >= 2 &&
836                strcmp("..namedfork", pathComponents[numComponents - 2].start) == 0 &&
837                strcmp("rsrc", pathComponents[numComponents - 1].start) == 0) {
838                numComponents -= 2;
839        }
840
841#       ifdef ATTR_CMN_FULLPATH
842        if (numComponents >= 3 && strncmp(".vol", pathComponents[0].start, pathComponents[0].len) == 0) {
843                // path in VOLFS, try to get inode -> name lookup from getattrlist(2).
844
845                // Add the slashes and the terminating \0
846                for (size_t i = 0; i < numComponents; ++i) {
847                        if (i == numComponents - 1) {
848                                pathComponents[i].start[pathComponents[i].len] = '\0';
849                        } else {
850                                pathComponents[i].start[pathComponents[i].len] = '/';
851                        }
852                }
853
854                struct attrlist attrlist;
855                attrlist.bitmapcount = ATTR_BIT_MAP_COUNT;
856                attrlist.reserved = 0;
857                attrlist.commonattr = ATTR_CMN_FULLPATH;
858                attrlist.volattr = 0;
859                attrlist.dirattr = 0;
860                attrlist.fileattr = 0;
861                attrlist.forkattr = 0;
862
863                char attrbuf[sizeof(uint32_t) + sizeof(attrreference_t) + (PATH_MAX + 1)];
864                /*           attrlength         attrref_t for the name     UTF-8 name up to PATH_MAX chars */
865
866                if (-1 == (getattrlist(normPath, &attrlist, attrbuf, sizeof(attrbuf), FSOPT_NOFOLLOW))) {
867                        perror("darwintrace: getattrlist");
868                        // ignore and just return the /.vol/ path
869                } else {
870                        attrreference_t *nameAttrRef = (attrreference_t *) (attrbuf + sizeof(uint32_t));
871                        strlcpy(normPath, ((char *) nameAttrRef) + nameAttrRef->attr_dataoffset, sizeof(normPath));
872
873                        numComponents = 0;
874                        char *writableToken = normPath + 1;
875                        while ((idx = strcspn(writableToken, "/")) > 0) {
876                                // found a token, tokenize and store it
877                                pathComponents[numComponents].start = writableToken;
878                                pathComponents[numComponents].len   = idx;
879                                numComponents++;
880
881                                bool final = writableToken[idx] == '\0';
882                                writableToken[idx] = '\0';
883                                if (final) {
884                                        break;
885                                }
886                                // advance token
887                                writableToken += idx + 1;
888                        }
889                }
890        }
891#       endif
892
893        bool pathIsSymlink;
894        size_t loopCount = 0;
895        do {
896                pathIsSymlink = false;
897
898                // Add the slashes and the terminating \0
899                for (size_t i = 0; i < numComponents; ++i) {
900                        if (i == numComponents - 1) {
901                                pathComponents[i].start[pathComponents[i].len] = '\0';
902                        } else {
903                                pathComponents[i].start[pathComponents[i].len] = '/';
904                        }
905                }
906
907                if ((flags & DT_FOLLOWSYMS) == 0) {
908                        // only expand symlinks when the DT_FOLLOWSYMS flags is set;
909                        // otherwise just ignore whether this path is a symlink or not to
910                        // speed up readdir(3).
911                        break;
912                }
913
914                if (++loopCount >= 10) {
915                        // assume cylce and let the OS deal with that (yes, this actually
916                        // happens in software!)
917                        break;
918                }
919
920                // Check whether the last component is a symlink; if it is, check
921                // whether it is in the sandbox, expand it and do the same thing again.
922                struct stat st;
923                //debug_printf("checking for symlink: %s\n", normPath);
924                if (lstat(normPath, &st) != -1 && S_ISLNK(st.st_mode)) {
925                        if (!__darwintrace_sandbox_check(normPath, flags)) {
926                                return false;
927                        }
928
929                        char link[MAXPATHLEN];
930                        pathIsSymlink = true;
931
932                        ssize_t linksize;
933                        if (-1 == (linksize = readlink(normPath, link, sizeof(link)))) {
934                                perror("darwintrace: readlink");
935                                abort();
936                        }
937                        link[linksize] = '\0';
938                        //debug_printf("readlink(%s) = %s\n", normPath, link);
939
940                        if (*link == '/') {
941                                // symlink is absolute, start fresh
942                                numComponents = 0;
943                                token = link + 1;
944                                dst = normPath + 1;
945                        } else {
946                                // symlink is relative, remove last component
947                                token = link;
948                                if (numComponents > 0) {
949                                        numComponents--;
950                                        if (numComponents > 0) {
951                                                // move dst back to the previous entry
952                                                path_component_t *lastComponent = pathComponents + (numComponents - 1);
953                                                dst = lastComponent->start + lastComponent->len + 1;
954                                        } else {
955                                                // we're at the top, move dst back to the beginning
956                                                dst = normPath + 1;
957                                        }
958                                }
959                        }
960
961                        while ((idx = strcspn(token, "/")) > 0) {
962                                // found a token, process it
963
964                                if (token[0] == '\0' || token[0] == '/') {
965                                        // empty entry, ignore
966                                } else if (token[0] == '.' && (token[1] == '\0' || token[1] == '/')) {
967                                        // reference to current directory, ignore
968                                } else if (token[0] == '.' && token[1] == '.' && (token[2] == '\0' || token[2] == '/')) {
969                                        // walk up one directory, but not if it's the last one, because /.. -> /
970                                        if (numComponents > 0) {
971                                                numComponents--;
972                                                if (numComponents > 0) {
973                                                        // move dst back to the previous entry
974                                                        path_component_t *lastComponent = pathComponents + (numComponents - 1);
975                                                        dst = lastComponent->start + lastComponent->len + 1;
976                                                } else {
977                                                        // we're at the top, move dst back to the beginning
978                                                        dst = normPath + 1;
979                                                }
980                                        }
981                                } else {
982                                        // copy token to normPath buffer
983                                        strlcpy(dst, token, idx + 1);
984                                        dst[idx] = '\0';
985                                        // add descriptor entry for new token
986                                        pathComponents[numComponents].start = dst;
987                                        pathComponents[numComponents].len   = idx;
988                                        numComponents++;
989
990                                        // advance destination
991                                        dst += idx + 1;
992                                }
993
994                                if (token[idx] == '\0') {
995                                        break;
996                                }
997                                token += idx + 1;
998                        }
999                }
1000        } while (pathIsSymlink);
1001
1002        return __darwintrace_sandbox_check(normPath, flags);
1003#undef getattrlist
1004#undef readlink
1005#undef lstat
1006}
Note: See TracBrowser for help on using the repository browser.