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

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

Add sys/paths to configure.ac.
Get darwintrace.c to compile on foreign systems.

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