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

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

base: darwintrace: Make notes where I sometimes see crashes

I did migration to El Capitan with trace mode enabled and used the crash
generated crash reports to track down where darwintrace breaks stuff in
practice. This document these locations with a FIXME so they can be fixed some
time.

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