X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/6b200bc335dc93c5516ccb52f14bd896d8c7fad7..7e6b461318c8a779d91381531435a68ee4e8b6ed:/OSX/libsecurity_authorization/lib/trampolineClient.cpp diff --git a/OSX/libsecurity_authorization/lib/trampolineClient.cpp b/OSX/libsecurity_authorization/lib/trampolineClient.cpp index dfbc986f..b7c5ee85 100644 --- a/OSX/libsecurity_authorization/lib/trampolineClient.cpp +++ b/OSX/libsecurity_authorization/lib/trampolineClient.cpp @@ -32,35 +32,51 @@ #include #include #include -#include -#include #include #include #include -// -// 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 +#include +#include +#include +#include +#include // // 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, @@ -81,155 +97,180 @@ 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; - - // create the mailbox file - FILE *mbox = tmpfile(); - if (!mbox) - return errAuthorizationInternal; - if (fwrite(extForm, sizeof(*extForm), 1, mbox) != 1) { - fclose(mbox); - return errAuthorizationInternal; - } - fflush(mbox); + // 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); - // compute the argument vector here because we can't allocate memory once we fork. - - // make text representation of the temp-file descriptor - char mboxFdText[20]; - snprintf(mboxFdText, sizeof(mboxFdText), "auth %d", fileno(mbox)); - - // where is the trampoline? + // 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 - - const char **argv = argVector(trampoline, pathToTool, mboxFdText, arguments); - - // make a notifier pipe - int notify[2]; - if (pipe(notify)) { - fclose(mbox); - return errAuthorizationToolExecuteFailure; + + // make a data exchange pipe + int dataPipe[2]; + if (pipe(dataPipe)) { + os_log_error(AUTH_LOG, "data 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]); - fclose(mbox); - 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) { - 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]); - return errAuthorizationToolExecuteFailure; - - default: { // parent - // close foreign side of pipes - close(notify[WRITE]); - if (communicationsPipe) - close(comm[WRITE]); + + // 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); + } + 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]); + if(argv) { + 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); - // close mailbox file (child has it open now) - fclose(mbox); - - // 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]); - if (communicationsPipe) { close(comm[READ]); close(comm[WRITE]); } - goto exit_point; - case 0: // end of file: exec succeeded - close(notify[READ]); - if (communicationsPipe) - *communicationsPipe = fdopen(comm[READ], "r+"); - secinfo("authexec", "parent resumes (no error)"); - status = errSecSuccess; - goto exit_point; - } + // 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); + } } - - case 0: // child - // close foreign side of pipes - close(notify[READ]); - if (communicationsPipe) - close(comm[READ]); - - // 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 - { - OSStatus error = errAuthorizationToolExecuteFailure; - error = h2n(error); - write(1, &error, sizeof(error)); - _exit(1); - } - } - } - + } + exit_point: - free(argv); - return status; + free(argv); + return status; } @@ -237,22 +278,212 @@ exit_point: // Build an argv vector // static const char **argVector(const char *trampoline, const char *pathToTool, - const char *mboxFdText, char *const *arguments) +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; +} + + + +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)) { - 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; + // 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; }