source: trunk/base/src/darwintracelib1.0/proc.c @ 141420

Last change on this file since 141420 was 141420, checked in by cal@…, 4 years ago

base: Fix trace mode on El Capitan

OS X El Capitan introduces System Integrity Protection for files. Executables
with this flag set will be started in a sanitized environment by the kernel,
stripping all DYLD_* variables. This breaks trace mode, because tracing relies
on preloading to wrap file related system calls using DYLD_INSERT_LIBRARIES.

A trivial workaround for the problem is to make a copy of the affected
binaries, which will strip the flag, and then adjust the invocation of the
binary to execute the copy instead (but leaving argv[0] as-is to avoid giving
the program an indication of being run from a non-standard location).

This change implements this approach by copying the SIP-flagged binaries to
$prefix/var/macports/sip-workaround on demand iff

  • the system has the SF_RESTRICTED flag defined
  • a binary is started with DYLD_INSERT_LIBRARIES set
  • the file exists and has SF_RESTRICTED set
  • the file isn't SUID or SGID (which we could not reliably copy, and which have never preserved DYLD_* variables)

If the file to be executed is a script and has a shebang line, the checks are
run on the interpreter instead, and if necessary, the interpreter is copied.
This requires interpreting the shebang line in user space.

Copies are created on-demand and are lazy: The file modification times are
checked before overwriting an existing copy. Copies are created in a per-user
folder, which will be created on-demand in a 1777 directory (like /tmp).

Changes are also needed way before darwintrace.dylib first runs: The DYLD_*
variables are already stripped in src/pextlib1.0/system.c, where
/usr/bin/sandbox-exec and /bin/sh are run, which both have the SF_RESTRICTED
flag on 10.11 now. Consequently, the same copying approach is applied there.

Because macports build run in a sandbox, the sandbox boundaries are extended to
allow access to $prefix/var/macports/sip-workaround.

  • Property svn:eol-style set to native
File size: 11.0 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.h 112642 2013-10-28 18:59:19Z 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 "sip_copy_proc.h"
41
42#include <ctype.h>
43#include <dlfcn.h>
44#include <errno.h>
45#include <fcntl.h>
46#include <stdlib.h>
47#include <string.h>
48#include <sys/param.h>
49#include <sys/syscall.h>
50#include <sys/types.h>
51#include <sys/uio.h>
52#include <unistd.h>
53
54#if defined(HAVE_SPAWN_H) && defined(HAVE_POSIX_SPAWN)
55#include <spawn.h>
56#endif
57
58static void store_env() __attribute__((constructor));
59
60/**
61 * Copy of the DYLD_INSERT_LIBRARIES environment variable to restore it in
62 * execve(2). DYLD_INSERT_LIBRARIES is needed to preload this library into any
63 * process' address space.
64 */
65static char *__env_dyld_insert_libraries;
66static char *__env_full_dyld_insert_libraries;
67
68/**
69 * Copy of the DYLD_FORCE_FLAT_NAMESPACE environment variable to restore it in
70 * execve(2). DYLD_FORCE_FLAT_NAMESPACE=1 is needed for the preload-based
71 * sandbox to work.
72 */
73static char *__env_dyld_force_flat_namespace;
74static char *__env_full_dyld_force_flat_namespace;
75
76/**
77 * Copy of the DARWINTRACE_LOG environment variable to restore it in execve(2).
78 * Contains the path to the unix socket used for communication with the
79 * MacPorts-side of the sandbox. Since this variable is also used from
80 * darwintrace.c, is can not be static.
81 */
82char *__env_darwintrace_log;
83static char *__env_full_darwintrace_log;
84
85/**
86 * Copy the environment variables, if they're defined. This is run as
87 * a constructor at startup.
88 */
89static void store_env() {
90#define COPYENV(name, variable, valuevar) do {\
91                char *val;\
92                if (NULL != (val = getenv(#name))) {\
93                        size_t lenName = strlen(#name);\
94                        size_t lenVal  = strlen(val);\
95                        if (NULL == (variable = malloc(lenName + 1 + lenVal + 1))) {\
96                                perror("darwintrace: malloc");\
97                                abort();\
98                        }\
99                        strcpy(variable, #name);\
100                        strcat(variable, "=");\
101                        strcat(variable, val);\
102                        valuevar = variable + lenName + 1;\
103                } else {\
104                        variable = NULL;\
105                        valuevar = NULL;\
106                }\
107        } while (0)
108
109        COPYENV(DYLD_INSERT_LIBRARIES, __env_full_dyld_insert_libraries, __env_dyld_insert_libraries);
110        COPYENV(DYLD_FORCE_FLAT_NAMESPACE, __env_full_dyld_force_flat_namespace, __env_dyld_force_flat_namespace);
111        COPYENV(DARWINTRACE_LOG, __env_full_darwintrace_log, __env_darwintrace_log);
112#undef COPYENV
113
114        char *debugpath = getenv("DARWINTRACE_DEBUG");
115        if (debugpath) {
116                __darwintrace_stderr = fopen(debugpath, "a+");
117        } else {
118                __darwintrace_stderr = stderr;
119        }
120}
121
122/**
123 * Return false if str doesn't begin with prefix, true otherwise.
124 */
125static inline bool __darwintrace_strbeginswith(const char *str, const char *prefix) {
126        char s;
127        char p;
128        do {
129                s = *str++;
130                p = *prefix++;
131        } while (p && (p == s));
132        return (p == '\0');
133}
134
135/**
136 * This function checks that envp contains the global variables we had when the
137 * library was loaded and modifies it if it doesn't. Returns a malloc(3)'d copy
138 * of envp where the appropriate values have been restored. The caller should
139 * pass the returned pointer to free(3) if necessary to avoid leaks.
140 */
141static inline char **restore_env(char *const envp[]) {
142        // we can re-use pre-allocated strings from store_env
143        char *dyld_insert_libraries_ptr     = __env_full_dyld_insert_libraries;
144        char *dyld_force_flat_namespace_ptr = __env_full_dyld_force_flat_namespace;
145        char *darwintrace_log_ptr           = __env_full_darwintrace_log;
146
147        char *const *enviter = envp;
148        size_t envlen = 0;
149        char **copy;
150        char **copyiter;
151
152        while (enviter != NULL && *enviter != NULL) {
153                envlen++;
154                enviter++;
155        }
156
157        // 4 is sufficient for the three variables we copy and the terminator
158        copy = malloc(sizeof(char *) * (envlen + 4));
159
160        enviter  = envp;
161        copyiter = copy;
162
163        while (enviter != NULL && *enviter != NULL) {
164                char *val = *enviter;
165                if (__darwintrace_strbeginswith(val, "DYLD_INSERT_LIBRARIES=")) {
166                        val = dyld_insert_libraries_ptr;
167                        dyld_insert_libraries_ptr = NULL;
168                } else if (__darwintrace_strbeginswith(val, "DYLD_FORCE_FLAT_NAMESPACE=")) {
169                        val = dyld_force_flat_namespace_ptr;
170                        dyld_force_flat_namespace_ptr = NULL;
171                } else if (__darwintrace_strbeginswith(val, "DARWINTRACE_LOG=")) {
172                        val = darwintrace_log_ptr;
173                        darwintrace_log_ptr = NULL;
174                }
175
176                if (val) {
177                        *copyiter++ = val;
178                }
179
180                enviter++;
181        }
182
183        if (dyld_insert_libraries_ptr) {
184                *copyiter++ = dyld_insert_libraries_ptr;
185        }
186        if (dyld_force_flat_namespace_ptr) {
187                *copyiter++ = dyld_force_flat_namespace_ptr;
188        }
189        if (darwintrace_log_ptr) {
190                *copyiter++ = darwintrace_log_ptr;
191        }
192
193        *copyiter = 0;
194
195        return copy;
196}
197
198/**
199 * Helper function that opens the file indicated by \a path, checks whether it
200 * is a script (i.e., contains a shebang line) and verifies the interpreter is
201 * within the sandbox bounds.
202 *
203 * \param[in] path The path of the file to be executed
204 * \return 0, if access should be granted, a non-zero error code to be stored
205 *         in \c errno otherwise
206 */
207static inline int check_interpreter(const char *restrict path) {
208#define open(x,y,z) syscall(SYS_open, (x), (y), (z))
209#define close(x) syscall(SYS_close, (x))
210        int fd = open(path, O_RDONLY, 0);
211        if (fd <= 0) {
212                return errno;
213        }
214
215        char buffer[MAXPATHLEN + 1 + 2];
216        ssize_t bytes_read;
217
218        /* Read the file for the interpreter. Fortunately, on OS X:
219         *   The system guarantees to read the number of bytes requested if
220         *   the descriptor references a normal file that has that many
221         *   bytes left before the end-of-file, but in no other case.
222         * That _does_ save us another ugly loop to get things right. */
223        bytes_read = read(fd, buffer, sizeof(buffer) - 1);
224        buffer[bytes_read] = '\0';
225        close(fd);
226
227        const char *buffer_end = buffer + bytes_read;
228        if (bytes_read > 2 && buffer[0] == '#' && buffer[1] == '!') {
229                char *interp = buffer + 2;
230
231                /* skip past leading whitespace */
232                while (interp < buffer_end && isblank(*interp)) {
233                        ++interp;
234                }
235                /* found interpreter (or ran out of data); skip until next
236                 * whitespace, then terminate the string */
237                if (interp < buffer_end) {
238                        char *interp_end = interp;
239                        strsep(&interp_end, " \t");
240                }
241
242                /* check the iterpreter against the sandbox */
243                if (!__darwintrace_is_in_sandbox(interp, DT_REPORT | DT_ALLOWDIR | DT_FOLLOWSYMS)) {
244                        return ENOENT;
245                }
246        }
247
248        return 0;
249#undef open
250#undef close
251}
252
253/**
254 * Wrapper for \c execve(2). Denies access and simulates the file does not
255 * exist, if it's outside the sandbox. Also checks for potential interpreters
256 * using \c check_interpreter.
257 */
258static int _dt_execve(const char *path, char *const argv[], char *const envp[]) {
259#define execve(x,y,z) syscall(SYS_execve, (x), (y), (z))
260        __darwintrace_setup();
261
262        int result = 0;
263
264        if (!__darwintrace_is_in_sandbox(path, DT_REPORT | DT_ALLOWDIR | DT_FOLLOWSYMS)) {
265                errno = ENOENT;
266                result = -1;
267        } else {
268                int interp_result = check_interpreter(path);
269                if (interp_result != 0) {
270                        errno = interp_result;
271                        result = -1;
272                } else {
273                        // Since \c execve(2) will likely not return, log before calling
274                        debug_printf("execve(%s) = ?\n", path);
275
276                        // Our variables won't survive exec, clean up
277                        __darwintrace_close();
278                        __darwintrace_pid = (pid_t) -1;
279
280                        // Call the original execve function, but restore environment
281                        char **newenv = restore_env(envp);
282                        result = sip_copy_execve(path, argv, newenv);
283                        free(newenv);
284                }
285        }
286
287        debug_printf("execve(%s) = %d\n", path, result);
288
289        return result;
290#undef execve
291}
292
293DARWINTRACE_INTERPOSE(_dt_execve, execve);
294
295#if defined(HAVE_SPAWN_H) && defined(HAVE_POSIX_SPAWN)
296/**
297 * Wrapper for \c posix_spawn(2). Denies access and simulates the file does not
298 * exist, if it's outside the sandbox. Also checks for potential interpreters
299 * using \c check_interpreter.
300 */
301static int _dt_posix_spawn(pid_t *restrict pid, const char *restrict path, const posix_spawn_file_actions_t *file_actions,
302                const posix_spawnattr_t *restrict attrp, char *const argv[restrict], char *const envp[restrict]) {
303        __darwintrace_setup();
304
305        int result = 0;
306
307        if (!__darwintrace_is_in_sandbox(path, DT_REPORT | DT_ALLOWDIR | DT_FOLLOWSYMS)) {
308                result = ENOENT;
309        } else {
310                int interp_result = check_interpreter(path);
311                if (interp_result != 0) {
312                        result = interp_result;
313                } else {
314                        short attrflags;
315                        if (   attrp != NULL
316                                && posix_spawnattr_getflags(attrp, &attrflags) == 0
317                                && (attrflags & POSIX_SPAWN_SETEXEC) > 0) {
318                                // Apple-specific extension: This call will not return, but
319                                // behave like execve(2). Since our variables won't survive
320                                // that, clean up. Also log the call, because we likely won't
321                                // be able to after the call.
322                                debug_printf("execve(%s) = ?\n", path);
323
324                                __darwintrace_close();
325                                __darwintrace_pid = (pid_t) - 1;
326                        }
327
328                        /* ATTN: the mac syscall you get from syscall(SYS_posix_spawn) corresponds
329                         * to __posix_spawn from /usr/lib/system/libsystem_kernel.dylib. We can not
330                         * override __posix_spawn directly, because it is called from posix_spawn
331                         * inside the same library (i.e., there is no dyld stub we can override).
332                         *
333                         * We cannot override posix_spawn and call __posix_spawn from it
334                         * either, because that will fail with an invalid argument. Thus,
335                         * we need to call the original posix_spawn from here. */
336                        // call the original posix_spawn function, but restore environment
337                        char **newenv = restore_env(envp);
338                        result = sip_copy_posix_spawn(pid, path, file_actions, attrp, argv, newenv);
339                        free(newenv);
340                }
341        }
342
343        debug_printf("posix_spawn(%s) = %d\n", path, result);
344
345        return result;
346}
347
348DARWINTRACE_INTERPOSE(_dt_posix_spawn, posix_spawn);
349#endif
Note: See TracBrowser for help on using the repository browser.