1/* Copyright (c) 2012 Apple Inc. All rights reserved. */ 2 3#include "agent.h" 4#include "authitems.h" 5#include "authd_private.h" 6#include "process.h" 7#include "mechanism.h" 8#include "authutilities.h" 9#include "authtoken.h" 10#include "session.h" 11#include "debugging.h" 12#include "engine.h" 13 14#import <xpc/private.h> 15#include <Security/AuthorizationPlugin.h> 16 17#include <mach/mach.h> 18#include <servers/bootstrap.h> 19#include <bootstrap_priv.h> 20 21#define SECURITYAGENT_BOOTSTRAP_NAME_BASE "com.apple.security.agentMain" 22#define SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE "com.apple.security.agentStub" 23//#define SECURITYAGENT_LOGINWINDOW_BOOTSTRAP_NAME_BASE "com.apple.security.agent.login" 24#define AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE "com.apple.security.authhost" 25 26#define UUID_INITIALIZER_FROM_SESSIONID(sessionid) \ 27{ 0,0,0,0, 0,0,0,0, 0,0,0,0, (unsigned char)((0xff000000 & (sessionid))>>24), (unsigned char)((0x00ff0000 & (sessionid))>>16), (unsigned char)((0x0000ff00 & (sessionid))>>8), (unsigned char)((0x000000ff & (sessionid))) } 28 29struct _agent_s { 30 __AUTH_BASE_STRUCT_HEADER__; 31 32 auth_items_t hints; 33 auth_items_t context; 34 mechanism_t mech; 35 engine_t engine; 36 PluginState pluginState; 37 uint64_t status; 38 39 xpc_connection_t agentConnection; 40 xpc_connection_t agentStubConnection; 41 dispatch_queue_t eventQueue; 42 dispatch_queue_t actionQueue; 43 44 pid_t agentPid; 45}; 46 47static void 48_agent_finalize(CFTypeRef value) 49{ 50 agent_t agent = (agent_t)value; 51 52 // If this ever becomes a concurrent queue, then this would need to be a barrier sync 53 dispatch_sync(agent->eventQueue, ^{ 54 // Mark the agent as dead 55 agent->pluginState = dead; 56 }); 57 58 // We're going away, which means no outside references exist. It's safe to dispose of the XPC connection. 59 if (NULL != agent->agentConnection) { 60 xpc_release(agent->agentConnection); 61 agent->agentConnection = NULL; 62 } 63 64 if (NULL != agent->agentStubConnection) { 65 xpc_release(agent->agentStubConnection); 66 agent->agentStubConnection = NULL; 67 } 68 69 // Now that we've released any XPC connection that may (or may not) be present 70 // it's safe to go ahead and free our memory. This is provided that all other 71 // blocks that were added to the event queue before the axe came down on the 72 // xpc connection have been executed. 73 74 // If this ever becomes a concurrent queue, then this would need to be a barrier sync 75 dispatch_sync(agent->eventQueue, ^{ 76 CFReleaseSafe(agent->hints); 77 CFReleaseSafe(agent->context); 78 CFReleaseSafe(agent->mech); 79 CFReleaseSafe(agent->engine); 80 dispatch_release(agent->actionQueue); 81 }); 82 83 dispatch_release(agent->eventQueue); 84 85 LOGD("agent[%i]: _agent_finalize called", agent->agentPid); 86} 87 88AUTH_TYPE_INSTANCE(agent, 89 .init = NULL, 90 .copy = NULL, 91 .finalize = _agent_finalize, 92 .equal = NULL, 93 .hash = NULL, 94 .copyFormattingDesc = NULL, 95 .copyDebugDesc = NULL 96 ); 97 98static CFTypeID agent_get_type_id() { 99 static CFTypeID type_id = _kCFRuntimeNotATypeID; 100 static dispatch_once_t onceToken; 101 102 dispatch_once(&onceToken, ^{ 103 type_id = _CFRuntimeRegisterClass(&_auth_type_agent); 104 }); 105 106 return type_id; 107} 108 109agent_t 110agent_create(engine_t engine, mechanism_t mech, auth_token_t auth, process_t proc, bool firstMech) 111{ 112 bool doSwitchAudit = false; 113 bool doSwitchBootstrap = false; 114 agent_t agent = NULL; 115 116 agent = (agent_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, agent_get_type_id(), AUTH_CLASS_SIZE(agent), NULL); 117 require(agent != NULL, done); 118 119 agent->mech = (mechanism_t)CFRetain(mech); 120 agent->engine = (engine_t)CFRetain(engine); 121 agent->hints = auth_items_create(); 122 agent->context = auth_items_create(); 123 agent->pluginState = init; 124 agent->agentPid = process_get_pid(proc); 125 agent->agentStubConnection = NULL; 126 127 const audit_info_s *audit_info = auth_token_get_audit_info(auth); 128 129 auditinfo_addr_t tempAddr; 130 tempAddr.ai_asid = audit_info->asid; 131 auditon(A_GETSINFO_ADDR, &tempAddr, sizeof(tempAddr)); 132 133 LOGV("agent[%i]: Stored auid %d fetched auid %d", agent->agentPid, audit_info->auid, tempAddr.ai_auid); 134 uid_t auid = tempAddr.ai_auid; 135 uuid_t sessionUUID = UUID_INITIALIZER_FROM_SESSIONID((uint32_t)audit_info->asid); 136 137 agent->eventQueue = dispatch_queue_create("Agent Event Queue", 0); 138 agent->actionQueue = dispatch_queue_create("Agent Action Queue", 0); 139 140 mach_port_t bootstrapPort = process_get_bootstrap(proc); 141 if (!mechanism_is_privileged(mech)) { 142 if ((int32_t)auid != -1) { 143 agent->agentStubConnection = xpc_connection_create_mach_service(SECURITYAGENT_STUB_BOOTSTRAP_NAME_BASE, NULL, 0); 144 xpc_connection_set_target_uid(agent->agentStubConnection, auid); 145 LOGV("agent[%i]: Creating a security agent stub", agent->agentPid); 146 xpc_connection_set_event_handler(agent->agentStubConnection, ^(xpc_object_t object){}); // Yes, this is a dummy handler, we never ever care about any responses from the stub. It can die in a fire for all I care. 147 xpc_connection_resume(agent->agentStubConnection); 148 149 xpc_object_t wakeupMessage = xpc_dictionary_create(NULL, NULL, 0); 150 xpc_dictionary_set_data(wakeupMessage, AUTH_XPC_SESSION_UUID, sessionUUID, sizeof(uuid_t)); 151 xpc_object_t responseMessage = xpc_connection_send_message_with_reply_sync(agent->agentStubConnection, wakeupMessage); 152 if (xpc_get_type(responseMessage) == XPC_TYPE_DICTIONARY) { 153 LOGV("agent[%i]: Valid response received from stub", agent->agentPid); 154 } else { 155 LOGV("agent[%i]: Error response received from stub", agent->agentPid); 156 } 157 xpc_release(wakeupMessage); 158 xpc_release(responseMessage); 159 160 mach_port_t newBootstrapPort = auth_token_get_creator_bootstrap(auth); 161 if (newBootstrapPort != MACH_PORT_NULL) { 162 bootstrapPort = newBootstrapPort; 163 } 164 } 165 166 agent->agentConnection = xpc_connection_create_mach_service(SECURITYAGENT_BOOTSTRAP_NAME_BASE, NULL,0); 167 xpc_connection_set_instance(agent->agentConnection, sessionUUID); 168 LOGV("agent[%i]: Creating a security agent", agent->agentPid); 169 doSwitchAudit = true; 170 doSwitchBootstrap = true; 171 } else { 172 agent->agentConnection = xpc_connection_create_mach_service(AUTHORIZATIONHOST_BOOTSTRAP_NAME_BASE, NULL, 0); 173 xpc_connection_set_instance(agent->agentConnection, sessionUUID); 174 LOGV("agent[%i]: Creating a standard authhost", agent->agentPid); 175 doSwitchAudit = true; 176 doSwitchBootstrap = true; 177 } 178 179 // **************** Here's the event handler, since I can never find it 180 xpc_connection_set_target_queue(agent->agentConnection, agent->eventQueue); 181 xpc_connection_set_event_handler(agent->agentConnection, ^(xpc_object_t object) { 182 char* objectDesc = xpc_copy_description(object); 183 LOGV("agent[%i]: global xpc message received %s", agent->agentPid, objectDesc); 184 free(objectDesc); 185 186 if (agent->pluginState == dead) { 187 // If the agent is dead for some reason, drop this message before we hurt ourselves. 188 return; 189 } 190 191 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) { 192 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY); 193 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_INTERRUPT)) { 194 agent->pluginState = interrupting; 195 engine_interrupt_agent(agent->engine); 196 } 197 } 198 }); 199 200 xpc_connection_resume(agent->agentConnection); 201 202 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0); 203 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_CREATE); 204 xpc_dictionary_set_string(requestObject, AUTH_XPC_PLUGIN_NAME, mechanism_get_plugin(mech)); 205 xpc_dictionary_set_string(requestObject, AUTH_XPC_MECHANISM_NAME, mechanism_get_param(mech)); 206 if (doSwitchAudit) { 207 mach_port_name_t jobPort; 208 if (audit_info != NULL) { 209 if (0 == audit_session_port(audit_info->asid, &jobPort)) { 210 LOGV("agent[%i]: attaching an audit session port", agent->agentPid); 211 xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_AUDIT_SESSION_PORT, jobPort); 212 } 213 } 214 } 215 216 if (doSwitchBootstrap) { 217 LOGV("agent[%i]: attaching a bootstrap port", agent->agentPid); 218 xpc_dictionary_set_mach_send(requestObject, AUTH_XPC_BOOTSTRAP_PORT, bootstrapPort); 219 } 220 221 // This loop will be repeated until we can get ahold of a SecurityAgent, or get a fatal error 222 // This will cause us to retry any CONNECTION_INTERRUPTED status messages 223 do { 224 xpc_object_t object = xpc_connection_send_message_with_reply_sync(agent->agentConnection, requestObject); 225 226 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) { 227 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY); 228 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_CREATE)) { 229 agent->status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE); 230 if (agent->status == kAuthorizationResultAllow) { 231 // Only go here if we have an "allow" from the SecurityAgent (the plugin create might have failed) 232 // This is backwards compatible so that it goes gently into the build, as the default is "allow". 233 agent->pluginState = created; 234 } else { 235 agent->pluginState = dead; 236 } 237 } 238 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) { 239 if (object == XPC_ERROR_CONNECTION_INVALID) { 240 agent->pluginState = dead; 241 } 242 } 243 xpc_release(object); 244 } while ((agent->pluginState == init) && (firstMech)); 245 246 xpc_release(requestObject); 247 248 if (agent->pluginState != created) { 249 CFRelease(agent); 250 agent = NULL; 251 } 252done: 253 return agent; 254} 255 256uint64_t 257agent_run(agent_t agent, auth_items_t hints, auth_items_t context, auth_items_t immutable_hints) 258{ 259 xpc_object_t hintsArray = auth_items_export_xpc(hints); 260 xpc_object_t contextArray = auth_items_export_xpc(context); 261 xpc_object_t immutableHintsArray = auth_items_export_xpc(immutable_hints); 262 263 dispatch_semaphore_t replyWaiter = dispatch_semaphore_create(0); 264 dispatch_sync(agent->actionQueue, ^{ 265 if (agent->pluginState != mechinterrupting) { 266 agent->pluginState = current; 267 agent->status = kAuthorizationResultUndefined; // Set this while the plugin chews on the user input. 268 269 auth_items_clear(agent->hints); 270 auth_items_clear(agent->context); 271 272 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0); 273 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INVOKE); 274 xpc_dictionary_set_value(requestObject, AUTH_XPC_HINTS_NAME, hintsArray); 275 xpc_dictionary_set_value(requestObject, AUTH_XPC_CONTEXT_NAME, contextArray); 276 xpc_dictionary_set_value(requestObject, AUTH_XPC_IMMUTABLE_HINTS_NAME, immutableHintsArray); 277 278 xpc_connection_send_message_with_reply(agent->agentConnection, requestObject, agent->actionQueue, ^(xpc_object_t object){ 279 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) { 280 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY); 281 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_RESULT)) { 282 if (agent->pluginState == current) { 283 xpc_object_t xpcContext = xpc_dictionary_get_value(object, AUTH_XPC_CONTEXT_NAME); 284 xpc_object_t xpcHints = xpc_dictionary_get_value(object, AUTH_XPC_HINTS_NAME); 285 auth_items_copy_xpc(agent->context, xpcContext); 286 auth_items_copy_xpc(agent->hints, xpcHints); 287 agent->status = xpc_dictionary_get_uint64(object, AUTH_XPC_REPLY_RESULT_VALUE); 288 agent->pluginState = active; 289 } 290 } 291 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) { 292 if ((object == XPC_ERROR_CONNECTION_INVALID) || (object == XPC_ERROR_CONNECTION_INTERRUPTED)) { 293 agent->pluginState = dead; 294 } 295 } 296 dispatch_semaphore_signal(replyWaiter); 297 }); 298 xpc_release(requestObject); 299 } 300 }); 301 302 if (agent->pluginState == current) { 303 dispatch_semaphore_wait(replyWaiter, DISPATCH_TIME_FOREVER); 304 } 305 dispatch_release(replyWaiter); 306 307 LOGV("agent[%i]: Finished call to SecurityAgent", agent->agentPid); 308 309 xpc_release(hintsArray); 310 xpc_release(contextArray); 311 xpc_release(immutableHintsArray); 312 313 return agent->status; 314} 315 316auth_items_t 317agent_get_hints(agent_t agent) 318{ 319 return agent->hints; 320} 321 322auth_items_t 323agent_get_context(agent_t agent) 324{ 325 return agent->context; 326} 327 328mechanism_t 329agent_get_mechanism(agent_t agent) 330{ 331 return agent->mech; 332} 333 334void 335agent_deactivate(agent_t agent) 336{ 337 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0); 338 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DEACTIVATE); 339 340 agent->pluginState = deactivating; 341 342 xpc_object_t object = xpc_connection_send_message_with_reply_sync(agent->agentConnection, requestObject); 343 if (xpc_get_type(object) == XPC_TYPE_DICTIONARY) { 344 const char *replyType = xpc_dictionary_get_string(object, AUTH_XPC_REPLY_METHOD_KEY); 345 if (0 == strcmp(replyType, AUTH_XPC_REPLY_METHOD_DEACTIVATE)) { 346 agent->pluginState = active; // This is strange, but true. We do actually want to set the state 'active'. 347 } 348 } else if (xpc_get_type(object) == XPC_TYPE_ERROR) { 349 if ((object == XPC_ERROR_CONNECTION_INVALID) || (object == XPC_ERROR_CONNECTION_INTERRUPTED)) { 350 agent->pluginState = dead; 351 } 352 } 353 xpc_release(object); 354 xpc_release(requestObject); 355} 356 357void agent_destroy(agent_t agent) 358{ 359 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0); 360 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_DESTROY); 361 xpc_connection_send_message(agent->agentConnection, requestObject); 362 xpc_release(requestObject); 363} 364 365PluginState 366agent_get_state(agent_t agent) 367{ 368 return agent->pluginState; 369} 370 371void 372agent_notify_interrupt(agent_t agent) 373{ 374 dispatch_sync(agent->actionQueue, ^{ 375 if (agent->pluginState == current) { 376 xpc_object_t requestObject = xpc_dictionary_create(NULL, NULL, 0); 377 xpc_dictionary_set_string(requestObject, AUTH_XPC_REQUEST_METHOD_KEY, AUTH_XPC_REQUEST_METHOD_INTERRUPT); 378 xpc_connection_send_message(agent->agentConnection, requestObject); 379 xpc_release(requestObject); 380 } else if (agent->pluginState == active) { 381 agent->pluginState = mechinterrupting; 382 } 383 }); 384} 385 386void 387agent_clear_interrupt(agent_t agent) 388{ 389 dispatch_sync(agent->actionQueue, ^{ 390 if (agent->pluginState == mechinterrupting) { 391 agent->pluginState = active; 392 } 393 }); 394} 395 396void 397agent_recieve(agent_t agent) 398{ 399} 400