]> git.saurik.com Git - apple/security.git/blobdiff - OSX/libsecurity_authorization/lib/trampolineClient.cpp
Security-59306.61.1.tar.gz
[apple/security.git] / OSX / libsecurity_authorization / lib / trampolineClient.cpp
index 00fed8056a1ae81063b3c05dbb89b0115bb48a40..b7c5ee855b0ecbbd24c8ef9e88a90aaf081f60f8 100644 (file)
 #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,
@@ -81,156 +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);
+            }
         }
-               break;
-               
-               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;
 }
 
 
@@ -238,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;
 }