+ // 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);
+ }
+ }
+ });
+
+ 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;
+ }
+
+ 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;
+ }
+
+ extern char** environ;
+
+ if (environ) {
+ xpc_object_t envArray = xpc_array_create(NULL, 0);
+ char **ptr = environ;
+
+ 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);
+
+ 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;