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

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

Fix darwintrace.c on 10.3 (tentative, no 10.3 box here) by determining with
autoconf if readlink conforms to POSIX 1003.1a as in 10.4+ or not (as in 10.3).

  • Property svn:eol-style set to native
File size: 15.8 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.17 2006/07/27 22:45:30 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#include <fcntl.h>
46#include <stdarg.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50#include <unistd.h>
51#include <sys/stat.h>
52#include <sys/param.h>
53#include <sys/syscall.h>
54#include <sys/paths.h>
55#include <errno.h>
56#include <sys/cdefs.h>
57
58/*
59 * Compile time options:
60 * DARWINTRACE_SHOW_PROCESS: show the process id of every access
61 * DARWINTRACE_LOG_CREATE: log creation of files as well.
62 * DARWINTRACE_SANDBOX: control creation, deletion and writing to files.
63 * DARWINTRACE_LOG_FULL_PATH: use F_GETPATH to log the full path.
64 * DARWINTRACE_DEBUG_OUTPUT: verbose output of stuff to debug darwintrace.
65 *
66 * global variables (only checked when setup is first called)
67 * DARWINTRACE_LOG
68 *    path to the log file (no logging happens if it's unset).
69 * DARWINTRACE_SANDBOX_BOUNDS
70 *    : separated allowed paths for the creation of files.
71 *    \: -> :
72 *    \\ -> \
73 */
74
75#ifndef DARWINTRACE_SHOW_PROCESS
76#define DARWINTRACE_SHOW_PROCESS 0
77#endif
78#ifndef DARWINTRACE_LOG_CREATE
79#define DARWINTRACE_LOG_CREATE 0
80#endif
81#ifndef DARWINTRACE_SANDBOX
82#define DARWINTRACE_SANDBOX 1
83#endif
84#ifndef DARWINTRACE_DEBUG_OUTPUT
85#define DARWINTRACE_DEBUG_OUTPUT 0
86#endif
87#ifndef DARWINTRACE_LOG_FULL_PATH
88#define DARWINTRACE_LOG_FULL_PATH 1
89#endif
90
91#ifndef DEFFILEMODE
92#define DEFFILEMODE 0666
93#endif
94
95/*
96 * Prototypes.
97 */
98inline int __darwintrace_strbeginswith(const char* str, const char* prefix);
99inline void __darwintrace_log_op(const char* op, const char* procname, const char* path, int fd);
100inline void __darwintrace_setup();
101inline void __darwintrace_cleanup_path(char *path);
102
103#define START_FD 81
104static int __darwintrace_fd = -2;
105#define BUFFER_SIZE     1024
106#if DARWINTRACE_SHOW_PROCESS
107static char __darwintrace_progname[BUFFER_SIZE];
108static pid_t __darwintrace_pid = -1;
109#endif
110#if DARWINTRACE_SANDBOX
111static char** __darwintrace_sandbox_bounds = NULL;
112#endif
113
114#if __STDC_VERSION__==199901L
115#if DARWINTRACE_DEBUG_OUTPUT
116#define dprintf(...) fprintf(stderr, __VA_ARGS__)
117#else
118#define dprintf(...)
119#endif
120#else
121#if DARWINTRACE_DEBUG_OUTPUT
122#define dprintf(format, param) fprintf(stderr, format, param)
123#else
124#define dprintf(format, param)
125#endif
126#endif
127
128/*
129 * return 0 if str doesn't begin with prefix, 1 otherwise.
130 */
131inline int __darwintrace_strbeginswith(const char* str, const char* prefix) {
132        char theCharS;
133        char theCharP;
134        do {
135                theCharS = *str++;
136                theCharP = *prefix++;
137        } while(theCharP && (theCharP == theCharS));
138        return (theCharP == 0);
139}
140
141inline void __darwintrace_setup() {
142#define open(x,y,z) syscall(SYS_open, (x), (y), (z))
143#define close(x) syscall(SYS_close, (x))
144        if (__darwintrace_fd == -2) {
145                char* path = getenv("DARWINTRACE_LOG");
146                if (path != NULL) {
147                        int olderrno = errno;
148                        int fd = open(path, O_CREAT | O_WRONLY | O_APPEND, DEFFILEMODE);
149                        int newfd;
150                        for(newfd = START_FD; newfd < START_FD + 21; newfd++) {
151                                if(-1 == write(newfd, "", 0) && errno == EBADF) {
152                                        if(-1 != dup2(fd, newfd)) {
153                                                __darwintrace_fd = newfd;
154                                        }
155                                        close(fd);
156                                        fcntl(__darwintrace_fd, F_SETFD, 1); /* close-on-exec */
157                                        break;
158                                }
159                        }
160                        errno = olderrno;
161                }
162        }
163#if DARWINTRACE_SHOW_PROCESS
164        if (__darwintrace_pid == -1) {
165                char** progname = _NSGetProgname();
166                __darwintrace_pid = getpid();
167                if (progname && *progname) {
168                        strcpy(__darwintrace_progname, *progname);
169                }
170        }
171#endif
172#if DARWINTRACE_SANDBOX
173        if (__darwintrace_sandbox_bounds == NULL) {
174                char* paths = getenv("DARWINTRACE_SANDBOX_BOUNDS");
175                if (paths != NULL) {
176                        /* copy the string */
177                        char* copy = strdup(paths);
178                        if (copy != NULL) {
179                                int nbPaths = 1;
180                                int nbAllocatedPaths = 5;
181                                char** paths = (char**) malloc(sizeof(char*) * nbAllocatedPaths);
182                                char* crsr = copy;
183                                char** pathsCrsr = paths;
184                                /* first path */
185                                *pathsCrsr++ = crsr;
186                                /* parse the paths (modify the copy) */
187                                do {
188                                        char theChar = *crsr;
189                                        if (theChar == '\0') {
190                                                /* the end of the paths */
191                                                break;
192                                        }
193                                        if (theChar == ':') {
194                                                /* the end of this path */
195                                                *crsr = 0;
196                                                nbPaths++;
197                                                if (nbPaths == nbAllocatedPaths) {
198                                                        nbAllocatedPaths += 5;
199                                                        paths = (char**) realloc(paths, sizeof(char*) * nbAllocatedPaths);
200                                                        /* reset the cursor in case paths pointer was moved */
201                                                        pathsCrsr = paths + (nbPaths - 1);
202                                                }
203                                                *pathsCrsr++ = crsr + 1;
204                                        }
205                                        if (theChar == '\\') {
206                                                /* escape character. test next char */
207                                                char nextChar = crsr[1];
208                                                if (nextChar == '\\') {
209                                                        /* rewrite the string */
210                                                        char* rewriteCrsr = crsr + 1;
211                                                        do {
212                                                                char theChar = *rewriteCrsr;
213                                                                rewriteCrsr[-1] = theChar;
214                                                                rewriteCrsr++;
215                                                        } while (theChar != 0);
216                                                } else if (nextChar == ':') {
217                                                        crsr++;
218                                                }
219                                                /* otherwise, ignore (keep the backslash) */
220                                        }
221                                       
222                                        /* next char */
223                                        crsr++;
224                                } while (1);
225                                /* null terminate the array */
226                                *pathsCrsr = 0;
227                                /* resize and save it */
228                                __darwintrace_sandbox_bounds = (char**) realloc(paths, sizeof(char*) * (nbPaths + 1));
229                        }
230                }
231        }
232#endif
233#undef close
234#undef open
235}
236
237/* log a call and optionally get the real path from the fd if it's not 0.
238 * op:                  the operation (open, readlink, execve)
239 * procname:    the name of the process (can be NULL)
240 * path:                the path of the file
241 * fd:                  a fd to the file, or 0 if we don't have any.
242 */
243inline void __darwintrace_log_op(const char* op, const char* procname, const char* path, int fd) {
244#if !DARWINTRACE_SHOW_PROCESS
245        #pragma unused(procname)
246#endif
247        int size;
248        char somepath[MAXPATHLEN];
249        char logbuffer[BUFFER_SIZE];
250       
251        do {
252#ifdef __APPLE__ /* Only Darwin has volfs and F_GETPATH */
253                if ((fd > 0) && (DARWINTRACE_LOG_FULL_PATH
254                        || (strncmp(path, "/.vol/", 6) == 0))) {
255                        if(fcntl(fd, F_GETPATH, somepath) == -1) {
256                                /* getpath failed. use somepath instead */
257                                strlcpy(somepath, path, sizeof(somepath));
258                                break;
259                        }
260                }
261#endif
262                if (path[0] != '/') {
263                        int len;
264                        (void) getcwd(somepath, sizeof(somepath));
265                        len = strlen(somepath);
266                        somepath[len++] = '/';
267                        strlcpy(&somepath[len], path, sizeof(somepath) - len);
268                        break;
269                }
270
271                /* otherwise, just copy the original path. */
272                strlcpy(somepath, path, sizeof(somepath));
273        } while (0);
274
275        /* clean the path. */
276        __darwintrace_cleanup_path(somepath);
277
278        size = snprintf(logbuffer, sizeof(logbuffer),
279#if DARWINTRACE_SHOW_PROCESS
280                "%s[%d]\t"
281#endif
282                "%s\t%s\n",
283#if DARWINTRACE_SHOW_PROCESS
284                procname ? procname : __darwintrace_progname, __darwintrace_pid,
285#endif
286                op, somepath );
287
288        write(__darwintrace_fd, logbuffer, size);
289        fsync(__darwintrace_fd);
290}
291
292/* remap resource fork access to the data fork.
293 * do a partial realpath(3) to fix "foo//bar" to "foo/bar"
294 */
295inline void __darwintrace_cleanup_path(char *path) {
296  size_t pathlen, rsrclen;
297  size_t i, shiftamount;
298  enum { SAWSLASH, NOTHING } state = NOTHING;
299
300  /* if this is a foo/..namedfork/rsrc, strip it off */
301  pathlen = strlen(path);
302  rsrclen = strlen(_PATH_RSRCFORKSPEC);
303  if(pathlen > rsrclen
304     && 0 == strcmp(path + pathlen - rsrclen,
305                    _PATH_RSRCFORKSPEC)) {
306    path[pathlen - rsrclen] = '\0';
307    pathlen -= rsrclen;
308  }
309
310  /* for each position in string (including
311     terminal \0), check if we're in a run of
312     multiple slashes, and only emit the
313     first one
314  */
315  for(i=0, shiftamount=0; i <= pathlen; i++) {
316    if(state == SAWSLASH) {
317      if(path[i] == '/') {
318        /* consume it */
319        shiftamount++;
320      } else {
321        state = NOTHING;
322        path[i - shiftamount] = path[i];
323      }
324    } else {
325      if(path[i] == '/') {
326        state = SAWSLASH;
327      }
328      path[i - shiftamount] = path[i];
329    }
330  }
331
332  dprintf("darwintrace: cleanup resulted in %s\n", path);
333}
334
335#if DARWINTRACE_SANDBOX
336/*
337 * return 1 if path (once normalized) is in sandbox, 0 otherwise.
338 * return -1 if no sandbox is defined or if the path couldn't be normalized.
339 */
340inline int __darwintrace_is_in_sandbox(const char* path) {
341        int result = -1; /* no sandbox is defined */
342        __darwintrace_setup();
343        if (__darwintrace_sandbox_bounds != NULL) {
344                /* check the path */
345                char** basePathsCrsr = __darwintrace_sandbox_bounds;
346                char* basepath = *basePathsCrsr++;
347                /* normalize the path */
348                char createpath[MAXPATHLEN];
349                if (realpath(path, createpath) != NULL) {
350                        __darwintrace_cleanup_path(createpath);
351                        /* say it's outside unless it's proved inside */
352                        result = 0;
353                        while (basepath != NULL) {
354                                if (__darwintrace_strbeginswith(createpath, basepath)) {
355                                        result = 1;
356                                        break;
357                                }
358                                basepath = *basePathsCrsr++;;
359                        }
360                } /* otherwise, operation will fail anyway */
361        }
362        return result;
363}
364#endif
365
366/* Log calls to open(2) into the file specified by DARWINTRACE_LOG.
367   Only logs if the DARWINTRACE_LOG environment variable is set.
368   Only logs files (or rather, do not logs directories)
369   Only logs files where the open succeeds.
370   Only logs files opened for read access, without the O_CREAT flag set
371        (unless DARWINTRACE_LOG_CREATE is set).
372   The assumption is that any file that can be created isn't necessary
373   to build the project.
374*/
375
376int open(const char* path, int flags, ...) {
377#define open(x,y,z) syscall(SYS_open, (x), (y), (z))
378        mode_t mode;
379        int result;
380        va_list args;
381
382        va_start(args, flags);
383        mode = va_arg(args, int);
384        va_end(args);
385#if DARWINTRACE_SANDBOX
386        result = 0;
387        if (flags & (O_CREAT | O_APPEND | O_RDWR | O_WRONLY | O_TRUNC)) {
388                int isInSandbox = __darwintrace_is_in_sandbox(path);
389                if (isInSandbox == 1) {
390                        dprintf("darwintrace: creation/writing was allowed at %s\n", path);
391                } else if (isInSandbox == 0) {
392                        /* outside sandbox, but sandbox is defined: forbid */
393                        dprintf("darwintrace: creation/writing was forbidden at %s\n", path);
394                        __darwintrace_log_op("sandbox_violation", NULL, path, 0);
395                        errno = EACCES;
396                        result = -1;
397                }
398        }
399        if (result == 0) {
400                result = open(path, flags, mode);
401        }
402#else
403        result = open(path, flags, mode);
404#endif
405        if (result >= 0) {
406                /* check that it's a file */
407                struct stat sb;
408                fstat(result, &sb);
409                if ((sb.st_mode & S_IFDIR) == 0) {
410                        if ((flags & (O_CREAT | O_WRONLY /*O_RDWR*/)) == 0 ) {
411                                __darwintrace_setup();
412                                if (__darwintrace_fd >= 0) {
413                                    dprintf("darwintrace: original open path is %s\n", path);
414                                        __darwintrace_log_op("open", NULL, path, result);
415                                }
416#if DARWINTRACE_LOG_CREATE
417                        } else if (flags & O_CREAT) {
418                                __darwintrace_setup();
419                                if (__darwintrace_fd >= 0) {
420                                    dprintf("darwintrace: original create path is %s\n", path);
421                                        __darwintrace_log_op("create", NULL, path, result);
422                                }
423#endif
424                        }
425                }
426        }
427        return result;
428#undef open
429}
430
431/* Log calls to readlink(2) into the file specified by DARWINTRACE_LOG.
432   Only logs if the DARWINTRACE_LOG environment variable is set.
433   Only logs files where the readlink succeeds.
434*/
435#ifdef READLINK_IS_NOT_P1003_1A
436int  readlink(const char * path, char * buf, int bufsiz) {
437#else
438ssize_t  readlink(const char * path, char * buf, size_t bufsiz) {
439#endif
440#define readlink(x,y,z) syscall(SYS_readlink, (x), (y), (z))
441        ssize_t result;
442
443        result = readlink(path, buf, bufsiz);
444        if (result >= 0) {
445          __darwintrace_setup();
446          if (__darwintrace_fd >= 0) {
447            dprintf("darwintrace: original readlink path is %s\n", path);
448                __darwintrace_log_op("readlink", NULL, path, 0);
449          }
450        }
451        return result;
452#undef readlink
453}
454
455int execve(const char* path, char* const argv[], char* const envp[]) {
456#define execve(x,y,z) syscall(SYS_execve, (x), (y), (z))
457#define open(x,y,z) syscall(SYS_open, (x), (y), (z))
458#define close(x) syscall(SYS_close, (x))
459        int result;
460#if DARWINTRACE_SHOW_PROCESS
461        int saved_pid;
462#endif
463        __darwintrace_setup();
464        if (__darwintrace_fd >= 0) {
465          struct stat sb;
466          /* for symlinks, we wan't to capture
467           * both the original path and the modified one,
468           * since for /usr/bin/gcc -> gcc-4.0,
469           * both "gcc_select" and "gcc" are contributors
470           */
471          if (lstat(path, &sb) == 0) {
472                int fd;
473
474            if(S_ISLNK(sb.st_mode)) {
475              /* for symlinks, print both */
476                  __darwintrace_log_op("execve", NULL, path, 0);
477            }
478               
479                fd = open(path, O_RDONLY, 0);
480                if (fd > 0) {
481                  char buffer[MAXPATHLEN+1];
482                  ssize_t bytes_read;
483       
484                  /* once we have an open fd, if a full path was requested, do it */
485                  __darwintrace_log_op("execve", NULL, path, fd);
486
487                  /* read the file for the interpreter */
488                  bytes_read = read(fd, buffer, MAXPATHLEN);
489                  buffer[bytes_read] = 0;
490                  if (bytes_read > 2 &&
491                        buffer[0] == '#' && buffer[1] == '!') {
492                        const char* interp = &buffer[2];
493                        int i;
494                        /* skip past leading whitespace */
495                        for (i = 2; i < bytes_read; ++i) {
496                          if (buffer[i] != ' ' && buffer[i] != '\t') {
497                                interp = &buffer[i];
498                                break;
499                          }
500                        }
501                        /* found interpreter (or ran out of data)
502                           skip until next whitespace, then terminate the string */
503                        for (; i < bytes_read; ++i) {
504                          if (buffer[i] == ' ' || buffer[i] == '\t' || buffer[i] == '\n') {
505                                buffer[i] = 0;
506                                break;
507                          }
508                        }
509                        /* we have liftoff */
510                        if (interp && interp[0] != '\0') {
511                          const char* procname = NULL;
512#if DARWINTRACE_SHOW_PROCESS
513                          procname = strrchr(argv[0], '/') + 1;
514                          if (procname == NULL) {
515                                procname = argv[0];
516                          }
517#endif
518                          __darwintrace_log_op("execve", procname, interp, 0);
519                        }
520                  }
521                  close(fd);
522                }
523          }
524        }
525       
526        result = execve(path, argv, envp);
527        return result;
528#undef close
529#undef open
530#undef execve
531}
532
533/* if darwintrace has  been initialized, trap
534   attempts to close our file descriptor
535*/
536int close(int fd) {
537#define close(x) syscall(SYS_close, (x))
538
539  if(__darwintrace_fd != -2 && fd == __darwintrace_fd) {
540    errno = EBADF;
541    return -1;
542  }
543
544  return close(fd);
545#undef close
546}
547
548#if DARWINTRACE_SANDBOX
549/* Trap attempts to unlink a file outside the sandbox.
550 */
551int unlink(const char* path) {
552#define __unlink(x) syscall(SYS_unlink, (x))
553        int result = 0;
554        int isInSandbox = __darwintrace_is_in_sandbox(path);
555        if (isInSandbox == 1) {
556                dprintf("darwintrace: unlink was allowed at %s\n", path);
557        } else if (isInSandbox == 0) {
558                /* outside sandbox, but sandbox is defined: forbid */
559                dprintf("darwintrace: unlink was forbidden at %s\n", path);
560                __darwintrace_log_op("sandbox_violation", NULL, path, 0);
561                errno = EACCES;
562                result = -1;
563        }
564       
565        if (result == 0) {
566                result = __unlink(path);
567        }
568       
569        return result;
570}
571#endif
Note: See TracBrowser for help on using the repository browser.