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 }