#include <fcntl.h>
#include <stdlib.h>
#include <sys/socket.h>
-#include <Security/Authorization.h>
-#include <Security/AuthorizationPriv.h>
#include <Security/SecBase.h>
#include <security_utilities/endian.h>
#include <security_utilities/debugging.h>
-//
-// Where is the trampoline itself?
-//
-#if !defined(TRAMPOLINE)
-# define TRAMPOLINE "/usr/libexec/security_authtrampoline" /* fallback */
-#endif
-
+#include "Security/Authorization.h"
+#include "AuthorizationPriv.h"
+#include "AuthorizationTrampolinePriv.h"
+#include <dispatch/semaphore.h>
+#include <unistd.h>
+#include <os/log.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <os/log.h>
//
// A few names for clarity's sake
//
enum {
- READ = 0, // read end of standard UNIX pipe
- WRITE = 1 // write end of standard UNIX pipe
+ READ = 0, // read end of standard UNIX pipe
+ WRITE = 1 // write end of standard UNIX pipe
};
+static os_log_t AUTH_LOG_DEFAULT() {
+ static dispatch_once_t once;
+ static os_log_t log;
+ dispatch_once(&once, ^{ log = os_log_create("com.apple.Authorization", "Trampoline"); });
+ return log;
+};
+
+#define AUTH_LOG AUTH_LOG_DEFAULT()
+
+//
+// Where is the trampoline itself?
+//
+#if !defined(TRAMPOLINE)
+# define TRAMPOLINE "/usr/libexec/security_authtrampoline" /* fallback */
+#endif
+
//
// Local (static) functions
//
static const char **argVector(const char *trampoline,
- const char *tool, const char *commFd,
- char *const *arguments);
+const char *tool, const char *commFd,
+char *const *arguments);
OSStatus AuthorizationExecuteWithPrivileges(AuthorizationRef authorization,
// The public client API function.
//
OSStatus AuthorizationExecuteWithPrivilegesExternalForm(const AuthorizationExternalForm * extForm,
- const char *pathToTool,
- AuthorizationFlags flags,
- char *const *arguments,
- FILE **communicationsPipe)
+const char *pathToTool,
+AuthorizationFlags flags,
+char *const *arguments,
+FILE **communicationsPipe)
{
- if (extForm == NULL)
+ os_log(AUTH_LOG, "AuthorizationExecuteWithPrivileges and AuthorizationExecuteWithPrivilegesExternalForm are deprecated and functionality will be removed soon - please update your application");
+ if (extForm == NULL)
return errAuthorizationInvalidPointer;
- // report the caller to the authorities
- aslmsg m = asl_new(ASL_TYPE_MSG);
- asl_set(m, "com.apple.message.domain", "com.apple.libsecurity_authorization.AuthorizationExecuteWithPrivileges");
- asl_set(m, "com.apple.message.signature", getprogname());
- asl_log(NULL, m, ASL_LEVEL_NOTICE, "AuthorizationExecuteWithPrivileges!");
- asl_free(m);
-
- // flags are currently reserved
- if (flags != 0)
- return errAuthorizationInvalidFlags;
-
- // compute the argument vector here because we can't allocate memory once we fork.
-
- // where is the trampoline?
+ // report the caller to the authorities
+ aslmsg m = asl_new(ASL_TYPE_MSG);
+ asl_set(m, "com.apple.message.domain", "com.apple.libsecurity_authorization.AuthorizationExecuteWithPrivileges");
+ asl_set(m, "com.apple.message.signature", getprogname());
+ asl_log(NULL, m, ASL_LEVEL_NOTICE, "AuthorizationExecuteWithPrivileges!");
+ asl_free(m);
+
+ // flags are currently reserved
+ if (flags != 0)
+ return errAuthorizationInvalidFlags;
+
+ // compute the argument vector here because we can't allocate memory once we fork.
+
+ // where is the trampoline?
#if defined(NDEBUG)
- const char *trampoline = TRAMPOLINE;
+ const char *trampoline = TRAMPOLINE;
#else //!NDEBUG
- const char *trampoline = getenv("AUTHORIZATIONTRAMPOLINE");
- if (!trampoline)
- trampoline = TRAMPOLINE;
+ const char *trampoline = getenv("AUTHORIZATIONTRAMPOLINE");
+ if (!trampoline)
+ trampoline = TRAMPOLINE;
#endif //NDEBUG
-
- // make a data exchange pipe
- int dataPipe[2];
- if (pipe(dataPipe)) {
- secinfo("authexec", "data pipe failure");
- return errAuthorizationToolExecuteFailure;
- }
-
- // make text representation of the pipe handle
- char pipeFdText[20];
- snprintf(pipeFdText, sizeof(pipeFdText), "auth %d", dataPipe[READ]);
- const char **argv = argVector(trampoline, pathToTool, pipeFdText, arguments);
-
- // make a notifier pipe
- int notify[2];
- if (pipe(notify)) {
- close(dataPipe[READ]); close(dataPipe[WRITE]);
+
+ // make a data exchange pipe
+ int dataPipe[2];
+ if (pipe(dataPipe)) {
+ os_log_error(AUTH_LOG, "data pipe failure");
+ return errAuthorizationToolExecuteFailure;
+ }
+
+ // make text representation of the pipe handle
+ char pipeFdText[20];
+ snprintf(pipeFdText, sizeof(pipeFdText), "auth %d", dataPipe[READ]);
+ const char **argv = argVector(trampoline, pathToTool, pipeFdText, arguments);
+
+ // make a notifier pipe
+ int notify[2];
+ if (pipe(notify)) {
+ close(dataPipe[READ]); close(dataPipe[WRITE]);
if(argv) {
- free(argv);
- }
- secinfo("authexec", "notify pipe failure");
- return errAuthorizationToolExecuteFailure;
+ free(argv);
+ }
+ os_log_error(AUTH_LOG, "notify pipe failure");
+ return errAuthorizationToolExecuteFailure;
}
-
- // make the communications pipe if requested
- int comm[2];
- if (communicationsPipe && socketpair(AF_UNIX, SOCK_STREAM, 0, comm)) {
- close(notify[READ]); close(notify[WRITE]);
- close(dataPipe[READ]); close(dataPipe[WRITE]);
+
+ // make the communications pipe if requested
+ int comm[2];
+ if (communicationsPipe && socketpair(AF_UNIX, SOCK_STREAM, 0, comm)) {
+ close(notify[READ]); close(notify[WRITE]);
+ close(dataPipe[READ]); close(dataPipe[WRITE]);
if(argv) {
- free(argv);
- }
- secinfo("authexec", "comm pipe failure");
- return errAuthorizationToolExecuteFailure;
- }
+ free(argv);
+ }
+ os_log_error(AUTH_LOG, "comm pipe failure");
+ return errAuthorizationToolExecuteFailure;
+ }
+
+ OSStatus status = errSecSuccess;
+
+ // do the standard forking tango...
+ int delay = 1;
+ for (int n = 5;; n--, delay *= 2) {
+ switch (fork()) {
+ case -1: // error
+ if (errno == EAGAIN) {
+ // potentially recoverable resource shortage
+ if (n > 0) {
+ os_log(AUTH_LOG, "resource shortage (EAGAIN), delaying %d seconds", delay);
+ sleep(delay);
+ continue;
+ }
+ }
+ os_log_error(AUTH_LOG, "fork failed (errno=%d)", errno);
+ close(notify[READ]); close(notify[WRITE]);
+ status = errAuthorizationToolExecuteFailure;
+ goto exit_point;
+
+ default: { // parent
+ // close foreign side of pipes
+ close(notify[WRITE]);
+ if (communicationsPipe)
+ close(comm[WRITE]);
+
+ close(dataPipe[READ]);
+ if (write(dataPipe[WRITE], extForm, sizeof(*extForm)) != sizeof(*extForm)) {
+ os_log_error(AUTH_LOG, "fwrite data failed (errno=%d)", errno);
+ status = errAuthorizationInternal;
+ close(notify[READ]);
+ close(dataPipe[WRITE]);
+ if (communicationsPipe) {
+ close(comm[READ]);
+ }
+ goto exit_point;
+ }
+ // get status notification from child
+ os_log_debug(AUTH_LOG, "parent waiting for status");
+ ssize_t rc = read(notify[READ], &status, sizeof(status));
+ status = n2h(status);
+ switch (rc) {
+ default: // weird result of read: post error
+ os_log_error(AUTH_LOG, "unexpected read return value %ld", long(rc));
+ status = errAuthorizationToolEnvironmentError;
+ // fall through
+ case sizeof(status): // read succeeded: child reported an error
+ os_log_error(AUTH_LOG, "parent received status=%d", (int)status);
+ close(notify[READ]);
+ close(dataPipe[WRITE]);
+ if (communicationsPipe) {
+ close(comm[READ]);
+ close(comm[WRITE]);
+ }
+ goto exit_point;
+ case 0: // end of file: exec succeeded
+ close(notify[READ]);
+ close(dataPipe[WRITE]);
+ if (communicationsPipe)
+ *communicationsPipe = fdopen(comm[READ], "r+");
+ os_log_debug(AUTH_LOG, "parent resumes (no error)");
+ status = errSecSuccess;
+ goto exit_point;
+ }
+ }
+
+ case 0: // child
+ // close foreign side of pipes
+ close(notify[READ]);
+ if (communicationsPipe)
+ close(comm[READ]);
+
+ // close write end of the data PIPE
+ close(dataPipe[WRITE]);
+
+ // fd 1 (stdout) holds the notify write end
+ dup2(notify[WRITE], 1);
+ close(notify[WRITE]);
+
+ // fd 0 (stdin) holds either the comm-link write-end or /dev/null
+ if (communicationsPipe) {
+ dup2(comm[WRITE], 0);
+ close(comm[WRITE]);
+ } else {
+ close(0);
+ open("/dev/null", O_RDWR);
+ }
+
+ // okay, execute the trampoline
+ if (argv)
+ execv(trampoline, (char *const*)argv);
+
+ // execute failed - tell the parent
+ {
+ // in case of failure, close read end of the data pipe as well
+ close(dataPipe[WRITE]);
+ close(dataPipe[READ]);
+ OSStatus error = errAuthorizationToolExecuteFailure;
+ error = h2n(error);
+ write(1, &error, sizeof(error));
+ _exit(1);
+ }
+ }
+ }
+
+exit_point:
+ free(argv);
+ return status;
+}
- OSStatus status = errSecSuccess;
- // do the standard forking tango...
- int delay = 1;
- for (int n = 5;; n--, delay *= 2) {
- switch (fork()) {
- case -1: // error
- if (errno == EAGAIN) {
- // potentially recoverable resource shortage
- if (n > 0) {
- secinfo("authexec", "resource shortage (EAGAIN), delaying %d seconds", delay);
- sleep(delay);
- continue;
- }
- }
- secinfo("authexec", "fork failed (errno=%d)", errno);
- close(notify[READ]); close(notify[WRITE]);
- status = errAuthorizationToolExecuteFailure;
- goto exit_point;
+//
+// Build an argv vector
+//
+static const char **argVector(const char *trampoline, const char *pathToTool,
+const char *mboxFdText, char *const *arguments)
+{
+ int length = 0;
+ if (arguments) {
+ for (char *const *p = arguments; *p; p++)
+ length++;
+ }
+ if (const char **args = (const char **)malloc(sizeof(const char *) * (length + 4))) {
+ args[0] = trampoline;
+ args[1] = pathToTool;
+ args[2] = mboxFdText;
+ if (arguments)
+ for (int n = 0; arguments[n]; n++)
+ args[n + 3] = arguments[n];
+ args[length + 3] = NULL;
+ return args;
+ }
+ return NULL;
+}
- default: { // parent
- // close foreign side of pipes
- close(notify[WRITE]);
- if (communicationsPipe)
- close(comm[WRITE]);
- close(dataPipe[READ]);
- if (write(dataPipe[WRITE], extForm, sizeof(*extForm)) != sizeof(*extForm)) {
- secinfo("authexec", "fwrite data failed (errno=%d)", errno);
- status = errAuthorizationInternal;
- close(notify[READ]);
- close(dataPipe[WRITE]);
- if (communicationsPipe) {
- close(comm[READ]);
- close(comm[WRITE]);
- }
- goto exit_point;
- }
- close(dataPipe[WRITE]);
- // get status notification from child
- secinfo("authexec", "parent waiting for status");
- ssize_t rc = read(notify[READ], &status, sizeof(status));
- status = n2h(status);
- switch (rc) {
- default: // weird result of read: post error
- secinfo("authexec", "unexpected read return value %ld", long(rc));
- status = errAuthorizationToolEnvironmentError;
- // fall through
- case sizeof(status): // read succeeded: child reported an error
- secinfo("authexec", "parent received status=%d", (int)status);
- close(notify[READ]);
- close(dataPipe[WRITE]);
- if (communicationsPipe) {
- close(comm[READ]);
- close(comm[WRITE]);
- }
- goto exit_point;
- case 0: // end of file: exec succeeded
- close(notify[READ]);
- close(dataPipe[WRITE]);
- if (communicationsPipe)
- *communicationsPipe = fdopen(comm[READ], "r+");
- secinfo("authexec", "parent resumes (no error)");
- status = errSecSuccess;
- goto exit_point;
- }
+
+OSStatus AuthorizationExecuteWithPrivilegesInternal(const AuthorizationRef authorization,
+ const char * _Nonnull pathToTool,
+ const char * _Nonnull const * arguments,
+ pid_t * newProcessPid,
+ const uid_t uid,
+ int stdOut,
+ int stdErr,
+ int stdIn,
+ void(^processFinished)(const int exitStatus))
+{
+ // externalize the authorization
+ AuthorizationExternalForm extForm;
+ if (OSStatus err = AuthorizationMakeExternalForm(authorization, &extForm))
+ return err;
+
+ return AuthorizationExecuteWithPrivilegesExternalFormInternal(&extForm, pathToTool, arguments, newProcessPid, uid, stdOut, stdErr, stdIn, processFinished);
+}
+
+OSStatus AuthorizationExecuteWithPrivilegesExternalFormInternal(const AuthorizationExternalForm *extAuthorization,
+ const char * _Nonnull pathToTool,
+ const char * _Nullable const * _Nullable arguments,
+ pid_t * newProcessPid,
+ const uid_t uid,
+ int stdOut,
+ int stdErr,
+ int stdIn,
+ void(^processFinished)(const int exitStatus))
+{
+ xpc_object_t message;
+ __block OSStatus retval = errAuthorizationInternal;
+ dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+ if (!sema) {
+ os_log_error(AUTH_LOG, "Unable to create trampoline semaphore");
+ return retval;
+ }
+ __block xpc_connection_t trampolineConnection = xpc_connection_create_mach_service("com.apple.security.authtrampoline", NULL, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
+
+ if (!trampolineConnection) {
+ os_log_error(AUTH_LOG, "Unable to create trampoline mach service");
+ dispatch_release(sema);
+ return retval;
+ }
+
+ xpc_connection_set_event_handler(trampolineConnection, ^(xpc_object_t event) {
+ xpc_type_t type = xpc_get_type(event);
+
+ if (type == XPC_TYPE_ERROR) {
+ if (trampolineConnection) {
+ xpc_release(trampolineConnection);
+ trampolineConnection = NULL;
+ }
+ if (event == XPC_ERROR_CONNECTION_INTERRUPTED && processFinished) {
+ os_log_error(AUTH_LOG, "Connection with trampoline was interruped");
+ processFinished(134); // simulate killed by SIGABRT
+ }
+ } else {
+ const char *requestId = xpc_dictionary_get_string(event, XPC_REQUEST_ID);
+ if (requestId && strncmp(XPC_EVENT_MSG, requestId, strlen(XPC_EVENT_MSG)) == 0) {
+ const char *eventType = xpc_dictionary_get_string(event, XPC_EVENT_TYPE);
+ if (eventType && strncmp(XPC_EVENT_TYPE_CHILDEND, eventType, strlen(XPC_EVENT_TYPE_CHILDEND)) == 0) {
+ int exitStatus = (int)xpc_dictionary_get_int64(event, RETVAL_STATUS);
+ os_log_debug(AUTH_LOG, "Child process ended with exit status %d", exitStatus);
+
+ if (trampolineConnection) {
+ xpc_connection_cancel(trampolineConnection);
+ xpc_release(trampolineConnection);
+ trampolineConnection = NULL;
+ }
+ if (processFinished) {
+ processFinished(exitStatus);
+ };
+ } else {
+ os_log_error(AUTH_LOG, "Unknown event type [%s] arrived from trampoline", eventType);
+ }
+ } else {
+ os_log_error(AUTH_LOG, "Unknown request [%s] arrived from trampoline", requestId);
+ }
}
-
- case 0: // child
- // close foreign side of pipes
- close(notify[READ]);
- if (communicationsPipe)
- close(comm[READ]);
+ });
+
+ xpc_connection_resume(trampolineConnection);
+
+ message = xpc_dictionary_create(NULL, NULL, 0);
+ xpc_dictionary_set_string(message, XPC_REQUEST_ID, XPC_REQUEST_CREATE_PROCESS);
+
+ Boolean waitForEndNeeded = (processFinished != NULL);
+ if (stdIn >= 0) {
+ xpc_object_t xpcInFd = xpc_fd_create(stdIn);
+ if (!xpcInFd) {
+ os_log_error(AUTH_LOG, "Unable to create XPC stdin FD");
+ goto finish;
+ }
+ xpc_dictionary_set_value(message, PARAM_STDIN, xpcInFd);
+ xpc_release(xpcInFd);
+ waitForEndNeeded = true;
+ }
+
+ if (stdOut >= 0) {
+ xpc_object_t xpcOutFd = xpc_fd_create(stdOut);
+ if (!xpcOutFd) {
+ os_log_error(AUTH_LOG, "Unable to create XPC stdout FD");
+ goto finish;
+ }
+ xpc_dictionary_set_value(message, PARAM_STDOUT, xpcOutFd);
+ xpc_release(xpcOutFd);
+ waitForEndNeeded = true;
+ }
- // close write end of the data PIPE
- close(dataPipe[WRITE]);
+ if (stdErr >= 0) {
+ xpc_object_t xpcErrFd = xpc_fd_create(stdErr);
+ if (!xpcErrFd) {
+ os_log_error(AUTH_LOG, "Unable to create XPC stderr FD");
+ goto finish;
+ }
+ xpc_dictionary_set_value(message, PARAM_STDERR, xpcErrFd);
+ xpc_release(xpcErrFd);
+ waitForEndNeeded = true;
+ }
- // fd 1 (stdout) holds the notify write end
- dup2(notify[WRITE], 1);
- close(notify[WRITE]);
-
- // fd 0 (stdin) holds either the comm-link write-end or /dev/null
- if (communicationsPipe) {
- dup2(comm[WRITE], 0);
- close(comm[WRITE]);
- } else {
- close(0);
- open("/dev/null", O_RDWR);
- }
-
- // okay, execute the trampoline
- if (argv)
- execv(trampoline, (char *const*)argv);
+ extern char** environ;
- // execute failed - tell the parent
- {
- // in case of failure, close read end of the data pipe as well
- close(dataPipe[WRITE]);
- close(dataPipe[READ]);
- OSStatus error = errAuthorizationToolExecuteFailure;
- error = h2n(error);
- write(1, &error, sizeof(error));
- _exit(1);
- }
- }
- }
+ if (environ) {
+ xpc_object_t envArray = xpc_array_create(NULL, 0);
+ char **ptr = environ;
-exit_point:
- free(argv);
- return status;
-}
+ while (*ptr) {
+ xpc_object_t xpcString = xpc_string_create(*ptr++);
+ xpc_array_append_value(envArray, xpcString);
+ xpc_release(xpcString);
+ }
+ xpc_dictionary_set_value(message, PARAM_ENV, envArray);
+ xpc_release(envArray);
+ }
+ xpc_dictionary_set_string(message, PARAM_TOOL_PATH, pathToTool);
+ xpc_dictionary_set_uint64(message, PARAM_EUID, uid);
+ {
+ const char *cwd = getcwd(NULL, 0);
+ if (cwd) {
+ xpc_dictionary_set_string(message, PARAM_CWD, cwd);
+ }
+ }
+ xpc_dictionary_set_bool(message, PARAM_CHILDEND_NEEDED, waitForEndNeeded);
-//
-// Build an argv vector
-//
-static const char **argVector(const char *trampoline, const char *pathToTool,
- const char *mboxFdText, char *const *arguments)
-{
- int length = 0;
- if (arguments) {
- for (char *const *p = arguments; *p; p++)
- length++;
- }
- if (const char **args = (const char **)malloc(sizeof(const char *) * (length + 4))) {
- args[0] = trampoline;
- args[1] = pathToTool;
- args[2] = mboxFdText;
- if (arguments)
- for (int n = 0; arguments[n]; n++)
- args[n + 3] = arguments[n];
- args[length + 3] = NULL;
- return args;
- }
- return NULL;
+ if (arguments) {
+ xpc_object_t paramsArray = xpc_array_create(NULL, 0);
+ int i = 0;
+ while (arguments[i] != NULL) {
+ xpc_object_t xpcString = xpc_string_create(arguments[i++]);
+ xpc_array_append_value(paramsArray, xpcString);
+ xpc_release(xpcString);
+ }
+ xpc_dictionary_set_value(message, PARAM_TOOL_PARAMS, paramsArray);
+ xpc_release(paramsArray);
+ }
+ xpc_dictionary_set_data(message, PARAM_AUTHREF, extAuthorization, sizeof(*extAuthorization));
+
+ retval = errAuthorizationToolExecuteFailure;
+ if (trampolineConnection) {
+ xpc_connection_send_message_with_reply(trampolineConnection, message, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(xpc_object_t event) {
+ xpc_type_t type = xpc_get_type(event);
+ const char *requestId = xpc_dictionary_get_string(event, XPC_REQUEST_ID);
+ if (type == XPC_TYPE_ERROR) {
+ os_log_error(AUTH_LOG, "Error when trying to communicate with the trampoline");
+ }
+ else if (requestId && strncmp(XPC_REPLY_MSG, requestId, strlen(XPC_REPLY_MSG)) == 0) {
+ retval = (OSStatus)xpc_dictionary_get_int64(event, RETVAL_STATUS);
+ if (newProcessPid && retval == errAuthorizationSuccess) {
+ *newProcessPid = (OSStatus)xpc_dictionary_get_uint64(event, RETVAL_CHILD_PID);
+ }
+ } else {
+ os_log_error(AUTH_LOG, "Trampoline returned invalid data");
+ }
+ dispatch_semaphore_signal(sema);
+ });
+ dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
+ } else {
+ os_log_error(AUTH_LOG, "Unable to establish connection to the trampoline");
+ }
+ dispatch_release(sema);
+
+finish:
+ if (message) {
+ xpc_release(message);
+ }
+ return retval;
}