<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[141420] trunk/base</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="https://trac.macports.org/changeset/141420">141420</a></dd>
<dt>Author</dt> <dd>cal@macports.org</dd>
<dt>Date</dt> <dd>2015-10-17 17:49:32 -0700 (Sat, 17 Oct 2015)</dd>
</dl>
<h3>Log Message</h3>
<pre>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.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbaseconfigureac">trunk/base/configure.ac</a></li>
<li><a href="#trunkbasedocbasemtreein">trunk/base/doc/base.mtree.in</a></li>
<li><a href="#trunkbasesrcdarwintracelib10Makefilein">trunk/base/src/darwintracelib1.0/Makefile.in</a></li>
<li><a href="#trunkbasesrcdarwintracelib10procc">trunk/base/src/darwintracelib1.0/proc.c</a></li>
<li><a href="#trunkbasesrcpextlib10Makefilein">trunk/base/src/pextlib1.0/Makefile.in</a></li>
<li><a href="#trunkbasesrcpextlib10systemc">trunk/base/src/pextlib1.0/system.c</a></li>
<li><a href="#trunkbasesrcport10portsandboxtcl">trunk/base/src/port1.0/portsandbox.tcl</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunkbasesrcpextlib10sip_copy_procc">trunk/base/src/pextlib1.0/sip_copy_proc.c</a></li>
<li><a href="#trunkbasesrcpextlib10sip_copy_proch">trunk/base/src/pextlib1.0/sip_copy_proc.h</a></li>
</ul>
<h3>Property Changed</h3>
<ul>
<li><a href="#trunkbasesrcdarwintracelib10">trunk/base/src/darwintracelib1.0/</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbaseconfigureac"></a>
<div class="modfile"><h4>Modified: trunk/base/configure.ac (141419 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/configure.ac        2015-10-18 00:49:18 UTC (rev 141419)
+++ trunk/base/configure.ac        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -312,6 +312,14 @@
</span><span class="cx"> eval "prefix_expanded=$prefix"
</span><span class="cx"> eval "exec_prefix_expanded=$exec_prefix"
</span><span class="cx"> AC_SUBST(prefix_expanded)
</span><ins>+
+# Define a path where our trace mode workaround for SIP-protected binaries on
+# >= 10.11 puts copies
+AC_DEFINE_UNQUOTED(
+        [DARWINTRACE_SIP_WORKAROUND_PATH],
+        ["${prefix}/var/macports/sip-workaround"],
+        [Absolute path to a directory used by darwintrace to copy SIP-protected files before executing])
+
</ins><span class="cx"> # do this twice, since there is a nested variable of
</span><span class="cx"> # ${prefix} inside of ${sysconfdir}
</span><span class="cx"> eval "MPCONFIGDIR_EXPANDED=$MPCONFIGDIR"
</span></span></pre></div>
<a id="trunkbasedocbasemtreein"></a>
<div class="modfile"><h4>Modified: trunk/base/doc/base.mtree.in (141419 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/doc/base.mtree.in        2015-10-18 00:49:18 UTC (rev 141419)
+++ trunk/base/doc/base.mtree.in        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -23,6 +23,8 @@
</span><span class="cx"> ..
</span><span class="cx"> registry
</span><span class="cx"> ..
</span><ins>+ sip-workaround mode=01777
+ ..
</ins><span class="cx"> software
</span><span class="cx"> ..
</span><span class="cx"> ..
</span></span></pre></div>
<a id="trunkbasesrcdarwintracelib10"></a>
<div class="propset"><h4>Property changes: trunk/base/src/darwintracelib1.0</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnignore"></a>
<div class="modfile"><h4>Modified: svn:ignore</h4></div>
<span class="cx">darwintrace.dylib
</span><span class="cx"> + *.d
</span><span class="cx">Makefile
</span><span class="cx">darwintrace.dylib
</span><span class="cx">sip_copy_proc.c
</span><span class="cx">sip_copy_proc.h
</span><a id="trunkbasesrcdarwintracelib10Makefilein"></a>
<div class="modfile"><h4>Modified: trunk/base/src/darwintracelib1.0/Makefile.in (141419 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/darwintracelib1.0/Makefile.in        2015-10-18 00:49:18 UTC (rev 141419)
+++ trunk/base/src/darwintracelib1.0/Makefile.in        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -5,30 +5,62 @@
</span><span class="cx">
</span><span class="cx"> # This Makefile will only be run on Darwin systems; we can safely use
</span><span class="cx"> # Apple-specifics here
</span><del>-SRCS=                $(wildcard *.c)
-OBJS=                $(SRCS:%.c=%.o)
-SHLIB_NAME=        darwintrace${SHLIB_SUFFIX}
-INSTALLDIR=        ${DESTDIR}${TCL_PACKAGE_PATH}/darwintrace1.0
</del><ins>+SRCS = \
+        access.c \
+        close.c \
+        darwintrace.c \
+        dup2.c \
+        mkdir.c \
+        open.c \
+        proc.c \
+        readdir.c \
+        readlink.c \
+        rename.c \
+        rmdir.c \
+        sip_copy_proc.c \
+        stat.c \
+        unlink.c
</ins><span class="cx">
</span><ins>+OBJS = $(SRCS:%.c=%.o)
+SHLIB_NAME = darwintrace$(SHLIB_SUFFIX)
+INSTALLDIR = $(DESTDIR)$(TCL_PACKAGE_PATH)/darwintrace1.0
+
</ins><span class="cx"> # Yes, we know having $ signs in identifiers is not a very good idea; in the
</span><span class="cx"> # case of darwintrace we still need them, though.
</span><del>-CFLAGS_PEDANTIC=
-CFLAGS+= -fPIC ${UNIVERSAL_ARCHFLAGS}
-SHLIB_LDFLAGS+=${UNIVERSAL_ARCHFLAGS}
</del><ins>+CFLAGS_PEDANTIC =
+CFLAGS += -fPIC $(UNIVERSAL_ARCHFLAGS)
+SHLIB_LDFLAGS += $(UNIVERSAL_ARCHFLAGS)
</ins><span class="cx">
</span><del>-all:: ${SHLIB_NAME}
</del><ins>+# Generate dependency information
+CPPFLAGS += -MMD -MP
</ins><span class="cx">
</span><del>-$(SHLIB_NAME):: ${OBJS}
-        ${SHLIB_LD} ${OBJS} -o ${SHLIB_NAME} ${SHLIB_LDFLAGS} ${LIBS}
</del><ins>+all:: $(SHLIB_NAME)
</ins><span class="cx">
</span><ins>+# Copy sip_copy_proc.{c,h} from pextlib1.0 where they are also needed
+sip_copy_proc.c: ../pextlib1.0/sip_copy_proc.c sip_copy_proc.h
+        cp $< $@
+
+sip_copy_proc.h: ../pextlib1.0/sip_copy_proc.h
+        cp $< $@
+
+# This won't be automatically detected during the first run of make, where the
+# .d files do not exist yet
+proc.c: sip_copy_proc.h
+
+$(SHLIB_NAME):: $(OBJS)
+        $(SHLIB_LD) $(OBJS) -o $(SHLIB_NAME) $(SHLIB_LDFLAGS) $(LIBS)
+
</ins><span class="cx"> clean::
</span><del>-        rm -f ${OBJS} ${SHLIB_NAME} so_locations
</del><ins>+        rm -f $(OBJS) $(SHLIB_NAME) so_locations sip_copy_proc.c sip_copy_proc.h $(SRCS:%.c=%.d)
</ins><span class="cx">
</span><span class="cx"> distclean:: clean
</span><span class="cx">         rm -f Makefile
</span><span class="cx">
</span><span class="cx"> install:: all
</span><del>-        $(INSTALL) -d -o "${DSTUSR}" -g "${DSTGRP}" -m "${DSTMODE}" "${INSTALLDIR}"
-        $(INSTALL) -o "${DSTUSR}" -g "${DSTGRP}" -m 444 "${SHLIB_NAME}" "${INSTALLDIR}"
</del><ins>+        $(INSTALL) -d -o "$(DSTUSR)" -g "$(DSTGRP)" -m "$(DSTMODE)" "$(INSTALLDIR)"
+        $(INSTALL) -o "$(DSTUSR)" -g "$(DSTGRP)" -m 444 "$(SHLIB_NAME)" "$(INSTALLDIR)"
</ins><span class="cx">
</span><span class="cx"> test::
</span><ins>+
+# Include dependency information
+-include $(SRCS:%.c=%.d)
</ins></span></pre></div>
<a id="trunkbasesrcdarwintracelib10procc"></a>
<div class="modfile"><h4>Modified: trunk/base/src/darwintracelib1.0/proc.c (141419 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/darwintracelib1.0/proc.c        2015-10-18 00:49:18 UTC (rev 141419)
+++ trunk/base/src/darwintracelib1.0/proc.c        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -37,6 +37,7 @@
</span><span class="cx">
</span><span class="cx"> #define DARWINTRACE_USE_PRIVATE_API 1
</span><span class="cx"> #include "darwintrace.h"
</span><ins>+#include "sip_copy_proc.h"
</ins><span class="cx">
</span><span class="cx"> #include <ctype.h>
</span><span class="cx"> #include <dlfcn.h>
</span><span class="lines">@@ -278,7 +279,7 @@
</span><span class="cx">
</span><span class="cx">                         // Call the original execve function, but restore environment
</span><span class="cx">                         char **newenv = restore_env(envp);
</span><del>-                        result = execve(path, argv, newenv);
</del><ins>+                        result = sip_copy_execve(path, argv, newenv);
</ins><span class="cx">                         free(newenv);
</span><span class="cx">                 }
</span><span class="cx">         }
</span><span class="lines">@@ -334,7 +335,7 @@
</span><span class="cx">                          * we need to call the original posix_spawn from here. */
</span><span class="cx">                         // call the original posix_spawn function, but restore environment
</span><span class="cx">                         char **newenv = restore_env(envp);
</span><del>-                        result = posix_spawn(pid, path, file_actions, attrp, argv, newenv);
</del><ins>+                        result = sip_copy_posix_spawn(pid, path, file_actions, attrp, argv, newenv);
</ins><span class="cx">                         free(newenv);
</span><span class="cx">                 }
</span><span class="cx">         }
</span></span></pre></div>
<a id="trunkbasesrcpextlib10Makefilein"></a>
<div class="modfile"><h4>Modified: trunk/base/src/pextlib1.0/Makefile.in (141419 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/pextlib1.0/Makefile.in        2015-10-18 00:49:18 UTC (rev 141419)
+++ trunk/base/src/pextlib1.0/Makefile.in        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -4,11 +4,32 @@
</span><span class="cx"> include ../../Mk/macports.autoconf.mk
</span><span class="cx">
</span><span class="cx"> OBJS= \
</span><del>-        Pextlib.o strsed.o fgetln.o md5cmd.o setmode.o xinstall.o \
-        fs-traverse.o strcasecmp.o vercomp.o filemap.o base32cmd.o \
-        sha1cmd.o curl.o rmd160cmd.o sha256cmd.o readline.o uid.o \
-        tracelib.o tty.o readdir.o pipe.o adv-flock.o system.o \
-        mktemp.o realpath.o
</del><ins>+        Pextlib.o \
+        adv-flock.o \
+        base32cmd.o \
+        curl.o \
+        fgetln.o \
+        filemap.o \
+        fs-traverse.o \
+        md5cmd.o \
+        mktemp.o \
+        pipe.o \
+        readdir.o \
+        readline.o \
+        realpath.o \
+        rmd160cmd.o \
+        setmode.o \
+        sha1cmd.o \
+        sha256cmd.o \
+        sip_copy_proc.o \
+        strcasecmp.o \
+        strsed.o \
+        system.o \
+        tracelib.o \
+        tty.o \
+        uid.o \
+        vercomp.o \
+        xinstall.o
</ins><span class="cx"> ifneq (@ac_cv_func_strlcat@,yes)
</span><span class="cx"> OBJS+=strlcat.o
</span><span class="cx"> endif
</span></span></pre></div>
<a id="trunkbasesrcpextlib10sip_copy_procc"></a>
<div class="addfile"><h4>Added: trunk/base/src/pextlib1.0/sip_copy_proc.c (0 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/pextlib1.0/sip_copy_proc.c         (rev 0)
+++ trunk/base/src/pextlib1.0/sip_copy_proc.c        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -0,0 +1,503 @@
</span><ins>+/* vim: set et sw=4 ts=4 sts=4: */
+/*
+ * sip_copy_proc.c
+ * $Id$
+ *
+ * Copyright (c) 2015 Clemens Lang <cal@macports.org>
+ * Copyright (c) 2015 The MacPorts Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The MacPorts Project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _DARWIN_FEATURE_64_BIT_INODE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "sip_copy_proc.h"
+
+#include <config.h>
+#ifndef DARWINTRACE_SIP_WORKAROUND_PATH
+#warning No value for DARWINTRACE_SIP_WORKAROUND_PATH found in config.h, using default of /tmp/macports-sip, which will fail unless you create it with mode 01777
+#define DARWINTRACE_SIP_WORKAROUND_PATH "/tmp/macports-sip"
+#endif
+
+/**
+ * Frees an array of strings and the array itself.
+ */
+static void free_argv(char *argv[]) {
+        char **arg = argv;
+        while (arg && *arg) {
+                free(*arg);
+                *arg = NULL;
+                arg++;
+        }
+
+        free(argv);
+}
+
+typedef enum _copy_needed_return_t {
+ copy_needed_error,
+ copy_not_needed,
+ copy_is_needed
+} copy_needed_return_t;
+
+/**
+ * Helper function to determine whether the binary indicated by \a path
+ * supports library injection using DYLD_INSERT_LIBRARIES directly or needs to
+ * be copied to a temporary path to support it.
+ *
+ * The following conditions must be fulfilled for the copy to be necessary:
+ * - \a environ needs to contain a variable that starts with
+ * DYLD_INSERT_LIBRARIES
+ * - If the file at \a path has a shebang, its shebang line will be read and
+ * the following checks will be done against the interpreter binary.
+ * Additionally, if the copy is necessary, the arguments given in \a argc
+ * (the number of arguments) and \a argv (the arguments itself) will be
+ * prefixed with the command and arguments from the shebang line. The
+ * original first argument will be replaced with \a path to make sure it is
+ * absolute.
+ * - \a path (or the interpreter given in the shebang of path) must have the
+ * \c SF_RESTRICTED flag set.
+ * - \a path (or the interpreter given in the shebang of path) must not be
+ * SUID or SGID.
+ *
+ * @param path The absolute path of the binary to be executed
+ * @param argc The number of arguments passed in \a argv
+ * @param argv The arguments to be passed to the file to be executed
+ * @param outargv Pointer to a modified array of arguments. Only valid if \c
+ * copy_is_needed is returned. May be \c NULL, in which case no
+ * modifications to the original \c argv were necessary. If
+ * non-null, a dynamically allocated array of dynamically
+ * allocated elements. The last element of the array is \c NULL,
+ * which makes \c *outargv suitable for passing to execve(2).
+ * Note that instead of the given \c path, you should pass \c
+ * (*outargv)[0] to execve(2) as first argument.
+ * @param environ The environment for the program to be started. Will be
+ * checked for the presence of a DYLD_INSERT_LIBRARIES variable.
+ * @param st Pointer to struct stat that will contain information about \c
+ * path, or of \c outargv isn't \c NULL, about \c (*outargv)[0]. This
+ * can be used to determine metadata of the file such as modification
+ * time and size to avoid unnecessary copies.
+ * @return \c copy_isneeded iff a copy is required. \c copy_not_needed if a copy
+ * is not needed. \c copy_needed_error on error, where errno will be
+ * set.
+ */
+static copy_needed_return_t copy_needed(const char *path, char *const argv[],
+ char **outargv[], char *const environ[], struct stat *st) {
+#ifndef SF_RESTRICTED /* no system integrity protection */
+        return copy_not_needed;
+#else /* defined(SF_RESTRICTED) */
+        // check whether DYLD_INSERT_LIBRARIES is set
+        bool dyld_insert_libraries_present = false;
+        char *const *env = environ;
+        while (env && *env) {
+                if (strncmp("DYLD_INSERT_LIBRARIES=", *env, strlen("DYLD_INSERT_LIBRARIES=")) == 0) {
+                        dyld_insert_libraries_present = true;
+                        break;
+                }
+                env++;
+        }
+        // if we didn't find DYLD_INSERT_LIBRARIES, a copy isn't needed
+        if (!dyld_insert_libraries_present) {
+                return copy_not_needed;
+        }
+
+        // open file to check for shebangs
+        const char *realpath = path;
+        size_t new_argc = 0;
+        char **new_argv = NULL;
+        FILE *f = fopen(path, "r");
+        if (!f) {
+                // if opening fails we won't be able to copy anyway
+                return copy_not_needed;
+        }
+
+        /* no error checking for fgetc(3) here, because this isn't a shebang if an
+         * error occurs */
+        if (fgetc(f) == '#' && fgetc(f) == '!') {
+                /* This is an interpreted script. The interpreter's flags are what
+                 * affects whether DYLD_* is stripped, so read the interpreter's path
+                 * from the file to check that instead. Additionally, read any flags
+                 * that may be passed to the interpreter, since we'll have to do the
+                 * shebang expansion in user space if we move the interpreter. */
+                char *linep = NULL;
+                size_t linecapp = 0;
+                // read first line to get the interpreter and its arguments
+                if (getline(&linep, &linecapp, f) > 0) {
+                        char *ctxt;
+ char *word;
+ size_t idx;
+                        // do word splitting on the interpreter line and store it in new_argv
+                        for (idx = 0, word = strtok_r(linep, " \t\n", &ctxt);
+                                        word != NULL;
+                                        idx++, word = strtok_r(NULL, " \t\n", &ctxt)) {
+                                // make sure we have enough space allocated
+                                if (new_argv == NULL) {
+                                        if ((new_argv = malloc(2 * sizeof(*new_argv))) == NULL) {
+ free(linep);
+ return copy_needed_error;
+                                        }
+                                        new_argc = 1;
+
+                                        // new_argv[0] will be overwritten in a second
+                                        // new_argv[1] is the terminating NULL
+                                        new_argv[0] = NULL;
+                                        new_argv[1] = NULL;
+                                } else if (idx >= new_argc) {
+                                        // realloc to increase the size
+                                        char **oldargv = new_argv;
+                                        if ((new_argv = realloc(oldargv, (idx + 2) * sizeof(*new_argv))) == NULL) {
+                                                free_argv(oldargv);
+ free(linep);
+ return copy_needed_error;
+                                        }
+                                        new_argc = idx + 1;
+                                }
+
+                                // store a copy of the word in new_argv
+                                new_argv[idx] = strdup(word);
+                                if (!new_argv[idx]) {
+                                        free_argv(new_argv);
+ free(linep);
+ return copy_needed_error;
+                                }
+ new_argv[idx + 1] = NULL;
+                        }
+
+                        free(linep);
+
+                        if (new_argv && *new_argv) {
+                                // interpreter found, check that instead of given path
+                                realpath = *new_argv;
+                        }
+                }
+        }
+
+        // check whether the binary has SF_RESTRICTED and isn't SUID/SGID
+        if (-1 == stat(realpath, st)) {
+                // on error, return and let execve(2) deal with it
+                free_argv(new_argv);
+                return copy_not_needed;
+        } else {
+                if (!(st->st_flags & SF_RESTRICTED)) {
+                        // no SIP on this binary
+                        free_argv(new_argv);
+                        return copy_not_needed;
+                }
+                if ((st->st_flags & (S_ISUID | S_ISGID)) > 0) {
+                        // the binary is SUID/SGID, which would get lost when copying;
+                        // DYLD_ variables are stripped for SUID/SGID binaries anyway
+                        free_argv(new_argv);
+                        return copy_not_needed;
+                }
+        }
+
+        // prefix the shebang line to the original argv
+        if (new_argv != NULL) {
+ size_t argc = 0;
+ for (char *const *argvwalk = argv; argvwalk && *argvwalk; ++argvwalk) {
+ argc++;
+ }
+
+                // realloc to increase the size
+                char **oldargv = new_argv;
+                if ((new_argv = realloc(oldargv, (new_argc + argc + 1) * sizeof(*new_argv))) == NULL) {
+                        free_argv(oldargv);
+ return copy_needed_error;
+                }
+
+                new_argv[new_argc] = strdup(path);
+                if (!new_argv[new_argc]) {
+ free_argv(new_argv);
+ return copy_needed_error;
+                }
+ new_argv[new_argc + 1] = NULL;
+
+                for (size_t idx = 1; idx < argc; ++idx) {
+                        new_argv[new_argc + idx] = strdup(argv[idx]);
+                        if (!new_argv[new_argc + idx]) {
+ free_argv(new_argv);
+ return copy_needed_error;
+                        }
+ new_argv[new_argc + idx + 1] = NULL;
+                }
+
+                new_argc = new_argc + argc;
+
+                *outargv = new_argv;
+        }
+
+        return copy_is_needed;
+#endif /* defined(SF_RESTRICTED) */
+}
+
+static char *lazy_copy(const char *path, struct stat *in_st) {
+ char *retval = NULL;
+ uid_t euid = geteuid();
+ int outfd = -1;
+ int infd = -1;
+
+ char *target_folder = NULL;
+ char *target_path = NULL;
+ char *target_path_temp = NULL;
+ char *dir = strdup(path);
+ if (!dir) {
+ goto lazy_copy_out;
+ }
+ char *endslash = strrchr(dir, '/');
+ if (endslash) {
+ *endslash = '\0';
+ }
+
+ if (-1 == asprintf(&target_folder, "%s/%lu%s", DARWINTRACE_SIP_WORKAROUND_PATH, (unsigned long) euid, dir)) {
+ goto lazy_copy_out;
+ }
+
+ if (-1 == asprintf(&target_path, "%s/%lu%s", DARWINTRACE_SIP_WORKAROUND_PATH, (unsigned long) euid, path)) {
+ goto lazy_copy_out;
+ }
+
+ if (-1 == asprintf(&target_path_temp, "%s/%lu/.XXXXXXXXXXXXXX", DARWINTRACE_SIP_WORKAROUND_PATH, (unsigned long) euid)) {
+ goto lazy_copy_out;
+ }
+
+ // ensure directory exists
+ char *pos = target_folder + strlen(DARWINTRACE_SIP_WORKAROUND_PATH);
+ while (pos && *pos) {
+ *pos = '\0';
+ if (-1 == mkdir(target_folder, 0755) && errno != EEXIST) {
+ fprintf(stderr, "sip_copy_proc: mkdir(%s): %s\n", target_folder, strerror(errno));
+ goto lazy_copy_out;
+ }
+ *pos = '/';
+ pos++;
+ pos = strchr(pos, '/');
+ }
+ if (-1 == mkdir(target_folder, 0755) && errno != EEXIST) {
+ fprintf(stderr, "sip_copy_proc: mkdir(%s): %s\n", target_folder, strerror(errno));
+ goto lazy_copy_out;
+ }
+
+ // check whether copying is needed; it isn't if the file exists and the
+ // modification times match
+ struct stat out_st;
+ if ( -1 != stat(target_path, &out_st)
+ && in_st->st_mtimespec.tv_sec == out_st.st_mtimespec.tv_sec
+ && in_st->st_mtimespec.tv_nsec == out_st.st_mtimespec.tv_nsec) {
+ // copying not needed
+ retval = target_path;
+ goto lazy_copy_out;
+ }
+
+ // create temporary file to copy into and then later atomically replace
+ // target file
+ if (-1 == (outfd = mkstemp(target_path_temp))) {
+ fprintf(stderr, "sip_copy_proc: mkstemp(%s): %s\n", target_path_temp, strerror(errno));
+ goto lazy_copy_out;
+ }
+
+ if (-1 == (infd = open(path, O_RDONLY | O_CLOEXEC))) {
+ fprintf(stderr, "sip_copy_proc: open(%s, O_RDONLY | O_CLOEXEC): %s\n", path, strerror(errno));
+ goto lazy_copy_out;
+ }
+
+ // ensure mode is copied
+ if (-1 == fchmod(outfd, in_st->st_mode)) {
+ fprintf(stderr, "sip_copy_proc: fchmod(%s, %o): %s\n", target_path_temp, in_st->st_mode, strerror(errno));
+ goto lazy_copy_out;
+ }
+
+ char *buf = malloc(in_st->st_blksize);
+ ssize_t bytes_read = 0;
+ ssize_t bytes_written = 0;
+ bool error = false;
+ do {
+ bytes_read = read(infd, buf, in_st->st_blksize);
+ if (bytes_read < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ } else {
+ error = true;
+ break;
+ }
+ }
+ if (bytes_read == 0) {
+ // EOF
+ break;
+ }
+
+ bytes_written = 0;
+ while (bytes_written < bytes_read) {
+ ssize_t written = write(outfd, buf + bytes_written, bytes_read - bytes_written);
+ if (written < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ error = true;
+ break;
+ }
+
+ bytes_written += written;
+ }
+ } while (!error);
+ if (bytes_read < 0 || bytes_written < 0) {
+ goto lazy_copy_out;
+ }
+
+ struct timeval times[2];
+ TIMESPEC_TO_TIMEVAL(&times[0], &in_st->st_mtimespec);
+ TIMESPEC_TO_TIMEVAL(&times[1], &in_st->st_mtimespec);
+ if (-1 == futimes(outfd, times)) {
+ fprintf(stderr, "sip_copy_proc: futimes(%s): %s\n", target_path_temp, strerror(errno));
+ goto lazy_copy_out;
+ }
+
+ if (-1 == rename(target_path_temp, target_path)) {
+ fprintf(stderr, "sip_copy_proc: rename(%s, %s): %s\n", target_path_temp, target_path, strerror(errno));
+ goto lazy_copy_out;
+ }
+
+ retval = target_path;
+
+lazy_copy_out:
+ {
+ int errno_save = errno;
+ close(outfd);
+ close(infd);
+ if (target_path_temp != NULL && -1 == unlink(target_path_temp) && errno != ENOENT) {
+ fprintf(stderr, "sip_copy_proc: unlink(%s): %s\n", target_path_temp, strerror(errno));
+ retval = NULL;
+ } else {
+ errno = errno_save;
+ }
+ }
+ free(dir);
+ free(target_path_temp);
+ free(target_folder);
+ if (retval != target_path) {
+ free(target_path);
+ }
+ return retval;
+}
+
+/**
+ * Behaves like execve(2), but checks whether trace mode is enabled (by
+ * checking for DYLD_INSERT_LIBRARIES in the environment) and the binary is
+ * covered by 10.11's new system integrity protection. If it is, the binary
+ * will be copied to a separate folder (or updated if already there and
+ * modification time differs) and executed from there.
+ */
+int sip_copy_execve(const char *path, char *const argv[], char *const envp[]) {
+ char **outargv = NULL;
+ struct stat st;
+
+ copy_needed_return_t need_copy = copy_needed(path, argv, &outargv, envp, &st);
+ switch (need_copy) {
+ case copy_needed_error:
+ return -1;
+ break;
+ case copy_not_needed:
+ return execve(path, argv, envp);
+ break;
+ case copy_is_needed: {
+ const char *to_be_copied = path;
+ char *const *to_be_argv = argv;
+ if (outargv) {
+ to_be_copied = outargv[0];
+ to_be_argv = outargv;
+ }
+
+ char *new_path = lazy_copy(to_be_copied, &st);
+ if (!new_path) {
+ return -1;
+ }
+
+ int ret = execve(new_path, to_be_argv, envp);
+ free_argv(outargv);
+ free(new_path);
+ return ret;
+ }
+ break;
+ }
+}
+
+/**
+ * Behaves like posix_spawn(2), but checks whether trace mode is enabled (by
+ * checking for DYLD_INSERT_LIBRARIES in the environment) and the binary is
+ * covered by 10.11's new system integrity protection. If it is, the binary
+ * will be copied to a separate folder (or updated if already there and
+ * modification time differs) and executed from there.
+ */
+int sip_copy_posix_spawn(
+ pid_t *restrict pid,
+ const char *restrict path,
+ const posix_spawn_file_actions_t *file_actions,
+ const posix_spawnattr_t *restrict attrp,
+ char *const argv[restrict],
+ char *const envp[restrict]) {
+ char **outargv = NULL;
+ struct stat st;
+
+ copy_needed_return_t need_copy = copy_needed(path, argv, &outargv, envp, &st);
+ switch (need_copy) {
+ case copy_needed_error:
+ return -1;
+ break;
+ case copy_not_needed:
+ return posix_spawn(pid, path, file_actions, attrp, argv, envp);
+ break;
+ case copy_is_needed: {
+ const char *to_be_copied = path;
+ char *const *to_be_argv = argv;
+ if (outargv) {
+ to_be_copied = outargv[0];
+ to_be_argv = outargv;
+ }
+
+ char *new_path = lazy_copy(to_be_copied, &st);
+ if (!new_path) {
+ return -1;
+ }
+
+ int ret = posix_spawn(pid, new_path, file_actions, attrp, to_be_argv, envp);
+ free_argv(outargv);
+ free(new_path);
+ return ret;
+ }
+ break;
+ }
+}
</ins></span></pre></div>
<a id="trunkbasesrcpextlib10sip_copy_proch"></a>
<div class="addfile"><h4>Added: trunk/base/src/pextlib1.0/sip_copy_proc.h (0 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/pextlib1.0/sip_copy_proc.h         (rev 0)
+++ trunk/base/src/pextlib1.0/sip_copy_proc.h        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -0,0 +1,60 @@
</span><ins>+/* vim: set et sw=4 ts=4 sts=4: */
+/*
+ * system.c
+ * $Id: system.c 138943 2015-07-24 20:35:45Z raimue@macports.org $
+ *
+ * Copyright (c) 2015 Clemens Lang <cal@macports.org>
+ * Copyright (c) 2015 The MacPorts Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of The MacPorts Project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <spawn.h>
+
+/**
+ * Behaves like execve(2), but checks whether trace mode is enabled (by
+ * checking for DYLD_INSERT_LIBRARIES in the environment) and the binary is
+ * covered by 10.11's new system integrity protection. If it is, the binary
+ * will be copied to a separate folder (or updated if already there and
+ * modification time differs) and executed from there.
+ */
+int sip_copy_execve(const char *path, char *const argv[], char *const envp[]);
+
+
+/**
+ * Behaves like posix_spawn(2), but checks whether trace mode is enabled (by
+ * checking for DYLD_INSERT_LIBRARIES in the environment) and the binary is
+ * covered by 10.11's new system integrity protection. If it is, the binary
+ * will be copied to a separate folder (or updated if already there and
+ * modification time differs) and executed from there.
+ */
+int sip_copy_posix_spawn(
+ pid_t *restrict pid,
+ const char *restrict path,
+ const posix_spawn_file_actions_t *file_actions,
+ const posix_spawnattr_t *restrict attrp,
+ char *const argv[restrict],
+ char *const envp[restrict]);
</ins></span></pre></div>
<a id="trunkbasesrcpextlib10systemc"></a>
<div class="modfile"><h4>Modified: trunk/base/src/pextlib1.0/system.c (141419 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/pextlib1.0/system.c        2015-10-18 00:49:18 UTC (rev 141419)
+++ trunk/base/src/pextlib1.0/system.c        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -58,6 +58,7 @@
</span><span class="cx"> #include <errno.h>
</span><span class="cx">
</span><span class="cx"> #include "system.h"
</span><ins>+#include "sip_copy_proc.h"
</ins><span class="cx"> #include "Pextlib.h"
</span><span class="cx">
</span><span class="cx"> #if HAVE_CRT_EXTERNS_H
</span><span class="lines">@@ -237,13 +238,13 @@
</span><span class="cx"> args[4] = "-c";
</span><span class="cx"> args[5] = cmdstring;
</span><span class="cx"> args[6] = NULL;
</span><del>- execve(sandbox_exec_path, args, environ);
</del><ins>+ sip_copy_execve(sandbox_exec_path, args, environ);
</ins><span class="cx"> } else {
</span><span class="cx"> args[0] = "sh";
</span><span class="cx"> args[1] = "-c";
</span><span class="cx"> args[2] = cmdstring;
</span><span class="cx"> args[3] = NULL;
</span><del>- execve("/bin/sh", args, environ);
</del><ins>+ sip_copy_execve("/bin/sh", args, environ);
</ins><span class="cx"> }
</span><span class="cx"> exit(128);
</span><span class="cx"> /*NOTREACHED*/
</span></span></pre></div>
<a id="trunkbasesrcport10portsandboxtcl"></a>
<div class="modfile"><h4>Modified: trunk/base/src/port1.0/portsandbox.tcl (141419 => 141420)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/port1.0/portsandbox.tcl        2015-10-18 00:49:18 UTC (rev 141419)
+++ trunk/base/src/port1.0/portsandbox.tcl        2015-10-18 00:49:32 UTC (rev 141420)
</span><span class="lines">@@ -42,7 +42,7 @@
</span><span class="cx"> # command line usage would be:
</span><span class="cx"> # sandbox-exec -p '(version 1) (allow default) (deny file-write*) (allow file-write* <filter>)' some-command
</span><span class="cx"> proc portsandbox::set_profile {target} {
</span><del>- global os.major portsandbox_profile workpath distpath altprefix \
</del><ins>+ global os.major portsandbox_profile workpath distpath prefix altprefix \
</ins><span class="cx"> package.destpath configure.ccache ccache_dir
</span><span class="cx">
</span><span class="cx"> switch $target {
</span><span class="lines">@@ -78,6 +78,7 @@
</span><span class="cx">
</span><span class="cx"> # TODO: remove altprefix support
</span><span class="cx"> lappend allow_dirs $workpath $altprefix
</span><ins>+ lappend allow_dirs $prefix/var/macports/sip-workaround
</ins><span class="cx"> if {${configure.ccache}} {
</span><span class="cx"> lappend allow_dirs $ccache_dir
</span><span class="cx"> }
</span></span></pre>
</div>
</div>
</body>
</html>