1 /* Copyright (c) 2012-2013 Apple Inc. All Rights Reserved. */
5 #include "authd_private.h"
8 #include "authutilities.h"
11 #include "debugging.h"
14 #import <xpc/private.h>
15 #include <Security/AuthorizationPlugin.h>
17 #include <mach/mach.h>
18 #include <servers/bootstrap.h>
19 #include <bootstrap_priv.h>
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"
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))) }
30 __AUTH_BASE_STRUCT_HEADER__
;
36 PluginState pluginState
;
39 xpc_connection_t agentConnection
;
40 xpc_connection_t agentStubConnection
;
41 dispatch_queue_t eventQueue
;
42 dispatch_queue_t actionQueue
;
48 _agent_finalize(CFTypeRef value
)
50 agent_t agent
= (agent_t
)value
;
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
;
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
;
64 if (NULL
!= agent
->agentStubConnection
) {
65 xpc_release(agent
->agentStubConnection
);
66 agent
->agentStubConnection
= NULL
;
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.
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
);
83 dispatch_release(agent
->eventQueue
);
85 LOGD("agent[%i]: _agent_finalize called", agent
->agentPid
);
88 AUTH_TYPE_INSTANCE(agent
,
91 .finalize
= _agent_finalize
,
94 .copyFormattingDesc
= NULL
,
98 static CFTypeID
agent_get_type_id() {
99 static CFTypeID type_id
= _kCFRuntimeNotATypeID
;
100 static dispatch_once_t onceToken
;
102 dispatch_once(&onceToken
, ^{
103 type_id
= _CFRuntimeRegisterClass(&_auth_type_agent
);
110 agent_create(engine_t engine
, mechanism_t mech
, auth_token_t auth
, process_t proc
, bool firstMech
)
112 bool doSwitchAudit
= false;
113 bool doSwitchBootstrap
= false;
114 agent_t agent
= NULL
;
116 agent
= (agent_t
)_CFRuntimeCreateInstance(kCFAllocatorDefault
, agent_get_type_id(), AUTH_CLASS_SIZE(agent
), NULL
);
117 require(agent
!= NULL
, done
);
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
;
127 const audit_info_s
*audit_info
= auth_token_get_audit_info(auth
);
129 auditinfo_addr_t tempAddr
;
130 tempAddr
.ai_asid
= audit_info
->asid
;
131 auditon(A_GETSINFO_ADDR
, &tempAddr
, sizeof(tempAddr
));
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
);
137 agent
->eventQueue
= dispatch_queue_create("Agent Event Queue", 0);
138 agent
->actionQueue
= dispatch_queue_create("Agent Action Queue", 0);
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
);
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
);
155 LOGV("agent[%i]: Error response received from stub", agent
->agentPid
);
157 xpc_release(wakeupMessage
);
158 xpc_release(responseMessage
);
160 mach_port_t newBootstrapPort
= auth_token_get_creator_bootstrap(auth
);
161 if (newBootstrapPort
!= MACH_PORT_NULL
) {
162 bootstrapPort
= newBootstrapPort
;
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;
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;
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
);
186 if (agent
->pluginState
== dead
) {
187 // If the agent is dead for some reason, drop this message before we hurt ourselves.
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
);
200 xpc_connection_resume(agent
->agentConnection
);
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
));
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
);
213 if (mach_port_mod_refs(mach_task_self(), jobPort
, MACH_PORT_RIGHT_SEND
, -1) != KERN_SUCCESS
) {
214 LOGE("unable to release send right for audit session, leaking");
220 if (doSwitchBootstrap
) {
221 LOGV("agent[%i]: attaching a bootstrap port", agent
->agentPid
);
222 xpc_dictionary_set_mach_send(requestObject
, AUTH_XPC_BOOTSTRAP_PORT
, bootstrapPort
);
225 // This loop will be repeated until we can get ahold of a SecurityAgent, or get a fatal error
226 // This will cause us to retry any CONNECTION_INTERRUPTED status messages
228 xpc_object_t object
= xpc_connection_send_message_with_reply_sync(agent
->agentConnection
, requestObject
);
230 if (xpc_get_type(object
) == XPC_TYPE_DICTIONARY
) {
231 const char *replyType
= xpc_dictionary_get_string(object
, AUTH_XPC_REPLY_METHOD_KEY
);
232 if (0 == strcmp(replyType
, AUTH_XPC_REPLY_METHOD_CREATE
)) {
233 agent
->status
= xpc_dictionary_get_uint64(object
, AUTH_XPC_REPLY_RESULT_VALUE
);
234 if (agent
->status
== kAuthorizationResultAllow
) {
235 // Only go here if we have an "allow" from the SecurityAgent (the plugin create might have failed)
236 // This is backwards compatible so that it goes gently into the build, as the default is "allow".
237 agent
->pluginState
= created
;
239 agent
->pluginState
= dead
;
242 } else if (xpc_get_type(object
) == XPC_TYPE_ERROR
) {
243 if (object
== XPC_ERROR_CONNECTION_INVALID
) {
244 agent
->pluginState
= dead
;
248 } while ((agent
->pluginState
== init
) && (firstMech
));
250 xpc_release(requestObject
);
252 if (agent
->pluginState
!= created
) {
261 agent_run(agent_t agent
, auth_items_t hints
, auth_items_t context
, auth_items_t immutable_hints
)
263 xpc_object_t hintsArray
= auth_items_export_xpc(hints
);
264 xpc_object_t contextArray
= auth_items_export_xpc(context
);
265 xpc_object_t immutableHintsArray
= auth_items_export_xpc(immutable_hints
);
267 dispatch_semaphore_t replyWaiter
= dispatch_semaphore_create(0);
268 dispatch_sync(agent
->actionQueue
, ^{
269 if (agent
->pluginState
!= mechinterrupting
) {
270 agent
->pluginState
= current
;
271 agent
->status
= kAuthorizationResultUndefined
; // Set this while the plugin chews on the user input.
273 auth_items_clear(agent
->hints
);
274 auth_items_clear(agent
->context
);
276 xpc_object_t requestObject
= xpc_dictionary_create(NULL
, NULL
, 0);
277 xpc_dictionary_set_string(requestObject
, AUTH_XPC_REQUEST_METHOD_KEY
, AUTH_XPC_REQUEST_METHOD_INVOKE
);
278 xpc_dictionary_set_value(requestObject
, AUTH_XPC_HINTS_NAME
, hintsArray
);
279 xpc_dictionary_set_value(requestObject
, AUTH_XPC_CONTEXT_NAME
, contextArray
);
280 xpc_dictionary_set_value(requestObject
, AUTH_XPC_IMMUTABLE_HINTS_NAME
, immutableHintsArray
);
282 xpc_connection_send_message_with_reply(agent
->agentConnection
, requestObject
, agent
->actionQueue
, ^(xpc_object_t object
){
283 if (xpc_get_type(object
) == XPC_TYPE_DICTIONARY
) {
284 const char *replyType
= xpc_dictionary_get_string(object
, AUTH_XPC_REPLY_METHOD_KEY
);
285 if (0 == strcmp(replyType
, AUTH_XPC_REPLY_METHOD_RESULT
)) {
286 if (agent
->pluginState
== current
) {
287 xpc_object_t xpcContext
= xpc_dictionary_get_value(object
, AUTH_XPC_CONTEXT_NAME
);
288 xpc_object_t xpcHints
= xpc_dictionary_get_value(object
, AUTH_XPC_HINTS_NAME
);
289 auth_items_copy_xpc(agent
->context
, xpcContext
);
290 auth_items_copy_xpc(agent
->hints
, xpcHints
);
291 agent
->status
= xpc_dictionary_get_uint64(object
, AUTH_XPC_REPLY_RESULT_VALUE
);
292 agent
->pluginState
= active
;
295 } else if (xpc_get_type(object
) == XPC_TYPE_ERROR
) {
296 if ((object
== XPC_ERROR_CONNECTION_INVALID
) || (object
== XPC_ERROR_CONNECTION_INTERRUPTED
)) {
297 agent
->pluginState
= dead
;
300 dispatch_semaphore_signal(replyWaiter
);
302 xpc_release(requestObject
);
306 if (agent
->pluginState
== current
) {
307 dispatch_semaphore_wait(replyWaiter
, DISPATCH_TIME_FOREVER
);
309 dispatch_release(replyWaiter
);
311 LOGV("agent[%i]: Finished call to SecurityAgent", agent
->agentPid
);
313 xpc_release(hintsArray
);
314 xpc_release(contextArray
);
315 xpc_release(immutableHintsArray
);
317 return agent
->status
;
321 agent_get_hints(agent_t agent
)
327 agent_get_context(agent_t agent
)
329 return agent
->context
;
333 agent_get_mechanism(agent_t agent
)
339 agent_deactivate(agent_t agent
)
341 xpc_object_t requestObject
= xpc_dictionary_create(NULL
, NULL
, 0);
342 xpc_dictionary_set_string(requestObject
, AUTH_XPC_REQUEST_METHOD_KEY
, AUTH_XPC_REQUEST_METHOD_DEACTIVATE
);
344 agent
->pluginState
= deactivating
;
346 xpc_object_t object
= xpc_connection_send_message_with_reply_sync(agent
->agentConnection
, requestObject
);
347 if (xpc_get_type(object
) == XPC_TYPE_DICTIONARY
) {
348 const char *replyType
= xpc_dictionary_get_string(object
, AUTH_XPC_REPLY_METHOD_KEY
);
349 if (0 == strcmp(replyType
, AUTH_XPC_REPLY_METHOD_DEACTIVATE
)) {
350 agent
->pluginState
= active
; // This is strange, but true. We do actually want to set the state 'active'.
352 } else if (xpc_get_type(object
) == XPC_TYPE_ERROR
) {
353 if ((object
== XPC_ERROR_CONNECTION_INVALID
) || (object
== XPC_ERROR_CONNECTION_INTERRUPTED
)) {
354 agent
->pluginState
= dead
;
358 xpc_release(requestObject
);
361 void agent_destroy(agent_t agent
)
363 xpc_object_t requestObject
= xpc_dictionary_create(NULL
, NULL
, 0);
364 xpc_dictionary_set_string(requestObject
, AUTH_XPC_REQUEST_METHOD_KEY
, AUTH_XPC_REQUEST_METHOD_DESTROY
);
365 xpc_connection_send_message(agent
->agentConnection
, requestObject
);
366 xpc_release(requestObject
);
370 agent_get_state(agent_t agent
)
372 return agent
->pluginState
;
376 agent_notify_interrupt(agent_t agent
)
378 dispatch_sync(agent
->actionQueue
, ^{
379 if (agent
->pluginState
== current
) {
380 xpc_object_t requestObject
= xpc_dictionary_create(NULL
, NULL
, 0);
381 xpc_dictionary_set_string(requestObject
, AUTH_XPC_REQUEST_METHOD_KEY
, AUTH_XPC_REQUEST_METHOD_INTERRUPT
);
382 xpc_connection_send_message(agent
->agentConnection
, requestObject
);
383 xpc_release(requestObject
);
384 } else if (agent
->pluginState
== active
) {
385 agent
->pluginState
= mechinterrupting
;
391 agent_clear_interrupt(agent_t agent
)
393 dispatch_sync(agent
->actionQueue
, ^{
394 if (agent
->pluginState
== mechinterrupting
) {
395 agent
->pluginState
= active
;
401 agent_recieve(agent_t agent
)