You've already forked macports-base
mirror of
https://github.com/macports/macports-base.git
synced 2026-07-12 18:18:43 -07:00
1b3ba14fdb
Relax the path check in sip_copy_proc.c so that it also passes in the build tree, not only in the install location.
842 lines
31 KiB
C
842 lines
31 KiB
C
/* vim: set et sw=4 ts=4 sts=4: */
|
|
/*
|
|
* sip_copy_proc.c
|
|
*
|
|
* 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/mman.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 <mach-o/dyld.h>
|
|
#include <mach-o/fat.h>
|
|
#include <mach-o/loader.h>
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef HAVE_COPYFILE
|
|
#include <copyfile.h>
|
|
#endif
|
|
|
|
#include "getline.h"
|
|
|
|
#include "sip_copy_proc.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 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 */
|
|
(void) path;
|
|
(void) argv;
|
|
(void) outargv;
|
|
(void) environ;
|
|
(void) st;
|
|
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);
|
|
fclose(f);
|
|
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);
|
|
fclose(f);
|
|
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);
|
|
fclose(f);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
fclose(f);
|
|
|
|
// 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_mode & (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) */
|
|
}
|
|
|
|
/**
|
|
* Apple ships system binaries on Apple Silicon systems with a separate CPU
|
|
* subtype that enables pointer authentication. While this is great from
|
|
* a security point of view, Apple marks this sub-architecture as for their use
|
|
* only and requires a boot arg to allow its use by binaries not signed by
|
|
* Apple. Changing the boot args in turn requires disabling system integrity
|
|
* protection.
|
|
*
|
|
* Fortunately it seems these binaries remain functional if we just change the
|
|
* CPU subtype in those binaries back to CPU_SUBTYPE_ARM64_ALL (i.e., disable
|
|
* pointer authentication). This function performs this change for arm64
|
|
* binaries and arm64 slices of universal binaries where necessary.
|
|
*
|
|
* See https://trac.macports.org/ticket/66358#comment:58.
|
|
*/
|
|
static int strip_pointer_auth(const char *filepath) {
|
|
int fd = -1;
|
|
struct stat st;
|
|
void *data = NULL;
|
|
int result = -1;
|
|
size_t filesize = 0;
|
|
|
|
if ((fd = open(filepath, O_RDWR)) < 0) {
|
|
fprintf(stderr, "failed to open '%s': %s\n", filepath, strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
if (fstat(fd, &st) != 0) {
|
|
fprintf(stderr, "failed to fstat '%s': %s\n", filepath, strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
if ((data = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 0)) == MAP_FAILED) {
|
|
fprintf(stderr, "failed to mmap '%s': %s\n", filepath, strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
filesize = (size_t) st.st_size;
|
|
|
|
const uint32_t *magic = (uint32_t *) data;
|
|
switch (*magic) {
|
|
/* For the purpose of changing the CPU subtype from CPU_SUBTYPE_ARM64E
|
|
* to CPU_SUBTYPE_ARM64_ALL, we only care about 64-bit headers and fat
|
|
* binaries, all other cases can be safely ignored. */
|
|
case MH_MAGIC_64: {
|
|
if (filesize < sizeof(struct mach_header_64)) {
|
|
// short file
|
|
goto out;
|
|
}
|
|
struct mach_header_64 *header = (struct mach_header_64 *) data;
|
|
if (header->cputype == CPU_TYPE_ARM64 && header->cpusubtype == (int32_t) (CPU_SUBTYPE_PTRAUTH_ABI | CPU_SUBTYPE_ARM64E)) {
|
|
/* Disable pointer authentication for the copied binary, since
|
|
* the kernel only allows it on binaries signed with Apple's
|
|
* key. */
|
|
header->cpusubtype = CPU_SUBTYPE_ARM64_ALL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FAT_MAGIC:
|
|
case FAT_CIGAM: {
|
|
if (filesize < sizeof(struct fat_header)) {
|
|
// short file
|
|
goto out;
|
|
}
|
|
struct fat_header *header = (struct fat_header *) data;
|
|
uint32_t nfat_arch = OSSwapBigToHostInt32(header->nfat_arch);
|
|
for (uint32_t idx = 0; idx < nfat_arch; ++idx) {
|
|
if (filesize < sizeof(struct fat_header) + idx * sizeof(struct fat_arch)) {
|
|
// short file
|
|
goto out;
|
|
}
|
|
struct fat_arch *arch = (struct fat_arch *) (((char *) data) + sizeof(struct fat_header) + idx * sizeof(struct fat_arch));
|
|
if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE_ARM64 &&
|
|
OSSwapBigToHostInt32(arch->cpusubtype) == (CPU_SUBTYPE_PTRAUTH_ABI | CPU_SUBTYPE_ARM64E)) {
|
|
/* Disable pointer authentication for the copied binary, since
|
|
* the kernel only allows it on binaries signed with Apple's
|
|
* key. */
|
|
arch->cpusubtype = OSSwapHostToBigInt32(CPU_SUBTYPE_ARM64_ALL);
|
|
|
|
uint32_t offset = OSSwapBigToHostInt32(arch->offset);
|
|
|
|
if (filesize < sizeof(struct mach_header_64) + offset) {
|
|
// short file
|
|
goto out;
|
|
}
|
|
|
|
struct mach_header_64 *header = (struct mach_header_64 *) (((char *) data) + offset);
|
|
if (header->cputype == CPU_TYPE_ARM64 && header->cpusubtype == (int32_t) (CPU_SUBTYPE_PTRAUTH_ABI | CPU_SUBTYPE_ARM64E)) {
|
|
header->cpusubtype = CPU_SUBTYPE_ARM64_ALL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FAT_MAGIC_64:
|
|
case FAT_CIGAM_64: {
|
|
if (filesize < sizeof(struct fat_header)) {
|
|
// short file
|
|
goto out;
|
|
}
|
|
struct fat_header *header = (struct fat_header *) data;
|
|
uint32_t nfat_arch = OSSwapBigToHostInt32(header->nfat_arch);
|
|
for (uint32_t idx = 0; idx < nfat_arch; ++idx) {
|
|
if (filesize < sizeof(struct fat_header) + idx * sizeof(struct fat_arch_64)) {
|
|
// short file
|
|
goto out;
|
|
}
|
|
struct fat_arch_64 *arch = (struct fat_arch_64 *) (((char *) data) + sizeof(struct fat_header) + idx * sizeof(struct fat_arch_64));
|
|
if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE_ARM64 &&
|
|
OSSwapBigToHostInt32(arch->cpusubtype) == (CPU_SUBTYPE_PTRAUTH_ABI | CPU_SUBTYPE_ARM64E)) {
|
|
/* Disable pointer authentication for the copied binary, since
|
|
* the kernel only allows it on binaries signed with Apple's
|
|
* key. */
|
|
arch->cpusubtype = OSSwapHostToBigInt32(CPU_SUBTYPE_ARM64_ALL);
|
|
|
|
uint32_t offset = OSSwapBigToHostInt64(arch->offset);
|
|
|
|
if (filesize < sizeof(struct mach_header_64) + offset) {
|
|
// short file
|
|
goto out;
|
|
}
|
|
|
|
struct mach_header_64 *header = (struct mach_header_64 *) (((char *) data) + offset);
|
|
if (header->cputype == CPU_TYPE_ARM64 && header->cpusubtype == (int32_t) (CPU_SUBTYPE_PTRAUTH_ABI | CPU_SUBTYPE_ARM64E)) {
|
|
header->cpusubtype = CPU_SUBTYPE_ARM64_ALL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
result = 0;
|
|
|
|
out:
|
|
if (data != NULL) {
|
|
munmap(data, st.st_size);
|
|
data = NULL;
|
|
}
|
|
|
|
if (fd != -1) {
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int resign(const char *filepath) {
|
|
int result = -1;
|
|
int exitstatus = 0;
|
|
pid_t pid = (pid_t) -1;
|
|
posix_spawn_file_actions_t action = NULL;
|
|
|
|
char *path = strdup(filepath);
|
|
if (!path) {
|
|
goto resign_out;
|
|
}
|
|
|
|
char * const argv[] = {"codesign", "-s", "-", "-f", path, NULL};
|
|
char * const envp[] = {NULL};
|
|
|
|
if ((errno = posix_spawn_file_actions_init(&action)) != 0) {
|
|
fprintf(stderr, "posix_spawn_file_actions_init() failed: %s\n", strerror(errno));
|
|
goto resign_out;
|
|
}
|
|
if ((errno = posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY|O_APPEND, 0)) != 0) {
|
|
fprintf(stderr, "posix_spawn_file_actions_addopen(STDOUT_FILENO, /dev/null) failed: %s", strerror(errno));
|
|
goto resign_out;
|
|
}
|
|
if ((errno = posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY|O_APPEND, 0)) != 0) {
|
|
fprintf(stderr, "posix_spawn_file_actions_addopen(STDERR_FILENO, /dev/null) failed: %s", strerror(errno));
|
|
goto resign_out;
|
|
}
|
|
|
|
if (0 != (errno = posix_spawn(&pid, "/usr/bin/codesign", &action, NULL, argv, envp))) {
|
|
fprintf(stderr, "Failed to spawn /usr/bin/codesign -s - -f %s: %s\n", path, strerror(errno));
|
|
goto resign_out;
|
|
}
|
|
|
|
if (pid != waitpid(pid, &exitstatus, 0)) {
|
|
fprintf(stderr, "waitpid(%d): %s\n", pid, strerror(errno));
|
|
goto resign_out;
|
|
}
|
|
|
|
if (WIFEXITED(exitstatus) && WEXITSTATUS(exitstatus) == 0) {
|
|
result = 0;
|
|
} else {
|
|
if (WIFSIGNALED(exitstatus)) {
|
|
fprintf(stderr, "codesign %s terminated with signal %d\n", path, WTERMSIG(exitstatus));
|
|
} else {
|
|
fprintf(stderr, "codesign %s exited with non-zero status %d\n", path, WEXITSTATUS(exitstatus));
|
|
}
|
|
goto resign_out;
|
|
}
|
|
|
|
resign_out:
|
|
if (action != NULL)
|
|
posix_spawn_file_actions_destroy(&action);
|
|
free(path);
|
|
return result;
|
|
}
|
|
|
|
static struct timespec determine_pextlib_mtime(void) {
|
|
const char *needle_darwintrace = "/darwintrace.dylib";
|
|
size_t needle_darwintrace_len = strlen(needle_darwintrace);
|
|
const char *needle_pextlib = "/pextlib1.0/Pextlib.dylib";
|
|
size_t needle_pextlib_len = strlen(needle_pextlib);
|
|
|
|
const char *lib_path = NULL;
|
|
|
|
for (uint32_t idx = 0; idx < _dyld_image_count(); ++idx) {
|
|
const char *image_name = _dyld_get_image_name(idx);
|
|
size_t image_name_len = strlen(image_name);
|
|
|
|
if (image_name_len > needle_darwintrace_len) {
|
|
if (strcmp(image_name + image_name_len - needle_darwintrace_len, needle_darwintrace) == 0) {
|
|
lib_path = image_name;
|
|
break;
|
|
}
|
|
}
|
|
if (image_name_len > needle_pextlib_len) {
|
|
if (strcmp(image_name + image_name_len - needle_pextlib_len, needle_pextlib) == 0) {
|
|
lib_path = image_name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lib_path == NULL) {
|
|
fprintf(stderr, "Failed to find Pextlib.dylib or darwintrace.dylib path in _dyld_get_image_name()\n");
|
|
abort();
|
|
}
|
|
|
|
struct stat st;
|
|
if (-1 == stat(lib_path, &st)) {
|
|
fprintf(stderr, "stat(%s) failed: %s", lib_path, strerror(errno));
|
|
abort();
|
|
}
|
|
|
|
return st.st_mtimespec;
|
|
}
|
|
|
|
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. As an exception to this rule, also copy if the
|
|
* ctime of the target file is older than the ctime of the Pextlib.dylib
|
|
* that contains this code. This exception allows updating the copy
|
|
* mechanism and triggers new copies of files if it possibly changed. */
|
|
static struct timespec pextlib_mtimespec = {0};
|
|
if (pextlib_mtimespec.tv_sec == 0 && pextlib_mtimespec.tv_nsec == 0) {
|
|
pextlib_mtimespec = determine_pextlib_mtime();
|
|
}
|
|
|
|
struct stat out_st = {0};
|
|
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
|
|
&& (
|
|
out_st.st_ctimespec.tv_sec > pextlib_mtimespec.tv_sec
|
|
|| (
|
|
out_st.st_ctimespec.tv_sec == pextlib_mtimespec.tv_sec
|
|
&& out_st.st_ctimespec.tv_nsec > pextlib_mtimespec.tv_nsec
|
|
)
|
|
)) {
|
|
// copying not needed
|
|
retval = target_path;
|
|
goto lazy_copy_out;
|
|
}
|
|
|
|
#ifdef HAVE_COPYFILE
|
|
// as copyfile(3) is not guaranteed to be atomic, copy to a temporary file first
|
|
if (mktemp(target_path_temp) == NULL) {
|
|
fprintf(stderr, "sip_copy_proc: mktemp(%s): %s\n", target_path_temp, strerror(errno));
|
|
goto lazy_copy_out;
|
|
}
|
|
|
|
// copyfile(3) will not preserve SF_RESTRICTED,
|
|
// we can safely copy the source file with all metadata.
|
|
// This cannot use COPYFILE_CLONE as it does not follow symlinks,
|
|
// see https://trac.macports.org/ticket/55575
|
|
if (copyfile(path, target_path_temp, NULL, COPYFILE_ACL | COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0) {
|
|
fprintf(stderr, "sip_copy_proc: copyfile(%s, %s): %s\n", path, target_path_temp, strerror(errno));
|
|
goto lazy_copy_out;
|
|
}
|
|
|
|
// Re-open the copied file in outfd, because copyfile(3) with COPYFILE_ALL
|
|
// (or COPYFILE_STAT, for that matter) does not seem to copy the file
|
|
// creation time correctly, so we need another futimes(2) to fix that.
|
|
#ifdef O_CLOEXEC
|
|
if (-1 == (outfd = open(target_path_temp, O_RDWR | O_CLOEXEC))) {
|
|
fprintf(stderr, "sip_copy_proc: open(%s, O_RDWR | O_CLOEXEC): %s\n", target_path_temp, strerror(errno));
|
|
#else
|
|
if (-1 == (outfd = open(target_path_temp, O_RDWR))) {
|
|
fprintf(stderr, "sip_copy_proc: open(%s, O_RDWR): %s\n", target_path_temp, strerror(errno));
|
|
#endif
|
|
goto lazy_copy_out;
|
|
}
|
|
#else /* !HAVE_COPYFILE */
|
|
// 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;
|
|
}
|
|
|
|
#ifdef O_CLOEXEC
|
|
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));
|
|
#else
|
|
if (-1 == (infd = open(path, O_RDONLY))) {
|
|
fprintf(stderr, "sip_copy_proc: open(%s, O_RDONLY): %s\n", path, strerror(errno));
|
|
#endif
|
|
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;
|
|
}
|
|
#endif /* HAVE_COPYFILE */
|
|
|
|
if (-1 == strip_pointer_auth(target_path_temp)) {
|
|
fprintf(stderr, "sip_copy_proc: strip_pointer_auth(%s) for source file %s failed\n", target_path_temp, path);
|
|
goto lazy_copy_out;
|
|
}
|
|
|
|
if (-1 == resign(target_path_temp)) {
|
|
fprintf(stderr, "sip_copy_proc: resign(%s) failed\n", target_path_temp);
|
|
goto lazy_copy_out;
|
|
}
|
|
|
|
struct timeval times[2];
|
|
TIMESPEC_TO_TIMEVAL(×[0], &in_st->st_mtimespec);
|
|
TIMESPEC_TO_TIMEVAL(×[1], &in_st->st_mtimespec);
|
|
if (-1 == utimes(target_path_temp, times)) {
|
|
fprintf(stderr, "sip_copy_proc: utimes(%s): %s\n", target_path_temp, strerror(errno));
|
|
goto lazy_copy_out;
|
|
}
|
|
|
|
// Since we removed COPYFILE_STAT from the copyfile(3) invocation above, we
|
|
// need to restore the permissions on the copy. Keep this after the
|
|
// utimes(2) call, or setting the times may fail due to lack of write permissions
|
|
if (-1 == chmod(target_path_temp, in_st->st_mode)) {
|
|
fprintf(stderr, "sip_copy_proc: chmod(%s, %o): %s\n", target_path_temp, in_st->st_mode, 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) {
|
|
free_argv(outargv);
|
|
return -1;
|
|
}
|
|
|
|
int ret = execve(new_path, to_be_argv, envp);
|
|
free_argv(outargv);
|
|
free(new_path);
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
free_argv(outargv);
|
|
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;
|
|
}
|
|
|
|
return -1;
|
|
}
|