source: trunk/base/src/pextlib1.0/system.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:keywords set to Id
File size: 11.1 KB
Line 
1/* vim: set et sw=4 ts=4 sts=4: */
2/*
3 * system.c
4 * $Id: system.c 141420 2015-10-18 00:49:32Z cal@macports.org $
5 *
6 * Copyright (c) 2002 - 2003 Apple, Inc.
7 * Copyright (c) 2008 - 2010, 2012 The MacPorts Project
8 * All rights reserved.
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 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of The MacPorts Project nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 */
34
35#if HAVE_CONFIG_H
36#include <config.h>
37#endif
38
39/* required for fdopen(3)/seteuid(2), among others */
40#define _XOPEN_SOURCE 600
41/* required for fgetln(3) on OS X */
42#define _DARWIN_C_SOURCE
43
44#include <tcl.h>
45
46#if HAVE_PATHS_H
47#include <paths.h>
48#endif
49
50#include <sys/types.h>
51#include <sys/wait.h>
52#include <sys/resource.h>
53#include <fcntl.h>
54#include <stdlib.h>
55#include <string.h>
56#include <unistd.h>
57#include <limits.h>
58#include <errno.h>
59
60#include "system.h"
61#include "sip_copy_proc.h"
62#include "Pextlib.h"
63
64#if HAVE_CRT_EXTERNS_H
65#include <crt_externs.h>
66#define environ (*_NSGetEnviron())
67#else
68extern char **environ;
69#endif
70
71#if !HAVE_FGETLN
72char *fgetln(FILE *stream, size_t *len);
73#endif
74
75#ifndef _PATH_DEVNULL
76#define _PATH_DEVNULL "/dev/null"
77#endif
78
79#define CBUFSIZ 30
80
81struct linebuf {
82    size_t len;
83    char *line;
84};
85
86static int check_sandboxing(Tcl_Interp *interp, char **sandbox_exec_path, char **profilestr)
87{
88    Tcl_Obj *tcl_result;
89    int active;
90    int len;
91
92    tcl_result = Tcl_GetVar2Ex(interp, "portsandbox_active", NULL, TCL_GLOBAL_ONLY);
93    if (!tcl_result || Tcl_GetBooleanFromObj(interp, tcl_result, &active) != TCL_OK || !active) {
94        return 0;
95    }
96
97    tcl_result = Tcl_GetVar2Ex(interp, "portutil::autoconf::sandbox_exec_path", NULL, TCL_GLOBAL_ONLY);
98    if (!tcl_result || !(*sandbox_exec_path = Tcl_GetString(tcl_result))) {
99        return 0;
100    }
101
102    tcl_result = Tcl_GetVar2Ex(interp, "portsandbox_profile", NULL, TCL_GLOBAL_ONLY);
103    if (!tcl_result || !(*profilestr = Tcl_GetStringFromObj(tcl_result, &len)) 
104        || len == 0) {
105        return 0;
106    }
107
108    return 1;
109}
110
111/* usage: system ?-notty? ?-nodup? ?-nice value? ?-W path? command */
112int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
113{
114    char *buf;
115    struct linebuf circbuf[CBUFSIZ];
116    size_t linelen;
117    char *args[7];
118    char *cmdstring;
119    int sandbox = 0;
120    char *sandbox_exec_path = NULL;
121    char *profilestr = NULL;
122    FILE *pdes;
123    int fdset[2], nullfd;
124    int fline, pos, ret;
125    int osetsid = 0;
126    int odup = 1; /* redirect stdin/stdout/stderr by default */
127    int oniceval = INT_MAX; /* magic value indicating no change */
128    const char *path = NULL;
129    pid_t pid;
130    uid_t euid;
131    Tcl_Obj *tcl_result;
132    int read_failed = 0;
133    int status;
134    int i;
135
136    if (objc < 2) {
137        Tcl_WrongNumArgs(interp, 1, objv, "?-notty? ?-nice value? ?-W path? command");
138        return TCL_ERROR;
139    }
140
141    cmdstring = Tcl_GetString(objv[objc - 1]);
142
143    for (i = 1; i < objc - 1; i++) {
144        char *arg = Tcl_GetString(objv[i]);
145        if (strcmp(arg, "-notty") == 0) {
146            osetsid = 1;
147        } else if (strcmp(arg, "-nodup") == 0) {
148            odup = 0;
149        } else if (strcmp(arg, "-nice") == 0) {
150            i++;
151            if (Tcl_GetIntFromObj(interp, objv[i], &oniceval) != TCL_OK) {
152                Tcl_SetResult(interp, "invalid value for -nice", TCL_STATIC);
153                return TCL_ERROR;
154            }
155        } else if (strcmp(arg, "-W") == 0) {
156            i++;
157            if ((path = Tcl_GetString(objv[i])) == NULL) {
158                Tcl_SetResult(interp, "invalid value for -W", TCL_STATIC);
159                return TCL_ERROR;
160            }
161        } else {
162            tcl_result = Tcl_NewStringObj("bad option ", -1);
163            Tcl_AppendObjToObj(tcl_result, Tcl_NewStringObj(arg, -1));
164            Tcl_SetObjResult(interp, tcl_result);
165            return TCL_ERROR;
166        }
167    }
168
169    /* print debug command info */
170    if (path) {
171        ui_debug(interp, "system -W %s: %s", path, cmdstring);
172    } else {
173        ui_debug(interp, "system: %s", cmdstring);
174    }
175
176    /* check if and how we should use sandbox-exec */
177    sandbox = check_sandboxing(interp, &sandbox_exec_path, &profilestr);
178
179    /*
180     * Fork a child to run the command, in a popen() like fashion -
181     * popen() itself is not used because stderr is also desired.
182     */
183    if (odup) {
184        if (pipe(fdset) != 0) {
185            Tcl_SetResult(interp, strerror(errno), TCL_STATIC);
186            return TCL_ERROR;
187        }
188    }
189
190    pid = fork();
191    switch (pid) {
192    case -1: /* error */
193        Tcl_SetResult(interp, strerror(errno), TCL_STATIC);
194        return TCL_ERROR;
195        /*NOTREACHED*/
196    case 0: /* child */
197        if (odup) {
198            close(fdset[0]);
199
200            if ((nullfd = open(_PATH_DEVNULL, O_RDONLY)) == -1)
201                _exit(1);
202            dup2(nullfd, STDIN_FILENO);
203            dup2(fdset[1], STDOUT_FILENO);
204            dup2(fdset[1], STDERR_FILENO);
205        }
206        /* drop the controlling terminal if requested */
207        if (osetsid) {
208            if (setsid() == -1)
209                _exit(1);
210        }
211        /* change scheduling priority if requested */
212        if (oniceval != INT_MAX) {
213            if (setpriority(PRIO_PROCESS, (id_t)getpid(), oniceval) != 0) {
214                /* ignore failure, just continue */
215            }
216        }
217        /* drop privileges entirely for child */
218        if (getuid() == 0 && (euid = geteuid()) != 0) {
219            gid_t egid = getegid();
220            if (seteuid(0) || setgid(egid) || setuid(euid)) {
221                _exit(1);
222            }
223        }
224
225        if (path != NULL) {
226            if (chdir(path) == -1) {
227                printf("chdir: %s: %s\n", path, strerror(errno));
228                exit(1);
229            }
230        }
231
232        /* XXX ugly string constants */
233        if (sandbox) {
234            args[0] = "sandbox-exec";
235            args[1] = "-p";
236            args[2] = profilestr;
237            args[3] = "sh";
238            args[4] = "-c";
239            args[5] = cmdstring;
240            args[6] = NULL;
241            sip_copy_execve(sandbox_exec_path, args, environ);
242        } else {
243            args[0] = "sh";
244            args[1] = "-c";
245            args[2] = cmdstring;
246            args[3] = NULL;
247            sip_copy_execve("/bin/sh", args, environ);
248        }
249        exit(128);
250        /*NOTREACHED*/
251    default: /* parent */
252        break;
253    }
254
255    if (odup) {
256        close(fdset[1]);
257
258        /* read from simulated popen() pipe */
259        read_failed = 0;
260        pos = 0;
261        memset(circbuf, 0, sizeof(circbuf));
262        pdes = fdopen(fdset[0], "r");
263        if (pdes) {
264            while ((buf = fgetln(pdes, &linelen)) != NULL) {
265                char *sbuf;
266                size_t slen;
267
268                /*
269                * Allocate enough space to insert a terminating
270                * '\0' if the line is not terminated with a '\n'
271                */
272                if (buf[linelen - 1] == '\n')
273                    slen = linelen;
274                else
275                    slen = linelen + 1;
276
277                if (circbuf[pos].len == 0)
278                    sbuf = malloc(slen);
279                else {
280                    sbuf = realloc(circbuf[pos].line, slen);
281                }
282
283                if (sbuf == NULL) {
284                    read_failed = 1;
285                    break;
286                }
287
288                memcpy(sbuf, buf, linelen);
289                /* terminate line with '\0',replacing '\n' if it exists */
290                sbuf[slen - 1] = '\0';
291
292                circbuf[pos].line = sbuf;
293                circbuf[pos].len = slen;
294
295                if (pos++ == CBUFSIZ - 1) {
296                    pos = 0;
297                }
298
299                ui_info(interp, "%s", sbuf);
300            }
301            fclose(pdes);
302        } else {
303            read_failed = 1;
304            Tcl_SetResult(interp, strerror(errno), TCL_STATIC);
305        }
306    }
307
308    status = TCL_ERROR;
309
310    if (wait(&ret) == pid && (WIFEXITED(ret) || WIFSIGNALED(ret)) && !read_failed) {
311        /* Normal exit, and reading from the pipe didn't fail. */
312        if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
313            status = TCL_OK;
314        } else {
315            Tcl_Obj* errorCode;
316
317            /* print error */
318            ui_info(interp, "Command failed: %s", cmdstring);
319            if (WIFEXITED(ret)) {
320                ui_info(interp, "Exit code: %d", WEXITSTATUS(ret));
321            } else if(WIFSIGNALED(ret)) {
322                ui_info(interp, "Killed by signal: %d", WTERMSIG(ret));
323            }
324
325            errorCode = Tcl_NewListObj(0, NULL);
326            if (WIFEXITED(ret)) {
327                /* set errorCode [list CHILDSTATUS <pid> <code>] */
328                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("CHILDSTATUS", -1));
329                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(pid));
330                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(WEXITSTATUS(ret)));
331                Tcl_SetObjErrorCode(interp, errorCode);
332            } else if (WIFSIGNALED(ret)) {
333                /* set errorCode [list CHILDKILLED <pid> <SIGNAME> <signal descripton>] */
334                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("CHILDKILLED", -1));
335                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(pid));
336                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj(Tcl_SignalId(WTERMSIG(ret)), -1));
337                Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj(Tcl_SignalMsg(WTERMSIG(ret)), -1));
338                Tcl_SetObjErrorCode(interp, errorCode);
339            }
340
341            Tcl_SetObjResult(interp, Tcl_NewStringObj("command execution failed", -1));
342        }
343    }
344
345    if (odup) {
346        /* Cleanup. */
347        close(fdset[0]);
348        for (fline = 0; fline < CBUFSIZ; fline++) {
349            if (circbuf[fline].len != 0) {
350                free(circbuf[fline].line);
351            }
352        }
353    }
354
355    return status;
356}
Note: See TracBrowser for help on using the repository browser.