/ OSX / libsecurity_authorization / lib / trampolineClient.cpp
trampolineClient.cpp
  1  /*
  2   * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved.
  3   * 
  4   * @APPLE_LICENSE_HEADER_START@
  5   * 
  6   * This file contains Original Code and/or Modifications of Original Code
  7   * as defined in and that are subject to the Apple Public Source License
  8   * Version 2.0 (the 'License'). You may not use this file except in
  9   * compliance with the License. Please obtain a copy of the License at
 10   * http://www.opensource.apple.com/apsl/ and read it before using this
 11   * file.
 12   * 
 13   * The Original Code and all software distributed under the License are
 14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 18   * Please see the License for the specific language governing rights and
 19   * limitations under the License.
 20   * 
 21   * @APPLE_LICENSE_HEADER_END@
 22   */
 23  
 24  
 25  //
 26  // trampolineClient - Authorization trampoline client-side implementation
 27  //
 28  #include <sys/types.h>
 29  #include <unistd.h>
 30  #include <errno.h>
 31  #include <fcntl.h>
 32  #include <stdlib.h>
 33  #include <sys/socket.h>
 34  #include <Security/SecBase.h>
 35  #include <security_utilities/endian.h>
 36  #include <security_utilities/debugging.h>
 37  
 38  #include "Security/Authorization.h"
 39  #include "AuthorizationPriv.h"
 40  #include "AuthorizationTrampolinePriv.h"
 41  #include <dispatch/semaphore.h>
 42  #include <unistd.h>
 43  #include <os/log.h>
 44  #include <sys/ioctl.h>
 45  #include <sys/poll.h>
 46  #include <os/log.h>
 47  
 48  //
 49  // A few names for clarity's sake
 50  //
 51  enum {
 52      READ = 0,        // read end of standard UNIX pipe
 53      WRITE = 1        // write end of standard UNIX pipe
 54  };
 55  
 56  static os_log_t AUTH_LOG_DEFAULT() {
 57      static dispatch_once_t once;
 58      static os_log_t log;
 59      dispatch_once(&once, ^{ log = os_log_create("com.apple.Authorization", "Trampoline"); });
 60      return log;
 61  };
 62  
 63  #define AUTH_LOG AUTH_LOG_DEFAULT()
 64  
 65  //
 66  // Where is the trampoline itself?
 67  //
 68  #if !defined(TRAMPOLINE)
 69  # define TRAMPOLINE "/usr/libexec/security_authtrampoline" /* fallback */
 70  #endif
 71  
 72  
 73  //
 74  // Local (static) functions
 75  //
 76  static const char **argVector(const char *trampoline,
 77  const char *tool, const char *commFd,
 78  char *const *arguments);
 79  
 80  
 81  OSStatus AuthorizationExecuteWithPrivileges(AuthorizationRef authorization,
 82                                              const char *pathToTool,
 83                                              AuthorizationFlags flags,
 84                                              char *const *arguments,
 85                                              FILE **communicationsPipe)
 86  {
 87  	// externalize the authorization
 88  	AuthorizationExternalForm extForm;
 89  	if (OSStatus err = AuthorizationMakeExternalForm(authorization, &extForm))
 90  		return err;
 91      
 92      return AuthorizationExecuteWithPrivilegesExternalForm(&extForm, pathToTool, flags, arguments, communicationsPipe);
 93  }
 94  
 95  //
 96  // The public client API function.
 97  //
 98  OSStatus AuthorizationExecuteWithPrivilegesExternalForm(const AuthorizationExternalForm * extForm,
 99  const char *pathToTool,
100  AuthorizationFlags flags,
101  char *const *arguments,
102  FILE **communicationsPipe)
103  {
104      os_log(AUTH_LOG, "AuthorizationExecuteWithPrivileges and AuthorizationExecuteWithPrivilegesExternalForm are deprecated and functionality will be removed soon - please update your application");
105      if (extForm == NULL)
106          return errAuthorizationInvalidPointer;
107      
108      // flags are currently reserved
109      if (flags != 0)
110          return errAuthorizationInvalidFlags;
111      
112      // compute the argument vector here because we can't allocate memory once we fork.
113      
114      // where is the trampoline?
115  #if defined(NDEBUG)
116      const char *trampoline = TRAMPOLINE;
117  #else //!NDEBUG
118      const char *trampoline = getenv("AUTHORIZATIONTRAMPOLINE");
119      if (!trampoline)
120          trampoline = TRAMPOLINE;
121  #endif //NDEBUG
122      
123      // make a data exchange pipe
124      int dataPipe[2];
125      if (pipe(dataPipe)) {
126          os_log_error(AUTH_LOG, "data pipe failure");
127          return errAuthorizationToolExecuteFailure;
128      }
129      
130      // make text representation of the pipe handle
131      char pipeFdText[20];
132      snprintf(pipeFdText, sizeof(pipeFdText), "auth %d", dataPipe[READ]);
133      const char **argv = argVector(trampoline, pathToTool, pipeFdText, arguments);
134      
135      // make a notifier pipe
136      int notify[2];
137      if (pipe(notify)) {
138          close(dataPipe[READ]); close(dataPipe[WRITE]);
139          if(argv) {
140              free(argv);
141          }
142          os_log_error(AUTH_LOG, "notify pipe failure");
143          return errAuthorizationToolExecuteFailure;
144      }
145      
146      // make the communications pipe if requested
147      int comm[2];
148      if (communicationsPipe && socketpair(AF_UNIX, SOCK_STREAM, 0, comm)) {
149          close(notify[READ]); close(notify[WRITE]);
150          close(dataPipe[READ]); close(dataPipe[WRITE]);
151          if(argv) {
152              free(argv);
153          }
154          os_log_error(AUTH_LOG, "comm pipe failure");
155          return errAuthorizationToolExecuteFailure;
156      }
157      
158      OSStatus status = errSecSuccess;
159      
160      // do the standard forking tango...
161      int delay = 1;
162      for (int n = 5;; n--, delay *= 2) {
163          switch (fork()) {
164              case -1:    // error
165                  if (errno == EAGAIN) {
166                      // potentially recoverable resource shortage
167                      if (n > 0) {
168                          os_log(AUTH_LOG, "resource shortage (EAGAIN), delaying %d seconds", delay);
169                          sleep(delay);
170                          continue;
171                      }
172                  }
173                  os_log_error(AUTH_LOG, "fork failed (errno=%d)", errno);
174                  close(notify[READ]); close(notify[WRITE]);
175                  status = errAuthorizationToolExecuteFailure;
176                  goto exit_point;
177                  
178              default: {    // parent
179                  // close foreign side of pipes
180                  close(notify[WRITE]);
181                  if (communicationsPipe)
182                      close(comm[WRITE]);
183                  
184                  close(dataPipe[READ]);
185                  if (write(dataPipe[WRITE], extForm, sizeof(*extForm)) != sizeof(*extForm)) {
186                      os_log_error(AUTH_LOG, "fwrite data failed (errno=%d)", errno);
187                      status = errAuthorizationInternal;
188                      close(notify[READ]);
189                      close(dataPipe[WRITE]);
190                      if (communicationsPipe) {
191                          close(comm[READ]);
192                      }
193                      goto exit_point;
194                  }
195                  // get status notification from child
196                  os_log_debug(AUTH_LOG, "parent waiting for status");
197                  ssize_t rc = read(notify[READ], &status, sizeof(status));
198                  status = n2h(status);
199                  switch (rc) {
200                      default:                // weird result of read: post error
201                          os_log_error(AUTH_LOG, "unexpected read return value %ld", long(rc));
202                          status = errAuthorizationToolEnvironmentError;
203                          // fall through
204                      case sizeof(status):    // read succeeded: child reported an error
205                          os_log_error(AUTH_LOG, "parent received status=%d", (int)status);
206                          close(notify[READ]);
207                          close(dataPipe[WRITE]);
208                          if (communicationsPipe) {
209                              close(comm[READ]);
210                              close(comm[WRITE]);
211                          }
212                          goto exit_point;
213                      case 0:                    // end of file: exec succeeded
214                          close(notify[READ]);
215                          close(dataPipe[WRITE]);
216                          if (communicationsPipe)
217                              *communicationsPipe = fdopen(comm[READ], "r+");
218                          os_log_debug(AUTH_LOG, "parent resumes (no error)");
219                          status = errSecSuccess;
220                          goto exit_point;
221                  }
222              }
223                  
224              case 0:        // child
225                  // close foreign side of pipes
226                  close(notify[READ]);
227                  if (communicationsPipe)
228                      close(comm[READ]);
229                  
230                  // close write end of the data PIPE
231                  close(dataPipe[WRITE]);
232                  
233                  // fd 1 (stdout) holds the notify write end
234                  dup2(notify[WRITE], 1);
235                  close(notify[WRITE]);
236                  
237                  // fd 0 (stdin) holds either the comm-link write-end or /dev/null
238                  if (communicationsPipe) {
239                      dup2(comm[WRITE], 0);
240                      close(comm[WRITE]);
241                  } else {
242                      close(0);
243                      open("/dev/null", O_RDWR);
244                  }
245                  
246                  // okay, execute the trampoline
247                  if (argv)
248                      execv(trampoline, (char *const*)argv);
249                  
250                  // execute failed - tell the parent
251              {
252                  // in case of failure, close read end of the data pipe as well
253                  close(dataPipe[WRITE]);
254                  close(dataPipe[READ]);
255                  OSStatus error = errAuthorizationToolExecuteFailure;
256                  error = h2n(error);
257                  write(1, &error, sizeof(error));
258                  _exit(1);
259              }
260          }
261      }
262      
263  exit_point:
264      free(argv);
265      return status;
266  }
267  
268  
269  //
270  // Build an argv vector
271  //
272  static const char **argVector(const char *trampoline, const char *pathToTool,
273  const char *mboxFdText, char *const *arguments)
274  {
275      int length = 0;
276      if (arguments) {
277          for (char *const *p = arguments; *p; p++)
278              length++;
279      }
280      if (const char **args = (const char **)malloc(sizeof(const char *) * (length + 4))) {
281          args[0] = trampoline;
282          args[1] = pathToTool;
283          args[2] = mboxFdText;
284          if (arguments)
285              for (int n = 0; arguments[n]; n++)
286                  args[n + 3] = arguments[n];
287          args[length + 3] = NULL;
288          return args;
289      }
290      return NULL;
291  }
292  
293  
294  
295  OSStatus AuthorizationExecuteWithPrivilegesInternal(const AuthorizationRef authorization,
296                                                      const char * _Nonnull pathToTool,
297                                                      const char * _Nonnull const * arguments,
298                                                      pid_t * newProcessPid,
299                                                      const uid_t uid,
300                                                      int stdOut,
301                                                      int stdErr,
302                                                      int stdIn,
303                                                      void(^processFinished)(const int exitStatus))
304  {
305      // externalize the authorization
306      AuthorizationExternalForm extForm;
307      if (OSStatus err = AuthorizationMakeExternalForm(authorization, &extForm))
308          return err;
309      
310      return AuthorizationExecuteWithPrivilegesExternalFormInternal(&extForm, pathToTool, arguments, newProcessPid, uid, stdOut, stdErr, stdIn, processFinished);
311  }
312  
313  OSStatus AuthorizationExecuteWithPrivilegesExternalFormInternal(const AuthorizationExternalForm *extAuthorization,
314                                                                  const char * _Nonnull pathToTool,
315                                                                  const char * _Nullable const * _Nullable arguments,
316                                                                  pid_t * newProcessPid,
317                                                                  const uid_t uid,
318                                                                  int stdOut,
319                                                                  int stdErr,
320                                                                  int stdIn,
321                                                                  void(^processFinished)(const int exitStatus))
322  {
323      xpc_object_t message;
324      __block OSStatus retval = errAuthorizationInternal;
325      dispatch_semaphore_t sema = dispatch_semaphore_create(0);
326      if (!sema) {
327          os_log_error(AUTH_LOG, "Unable to create trampoline semaphore");
328          return retval;
329      }
330      __block xpc_connection_t trampolineConnection = xpc_connection_create_mach_service("com.apple.security.authtrampoline", NULL, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
331      
332      if (!trampolineConnection) {
333          os_log_error(AUTH_LOG, "Unable to create trampoline mach service");
334          dispatch_release(sema);
335          return retval;
336      }
337      
338      xpc_connection_set_event_handler(trampolineConnection, ^(xpc_object_t event) {
339          xpc_type_t type = xpc_get_type(event);
340          
341          if (type == XPC_TYPE_ERROR) {
342              if (trampolineConnection) {
343                  xpc_release(trampolineConnection);
344                  trampolineConnection = NULL;
345              }
346              if (event == XPC_ERROR_CONNECTION_INTERRUPTED && processFinished) {
347                  os_log_error(AUTH_LOG, "Connection with trampoline was interruped");
348                  processFinished(134); // simulate killed by SIGABRT
349              }
350          } else {
351              const char *requestId = xpc_dictionary_get_string(event, XPC_REQUEST_ID);
352              if (requestId && strncmp(XPC_EVENT_MSG, requestId, strlen(XPC_EVENT_MSG)) == 0) {
353                  const char *eventType = xpc_dictionary_get_string(event, XPC_EVENT_TYPE);
354                  if (eventType && strncmp(XPC_EVENT_TYPE_CHILDEND, eventType, strlen(XPC_EVENT_TYPE_CHILDEND)) == 0) {
355                      int exitStatus = (int)xpc_dictionary_get_int64(event, RETVAL_STATUS);
356                      os_log_debug(AUTH_LOG, "Child process ended with exit status %d", exitStatus);
357                      
358                      if (trampolineConnection) {
359                          xpc_connection_cancel(trampolineConnection);
360                          xpc_release(trampolineConnection);
361                          trampolineConnection = NULL;
362                      }
363                      if (processFinished) {
364                          processFinished(exitStatus);
365                      };
366                  } else {
367                      os_log_error(AUTH_LOG, "Unknown event type [%s] arrived from trampoline", eventType);
368                  }
369              } else {
370                  os_log_error(AUTH_LOG, "Unknown request [%s] arrived from trampoline", requestId);
371              }
372          }
373      });
374      
375      xpc_connection_resume(trampolineConnection);
376      
377      message = xpc_dictionary_create(NULL, NULL, 0);
378      xpc_dictionary_set_string(message, XPC_REQUEST_ID, XPC_REQUEST_CREATE_PROCESS);
379      
380      Boolean waitForEndNeeded = (processFinished != NULL);
381      if (stdIn >= 0) {
382          xpc_object_t xpcInFd = xpc_fd_create(stdIn);
383          if (!xpcInFd) {
384              os_log_error(AUTH_LOG, "Unable to create XPC stdin FD");
385              goto finish;
386          }
387          xpc_dictionary_set_value(message, PARAM_STDIN, xpcInFd);
388          xpc_release(xpcInFd);
389          waitForEndNeeded = true;
390      }
391      
392      if (stdOut >= 0) {
393          xpc_object_t xpcOutFd = xpc_fd_create(stdOut);
394          if (!xpcOutFd) {
395              os_log_error(AUTH_LOG, "Unable to create XPC stdout FD");
396              goto finish;
397          }
398          xpc_dictionary_set_value(message, PARAM_STDOUT, xpcOutFd);
399          xpc_release(xpcOutFd);
400          waitForEndNeeded = true;
401      }
402  
403      if (stdErr >= 0) {
404          xpc_object_t xpcErrFd = xpc_fd_create(stdErr);
405          if (!xpcErrFd) {
406              os_log_error(AUTH_LOG, "Unable to create XPC stderr FD");
407              goto finish;
408          }
409          xpc_dictionary_set_value(message, PARAM_STDERR, xpcErrFd);
410          xpc_release(xpcErrFd);
411          waitForEndNeeded = true;
412      }
413  
414      extern char** environ;
415  
416      if (environ) {
417          xpc_object_t envArray = xpc_array_create(NULL, 0);
418          char **ptr = environ;
419  
420          while (*ptr) {
421              xpc_object_t xpcString = xpc_string_create(*ptr++);
422              xpc_array_append_value(envArray, xpcString);
423              xpc_release(xpcString);
424          }
425          xpc_dictionary_set_value(message, PARAM_ENV, envArray);
426          xpc_release(envArray);
427      }
428  
429      xpc_dictionary_set_string(message, PARAM_TOOL_PATH, pathToTool);
430      xpc_dictionary_set_uint64(message, PARAM_EUID, uid);
431      {
432          const char *cwd = getcwd(NULL, 0);
433          if (cwd) {
434              xpc_dictionary_set_string(message, PARAM_CWD, cwd);
435          }
436      }
437      xpc_dictionary_set_bool(message, PARAM_CHILDEND_NEEDED, waitForEndNeeded);
438  
439      if (arguments) {
440          xpc_object_t paramsArray = xpc_array_create(NULL, 0);
441          int i = 0;
442          while (arguments[i] != NULL) {
443              xpc_object_t xpcString = xpc_string_create(arguments[i++]);
444              xpc_array_append_value(paramsArray, xpcString);
445              xpc_release(xpcString);
446          }
447          xpc_dictionary_set_value(message, PARAM_TOOL_PARAMS, paramsArray);
448          xpc_release(paramsArray);
449      }
450      xpc_dictionary_set_data(message, PARAM_AUTHREF, extAuthorization, sizeof(*extAuthorization));
451      
452      retval = errAuthorizationToolExecuteFailure;
453      if (trampolineConnection) {
454          xpc_connection_send_message_with_reply(trampolineConnection, message, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(xpc_object_t event) {
455              xpc_type_t type = xpc_get_type(event);
456              const char *requestId = xpc_dictionary_get_string(event, XPC_REQUEST_ID);
457              if (type == XPC_TYPE_ERROR) {
458                  os_log_error(AUTH_LOG, "Error when trying to communicate with the trampoline");
459              }
460              else if (requestId && strncmp(XPC_REPLY_MSG, requestId, strlen(XPC_REPLY_MSG)) == 0) {
461                  retval = (OSStatus)xpc_dictionary_get_int64(event, RETVAL_STATUS);
462                  if (newProcessPid && retval == errAuthorizationSuccess) {
463                      *newProcessPid = (OSStatus)xpc_dictionary_get_uint64(event, RETVAL_CHILD_PID);
464                  }
465              } else {
466                  os_log_error(AUTH_LOG, "Trampoline returned invalid data");
467              }
468              dispatch_semaphore_signal(sema);
469          });
470          dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
471      } else {
472          os_log_error(AUTH_LOG, "Unable to establish connection to the trampoline");
473      }
474      dispatch_release(sema);
475      
476  finish:
477      if (message) {
478          xpc_release(message);
479      }
480      return retval;
481  }