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

Last change on this file since 18781 was 18781, checked in by pguyot (Paul Guyot), 14 years ago

darwintrace now reports creation of directories outside the sandbox.
It works with rb-rubygems. Cf:
http://bugzilla.opendarwin.org/show_bug.cgi?id=5491

  • Property svn:eol-style set to native
File size: 17.2 KB
Line 
1/*
2 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
3 * Copyright (c) 2005-2006 Paul Guyot <pguyot@kallisys.net>,
4 * All rights reserved.
5 *
6 * $Id: darwintrace.c,v 1.19 2006/07/28 10:11:09 pguyot Exp $
7 *
8 * @APPLE_BSD_LICENSE_HEADER_START@
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1.  Redistributions of source code must retain the above copyright
15 *     notice, this list of conditions and the following disclaimer.
16 * 2.  Redistributions in binary form must reproduce the above copyright
17 *     notice, this list of conditions and the following disclaimer in the
18 *     documentation and/or other materials provided with the distribution.
19 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
20 *     its contributors may be used to endorse or promote products derived
21 *     from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
24 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
27 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
30 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 *
34 * @APPLE_BSD_LICENSE_HEADER_END@
35 */
36
37#ifdef HAVE_CONFIG_H
38#include <config.h>
39#endif
40
41#ifdef HAVE_CRT_EXTERNS_H
42#include <crt_externs.h>
43#endif
44
45#ifdef HAVE_SYS_PATHS_H
46#include <sys/paths.h>
47#endif
48
49#include <fcntl.h>
50#include <stdarg.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <string.h>
54#include <unistd.h>
55#include <sys/types.h>
56#include <sys/stat.h>
57#include <sys/param.h>
58#include <sys/syscall.h>
59#include <errno.h>
60
61#ifndef HAVE_STRLCPY
62/* Define strlcpy if it's not available. */
63size_t strlcpy(char* dst, const char* src, size_t size);
64size_t strlcpy(char* dst, const char* src, size_t size)
65{
66        size_t result = strlen(src);
67        if (size > 0)
68        {
69                size_t copylen = size - 1;
70                if (copylen > result)
71                {
72                        copylen = result;
73                }
74                memcpy(dst, src, copylen);
75                dst[copylen] = 0;
76        }
77        return result;
78}
79#endif
80
81/*
82 * Compile time options:
83 * DARWINTRACE_SHOW_PROCESS: show the process id of every access
84 * DARWINTRACE_LOG_CREATE: log creation of files as well.
85 * DARWINTRACE_SANDBOX: control creation, deletion and writing to files and dirs.
86 * DARWINTRACE_LOG_FULL_PATH: use F_GETPATH to log the full path.
87 * DARWINTRACE_DEBUG_OUTPUT: verbose output of stuff to debug darwintrace.
88 *
89 * global variables (only checked when setup is first called)
90 * DARWINTRACE_LOG
91 *    path to the log file (no logging happens if it's unset).
92 * DARWINTRACE_SANDBOX_BOUNDS
93 *    : separated allowed paths for the creation of files.
94 *    \: -> :
95 *    \\ -> \
96 */
97
98#ifndef DARWINTRACE_SHOW_PROCESS
99#define DARWINTRACE_SHOW_PROCESS 0
100#endif
101#ifndef DARWINTRACE_LOG_CREATE
102#define DARWINTRACE_LOG_CREATE 0
103#endif
104#ifndef DARWINTRACE_SANDBOX
105#define DARWINTRACE_SANDBOX 1
106#endif
107#ifndef DARWINTRACE_DEBUG_OUTPUT
108#define DARWINTRACE_DEBUG_OUTPUT 0
109#endif
110#ifndef DARWINTRACE_LOG_FULL_PATH
111#define DARWINTRACE_LOG_FULL_PATH 1
112#endif
113
114#ifndef DEFFILEMODE
115#define DEFFILEMODE 0666
116#endif
117
118/*
119 * Prototypes.
120 */
121inline int __darwintrace_strbeginswith(const char* str, const char* prefix);
122inline void __darwintrace_log_op(const char* op, const char* procname, const char* path, int fd);
123inline void __darwintrace_setup();
124inline void __darwintrace_cleanup_path(char *path);
125
126#define START_FD 81
127static int __darwintrace_fd = -2;
128#define BUFFER_SIZE     1024
129#if DARWINTRACE_SHOW_PROCESS
130static char __darwintrace_progname[BUFFER_SIZE];
131static pid_t __darwintrace_pid = -1;
132#endif
133#if DARWINTRACE_SANDBOX
134static char** __darwintrace_sandbox_bounds = NULL;
135#endif
136
137#if __STDC_VERSION__==199901L
138#if DARWINTRACE_DEBUG_OUTPUT
139#define dprintf(...) fprintf(stderr, __VA_ARGS__)
140#else
141#define dprintf(...)
142#endif
143#else
144#if DARWINTRACE_DEBUG_OUTPUT
145#define dprintf(format, param) fprintf(stderr, format, param)
146#else
147#define dprintf(format, param)
148#endif
149#endif
150
151/*
152 * return 0 if str doesn't begin with prefix, 1 otherwise.
153 */
154inline int __darwintrace_strbeginswith(const char* str, const char* prefix) {
155        char theCharS;
156        char theCharP;
157        do {
158                theCharS = *str++;
159                theCharP = *prefix++;
160        } while(theCharP && (theCharP == theCharS));
161        return (theCharP == 0);
162}
163
164inline void __darwintrace_setup() {
165#define open(x,y,z) syscall(SYS_open, (x), (y), (z))
166#define close(x) syscall(SYS_close, (x))
167        if (__darwintrace_fd == -2) {
168                char* path = getenv("DARWINTRACE_LOG");
169                if (path != NULL) {
170                        int olderrno = errno;
171                        int fd = open(path, O_CREAT | O_WRONLY | O_APPEND, DEFFILEMODE);
172                        int newfd;
173                        for(newfd = START_FD; newfd < START_FD + 21; newfd++) {
174                                if(-1 == write(newfd, "", 0) && errno == EBADF) {
175                                        if(-1 != dup2(fd, newfd)) {
176                                                __darwintrace_fd = newfd;
177                                        }
178                                        close(fd);
179                                        fcntl(__darwintrace_fd, F_SETFD, 1); /* close-on-exec */
180                                        break;
181                                }
182                        }
183                        errno = olderrno;
184                }
185        }
186#if DARWINTRACE_SHOW_PROCESS
187        if (__darwintrace_pid == -1) {
188                char** progname = _NSGetProgname();
189                __darwintrace_pid = getpid();
190                if (progname && *progname) {
191                        strcpy(__darwintrace_progname, *progname);
192                }
193        }
194#endif
195#if DARWINTRACE_SANDBOX
196        if (__darwintrace_sandbox_bounds == NULL) {
197                char* paths = getenv("DARWINTRACE_SANDBOX_BOUNDS");
198                if (paths != NULL) {
199                        /* copy the string */
200                        char* copy = strdup(paths);
201                        if (copy != NULL) {
202                                int nbPaths = 1;
203                                int nbAllocatedPaths = 5;
204                                char** paths = (char**) malloc(sizeof(char*) * nbAllocatedPaths);
205                                char* crsr = copy;
206                                char** pathsCrsr = paths;
207                                /* first path */
208                                *pathsCrsr++ = crsr;
209                                /* parse the paths (modify the copy) */
210                                do {
211                                        char theChar = *crsr;
212                                        if (theChar == '\0') {
213                                                /* the end of the paths */
214                                                break;
215                                        }
216                                        if (theChar == ':') {
217                                                /* the end of this path */
218                                                *crsr = 0;
219                                                nbPaths++;
220                                                if (nbPaths == nbAllocatedPaths) {
221                                                        nbAllocatedPaths += 5;
222                                                        paths = (char**) realloc(paths, sizeof(char*) * nbAllocatedPaths);
223                                                        /* reset the cursor in case paths pointer was moved */
224                                                        pathsCrsr = paths + (nbPaths - 1);
225                                                }
226                                                *pathsCrsr++ = crsr + 1;
227                                        }
228                                        if (theChar == '\\') {
229                                                /* escape character. test next char */
230                                                char nextChar = crsr[1];
231                                                if (nextChar == '\\') {
232                                                        /* rewrite the string */
233                                                        char* rewriteCrsr = crsr + 1;
234                                                        do {
235                                                                char theChar = *rewriteCrsr;
236                                                                rewriteCrsr[-1] = theChar;
237                                                                rewriteCrsr++;
238                                                        } while (theChar != 0);
239                                                } else if (nextChar == ':') {
240                                                        crsr++;
241                                                }
242                                                /* otherwise, ignore (keep the backslash) */
243                                        }
244                                       
245                                        /* next char */
246                                        crsr++;
247                                } while (1);
248                                /* null terminate the array */
249                                *pathsCrsr = 0;
250                                /* resize and save it */
251                                __darwintrace_sandbox_bounds = (char**) realloc(paths, sizeof(char*) * (nbPaths + 1));
252                        }
253                }
254        }
255#endif
256#undef close
257#undef open
258}
259
260/* log a call and optionally get the real path from the fd if it's not 0.
261 * op:                  the operation (open, readlink, execve)
262 * procname:    the name of the process (can be NULL)
263 * path:                the path of the file
264 * fd:                  a fd to the file, or 0 if we don't have any.
265 */
266inline void __darwintrace_log_op(const char* op, const char* procname, const char* path, int fd) {
267#if !DARWINTRACE_SHOW_PROCESS
268        #pragma unused(procname)
269#endif
270        int size;
271        char somepath[MAXPATHLEN];
272        char logbuffer[BUFFER_SIZE];
273       
274        do {
275#ifdef __APPLE__ /* Only Darwin has volfs and F_GETPATH */
276                if ((fd > 0) && (DARWINTRACE_LOG_FULL_PATH
277                        || (strncmp(path, "/.vol/", 6) == 0))) {
278                        if(fcntl(fd, F_GETPATH, somepath) == -1) {
279                                /* getpath failed. use somepath instead */
280                                strlcpy(somepath, path, sizeof(somepath));
281                                break;
282                        }
283                }
284#endif
285                if (path[0] != '/') {
286                        int len;
287                        (void) getcwd(somepath, sizeof(somepath));
288                        len = strlen(somepath);
289                        somepath[len++] = '/';
290                        strlcpy(&somepath[len], path, sizeof(somepath) - len);
291                        break;
292                }
293
294                /* otherwise, just copy the original path. */
295                strlcpy(somepath, path, sizeof(somepath));
296        } while (0);
297
298        /* clean the path. */
299        __darwintrace_cleanup_path(somepath);
300
301        size = snprintf(logbuffer, sizeof(logbuffer),
302#if DARWINTRACE_SHOW_PROCESS
303                "%s[%d]\t"
304#endif
305                "%s\t%s\n",
306#if DARWINTRACE_SHOW_PROCESS
307                procname ? procname : __darwintrace_progname, __darwintrace_pid,
308#endif
309                op, somepath );
310
311        write(__darwintrace_fd, logbuffer, size);
312        fsync(__darwintrace_fd);
313}
314
315/* remap resource fork access to the data fork.
316 * do a partial realpath(3) to fix "foo//bar" to "foo/bar"
317 */
318inline void __darwintrace_cleanup_path(char *path) {
319  size_t pathlen;
320#ifdef __APPLE__
321  size_t rsrclen;
322#endif
323  size_t i, shiftamount;
324  enum { SAWSLASH, NOTHING } state = NOTHING;
325
326  /* if this is a foo/..namedfork/rsrc, strip it off */
327  pathlen = strlen(path);
328  /* ..namedfork/rsrc is only on OS X */
329#ifdef __APPLE__
330  rsrclen = strlen(_PATH_RSRCFORKSPEC);
331  if(pathlen > rsrclen
332     && 0 == strcmp(path + pathlen - rsrclen,
333                    _PATH_RSRCFORKSPEC)) {
334    path[pathlen - rsrclen] = '\0';
335    pathlen -= rsrclen;
336  }
337#endif
338
339  /* for each position in string (including
340     terminal \0), check if we're in a run of
341     multiple slashes, and only emit the
342     first one
343  */
344  for(i=0, shiftamount=0; i <= pathlen; i++) {
345    if(state == SAWSLASH) {
346      if(path[i] == '/') {
347        /* consume it */
348        shiftamount++;
349      } else {
350        state = NOTHING;
351        path[i - shiftamount] = path[i];
352      }
353    } else {
354      if(path[i] == '/') {
355        state = SAWSLASH;
356      }
357      path[i - shiftamount] = path[i];
358    }
359  }
360
361  dprintf("darwintrace: cleanup resulted in %s\n", path);
362}
363
364#if DARWINTRACE_SANDBOX
365/*
366 * return 1 if path (once normalized) is in sandbox, 0 otherwise.
367 * return -1 if no sandbox is defined or if the path couldn't be normalized.
368 */
369inline int __darwintrace_is_in_sandbox(const char* path) {
370        int result = -1; /* no sandbox is defined */
371        __darwintrace_setup();
372        if (__darwintrace_sandbox_bounds != NULL) {
373                /* check the path */
374                char** basePathsCrsr = __darwintrace_sandbox_bounds;
375                char* basepath = *basePathsCrsr++;
376                /* normalize the path */
377                char createpath[MAXPATHLEN];
378                if (realpath(path, createpath) != NULL) {
379                        __darwintrace_cleanup_path(createpath);
380                        /* say it's outside unless it's proved inside */
381                        result = 0;
382                        while (basepath != NULL) {
383                                if (__darwintrace_strbeginswith(createpath, basepath)) {
384                                        result = 1;
385                                        break;
386                                }
387                                basepath = *basePathsCrsr++;;
388                        }
389                } /* otherwise, operation will fail anyway */
390        }
391        return result;
392}
393#endif
394
395/* Log calls to open(2) into the file specified by DARWINTRACE_LOG.
396   Only logs if the DARWINTRACE_LOG environment variable is set.
397   Only logs files (or rather, do not logs directories)
398   Only logs files where the open succeeds.
399   Only logs files opened for read access, without the O_CREAT flag set
400        (unless DARWINTRACE_LOG_CREATE is set).
401   The assumption is that any file that can be created isn't necessary
402   to build the project.
403*/
404
405int open(const char* path, int flags, ...) {
406#define open(x,y,z) syscall(SYS_open, (x), (y), (z))
407        mode_t mode;
408        int result;
409        va_list args;
410
411        va_start(args, flags);
412        mode = va_arg(args, int);
413        va_end(args);
414#if DARWINTRACE_SANDBOX
415        result = 0;
416        if (flags & (O_CREAT | O_APPEND | O_RDWR | O_WRONLY | O_TRUNC)) {
417                int isInSandbox = __darwintrace_is_in_sandbox(path);
418                if (isInSandbox == 1) {
419                        dprintf("darwintrace: creation/writing was allowed at %s\n", path);
420                } else if (isInSandbox == 0) {
421                        /* outside sandbox, but sandbox is defined: forbid */
422                        dprintf("darwintrace: creation/writing was forbidden at %s\n", path);
423                        __darwintrace_log_op("sandbox_violation", NULL, path, 0);
424                        errno = EACCES;
425                        result = -1;
426                }
427        }
428        if (result == 0) {
429                result = open(path, flags, mode);
430        }
431#else
432        result = open(path, flags, mode);
433#endif
434        if (result >= 0) {
435                /* check that it's a file */
436                struct stat sb;
437                fstat(result, &sb);
438                if ((sb.st_mode & S_IFDIR) == 0) {
439                        if ((flags & (O_CREAT | O_WRONLY /*O_RDWR*/)) == 0 ) {
440                                __darwintrace_setup();
441                                if (__darwintrace_fd >= 0) {
442                                    dprintf("darwintrace: original open path is %s\n", path);
443                                        __darwintrace_log_op("open", NULL, path, result);
444                                }
445#if DARWINTRACE_LOG_CREATE
446                        } else if (flags & O_CREAT) {
447                                __darwintrace_setup();
448                                if (__darwintrace_fd >= 0) {
449                                    dprintf("darwintrace: original create path is %s\n", path);
450                                        __darwintrace_log_op("create", NULL, path, result);
451                                }
452#endif
453                        }
454                }
455        }
456        return result;
457#undef open
458}
459
460/* Log calls to readlink(2) into the file specified by DARWINTRACE_LOG.
461   Only logs if the DARWINTRACE_LOG environment variable is set.
462   Only logs files where the readlink succeeds.
463*/
464#ifdef READLINK_IS_NOT_P1003_1A
465int  readlink(const char * path, char * buf, int bufsiz) {
466#else
467ssize_t  readlink(const char * path, char * buf, size_t bufsiz) {
468#endif
469#define readlink(x,y,z) syscall(SYS_readlink, (x), (y), (z))
470        ssize_t result;
471
472        result = readlink(path, buf, bufsiz);
473        if (result >= 0) {
474          __darwintrace_setup();
475          if (__darwintrace_fd >= 0) {
476            dprintf("darwintrace: original readlink path is %s\n", path);
477                __darwintrace_log_op("readlink", NULL, path, 0);
478          }
479        }
480        return result;
481#undef readlink
482}
483
484int execve(const char* path, char* const argv[], char* const envp[]) {
485#define execve(x,y,z) syscall(SYS_execve, (x), (y), (z))
486#define open(x,y,z) syscall(SYS_open, (x), (y), (z))
487#define close(x) syscall(SYS_close, (x))
488        int result;
489#if DARWINTRACE_SHOW_PROCESS
490        int saved_pid;
491#endif
492        __darwintrace_setup();
493        if (__darwintrace_fd >= 0) {
494          struct stat sb;
495          /* for symlinks, we wan't to capture
496           * both the original path and the modified one,
497           * since for /usr/bin/gcc -> gcc-4.0,
498           * both "gcc_select" and "gcc" are contributors
499           */
500          if (lstat(path, &sb) == 0) {
501                int fd;
502
503            if(S_ISLNK(sb.st_mode)) {
504              /* for symlinks, print both */
505                  __darwintrace_log_op("execve", NULL, path, 0);
506            }
507               
508                fd = open(path, O_RDONLY, 0);
509                if (fd > 0) {
510                  char buffer[MAXPATHLEN+1];
511                  ssize_t bytes_read;
512       
513                  /* once we have an open fd, if a full path was requested, do it */
514                  __darwintrace_log_op("execve", NULL, path, fd);
515
516                  /* read the file for the interpreter */
517                  bytes_read = read(fd, buffer, MAXPATHLEN);
518                  buffer[bytes_read] = 0;
519                  if (bytes_read > 2 &&
520                        buffer[0] == '#' && buffer[1] == '!') {
521                        const char* interp = &buffer[2];
522                        int i;
523                        /* skip past leading whitespace */
524                        for (i = 2; i < bytes_read; ++i) {
525                          if (buffer[i] != ' ' && buffer[i] != '\t') {
526                                interp = &buffer[i];
527                                break;
528                          }
529                        }
530                        /* found interpreter (or ran out of data)
531                           skip until next whitespace, then terminate the string */
532                        for (; i < bytes_read; ++i) {
533                          if (buffer[i] == ' ' || buffer[i] == '\t' || buffer[i] == '\n') {
534                                buffer[i] = 0;
535                                break;
536                          }
537                        }
538                        /* we have liftoff */
539                        if (interp && interp[0] != '\0') {
540                          const char* procname = NULL;
541#if DARWINTRACE_SHOW_PROCESS
542                          procname = strrchr(argv[0], '/') + 1;
543                          if (procname == NULL) {
544                                procname = argv[0];
545                          }
546#endif
547                          __darwintrace_log_op("execve", procname, interp, 0);
548                        }
549                  }
550                  close(fd);
551                }
552          }
553        }
554       
555        result = execve(path, argv, envp);
556        return result;
557#undef close
558#undef open
559#undef execve
560}
561
562/* if darwintrace has  been initialized, trap
563   attempts to close our file descriptor
564*/
565int close(int fd) {
566#define close(x) syscall(SYS_close, (x))
567
568  if(__darwintrace_fd != -2 && fd == __darwintrace_fd) {
569    errno = EBADF;
570    return -1;
571  }
572
573  return close(fd);
574#undef close
575}
576
577#if DARWINTRACE_SANDBOX
578/* Trap attempts to unlink a file outside the sandbox.
579 */
580int unlink(const char* path) {
581#define __unlink(x) syscall(SYS_unlink, (x))
582        int result = 0;
583        int isInSandbox = __darwintrace_is_in_sandbox(path);
584        if (isInSandbox == 1) {
585                dprintf("darwintrace: unlink was allowed at %s\n", path);
586        } else if (isInSandbox == 0) {
587                /* outside sandbox, but sandbox is defined: forbid */
588                dprintf("darwintrace: unlink was forbidden at %s\n", path);
589                __darwintrace_log_op("sandbox_violation", NULL, path, 0);
590                errno = EACCES;
591                result = -1;
592        }
593       
594        if (result == 0) {
595                result = __unlink(path);
596        }
597       
598        return result;
599}
600#endif
601
602#if DARWINTRACE_SANDBOX
603/* Trap attempts to create directories outside the sandbox.
604 */
605int mkdir(const char* path, mode_t mode) {
606#define __mkdir(x,y) syscall(SYS_mkdir, (x), (y))
607        int result = 0;
608        int isInSandbox = __darwintrace_is_in_sandbox(path);
609        if (isInSandbox == 1) {
610                dprintf("darwintrace: mkdir was allowed at %s\n", path);
611        } else if (isInSandbox == 0) {
612                /* outside sandbox, but sandbox is defined: forbid */
613                /* only consider directories that do not exist. */
614                struct stat theInfo;
615                int err;
616                err = lstat(path, &theInfo);
617                if ((err == -1) && (errno == ENOENT))
618                {
619                        dprintf("darwintrace: mkdir was forbidden at %s\n", path);
620                        __darwintrace_log_op("sandbox_violation", NULL, path, 0);
621                        errno = EACCES;
622                        result = -1;
623                } /* otherwise, mkdir will do nothing (directory exists) or fail
624                     (another error) */
625        }
626       
627        if (result == 0) {
628                result = __mkdir(path, mode);
629        }
630       
631        return result;
632}
633#endif
Note: See TracBrowser for help on using the repository browser.