I have a sandboxed Cocoa app that, during an export process, needs to run a third party command-line tool. This tool appears to be hardcoded to use /tmp
for its temporary files; sandboxing doesn't permit access to this folder, so the export fails.
How can I get this tool to run? I don't have access to its source code, so I can't modify it to use NSTemporaryDirectory()
, and it doesn't appear to respect the TMP
or TEMPDIR
environment variables. For reasons I don't understand, giving myself a com.apple.security.temporary-exception.files.absolute-path.read-write
entitlement doesn't seem to work, either.
Is there some way to re-map folders within my sandbox? Is there some obscure trick I can use? Should I try to patch the tool's binary somehow? I'm at my wit's end here.
I was able to get user3159253's DYLD_INSERT_LIBRARIES
approach to work. I'm hoping they will write an answer describing how that works, so I'll leave the details of that out and explain the parts that ended up being specific to this case.
Thanks to LLDB, elbow grease, and not a little help from Hopper, I was able to determine that the third-party tool used mkstemp()
to generate its temporary file names, and some calls (not all) used a fixed template starting with /tmp
. I then wrote a libtmphack.dylib that intercepted calls to mkstemp()
and modified the parameters before calling the standard library version.
Since mkstemp()
takes a pointer to a preallocated buffer, I didn't feel like I could rewrite a path starting with a short string like "/tmp" to the very long string needed to get to the Caches folder inside the sandbox. Instead, I opted to create a symlink to it called "$tmp" in the current working directory. This could break if the tool chdir()
'd at an inopportune time, but fortunately it doesn't seem to do that.
Here's my code:
//
// libtmphack.c
// Typesetter
//
// Created by Brent Royal-Gordon on 8/27/14.
// Copyright (c) 2014 Groundbreaking Software. This file is MIT licensed.
//
#include "libtmphack.h"
#include <dlfcn.h>
#include <stdlib.h>
#include <unistd.h>
//#include <errno.h>
#include <string.h>
static int gbs_has_prefix(char * needle, char * haystack) {
return strncmp(needle, haystack, strlen(needle)) == 0;
}
int mkstemp(char *template) {
static int (*original_mkstemp)(char * template) = NULL;
if(!original_mkstemp) {
original_mkstemp = dlsym(RTLD_NEXT, "mkstemp");
}
if(gbs_has_prefix("/tmp", template)) {
printf("libtmphack: rewrote mkstemp(\"%s\") ", template);
template[0] = '$';
printf("to mkstemp(\"%s\")\n", template);
// If this isn't successful, we'll presume it's because it's already been made
symlink(getenv("TEMP"), "$tmp");
int ret = original_mkstemp(template);
// Can't do this, the caller needs to be able to open the file
// int retErrno = errno;
// unlink("$tmp");
// errno = retErrno;
return ret;
}
else {
printf("libtmphack: OK with mkstemp(\"%s\")\n", template);
return original_mkstemp(template);
}
}
Very quick and dirty, but it works like a charm.
Since @BrentRoyal-Gordon has already published a working solution I'm simply duplicating my comment which inspired him to produce the solution:
In order to fix a program behavior, I would intercept and override some system calls with the help of DYLD_INSERT_LIBRARIES and a custom shared library with a custom implementation of the given system calls.
The exact list of the syscalls which need to be overridden depends on nature of the application and can be studied with a number of tools built upon MacOS DTrace kernel facility. E.g. dtruss or Hopper. @BrentRoyal-Gordon has investigated that the app can be fixed solely with an /appropriate/ implementation of mkstemp
.
That's it. I'm still not sure that I've deserved the bounty :)
Another solution would be to use chroot
within the child process (or posix_spawn
options) to change its root directory to a directory that is within your sandbox. Its “/tmp” will then be a “tmp” directory within that directory.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With