]> git.saurik.com Git - apple/securityd.git/blob - libsecurity_agent/lib/agentclient.cpp
securityd-55130.10.tar.gz
[apple/securityd.git] / libsecurity_agent / lib / agentclient.cpp
1 /*
2 * agentclient.cpp
3 * SecurityAgent
4 *
5 * Copyright (c) 2002,2008 Apple Inc.. All rights reserved.
6 *
7 */
8
9
10 #include <stdio.h>
11
12 /*
13 For now all the calls into agentclient will be synchronous, with timeouts
14
15 On a timeout, we will return control to the client, but we really need to send the appropriate abort right there and then, otherwise they'll need to call the same method again to check that the reply still isn't there.
16
17 If we receive a reply that is not confirming attempts to abort, we'll process these and return them to the caller.
18
19 Alternatively, there can be an answer that isn't the answer we expected: setError, where the server aborts the transaction.
20
21 We can't support interrupt() with a synchronous interface unless we add some notification that let's the client know that the "server" is dead
22 */
23
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <grp.h>
27
28 #include <security_agent_server/sa_reply.h> // for size of replies
29 #include <security_agent_client/sa_request.h>
30
31 #include <security_utilities/mach++.h>
32 #include <security_cdsa_utilities/walkers.h>
33 #include <security_cdsa_utilities/cssmwalkers.h>
34 #include <security_cdsa_utilities/AuthorizationWalkers.h>
35 #include <security_cdsa_utilities/AuthorizationData.h>
36
37 #include <security_utilities/logging.h>
38
39 using LowLevelMemoryUtilities::increment;
40 using LowLevelMemoryUtilities::difference;
41 using Security::DataWalkers::walk;
42
43 using Authorization::AuthItemSet;
44 using Authorization::AuthItemRef;
45 using Authorization::AuthValueOverlay;
46
47 #include "agentclient.h"
48
49 namespace SecurityAgent {
50
51 class CheckingReconstituteWalker {
52 public:
53 CheckingReconstituteWalker(void *ptr, void *base, size_t size)
54 : mBase(base), mLimit(increment(base, size)), mOffset(difference(ptr, base)) { }
55
56 template <class T>
57 void operator () (T &obj, size_t size = sizeof(T))
58 { }
59
60 template <class T>
61 void operator () (T * &addr, size_t size = sizeof(T))
62 {
63 blob(addr, size);
64 }
65
66 template <class T>
67 void blob(T * &addr, size_t size)
68 {
69 DEBUGWALK("checkreconst:*");
70 if (addr) {
71 if (addr < mBase || increment(addr, size) > mLimit)
72 MacOSError::throwMe(errAuthorizationInternal);
73 addr = increment<T>(addr, mOffset);
74 }
75 }
76
77 static const bool needsRelinking = true;
78 static const bool needsSize = false;
79
80 private:
81 void *mBase; // old base address
82 void *mLimit; // old last byte address + 1
83 off_t mOffset; // relocation offset
84 };
85
86 template <class T>
87 void relocate(T *obj, T *base, size_t size)
88 {
89 if (obj) {
90 CheckingReconstituteWalker w(obj, base, size);
91 walk(w, base);
92 }
93 }
94
95 void Client::check(mach_msg_return_t returnCode)
96 {
97 // first check the Mach IPC return code
98 switch (returnCode) {
99 case MACH_MSG_SUCCESS: // peachy
100 break;
101 case MIG_SERVER_DIED: // explicit can't-send-it's-dead
102 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
103 default: // some random Mach error
104 MachPlusPlus::Error::throwMe(returnCode);
105 }
106 }
107
108 void Client::checkResult()
109 {
110 // now check the OSStatus return from the server side
111 switch (result()) {
112 case kAuthorizationResultAllow: return;
113 case kAuthorizationResultDeny:
114 case kAuthorizationResultUserCanceled: CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
115 default: MacOSError::throwMe(errAuthorizationInternal);
116 }
117 }
118
119
120
121 #pragma mark administrative operations
122
123 Client::Client() : mState(init), mTerminateOnSleep(false)
124 {
125 // create reply port
126 mClientPort.allocate(); //implicit MACH_PORT_RIGHT_RECEIVE
127
128 // register with agentclients
129 Clients::gClients().insert(this);
130
131 // add this client to the global list so that we can kill it if we have to
132 StLock<Mutex> _(Clients::gAllClientsMutex());
133 Clients::allClients().insert(this);
134 }
135
136 void
137 Client::activate(Port serverPort)
138 {
139 if (!serverPort)
140 MacOSError::throwMe(errAuthorizationInternal);
141
142 secdebug("agentclient", "using server at port %d", serverPort.port());
143 mServerPort = serverPort;
144 }
145
146
147 Client::~Client()
148 {
149 teardown();
150 }
151
152
153 // start/endTransaction calls stand outside the usual client protocol: they
154 // don't participate in the state-management or multiplexing-by-port tangoes.
155 // (These calls could take advantage of activate(), but callers would be
156 // instantiating an entire Client object for the sake of mServerPort.)
157 // Conversely, SecurityAgent::Client does not cache transaction state.
158 OSStatus
159 Client::startTransaction(Port serverPort)
160 {
161 if (!serverPort)
162 return errAuthorizationInternal;
163 kern_return_t ret = sa_request_client_txStart(serverPort);
164 secdebug("agentclient", "Transaction started (port %u)", serverPort.port());
165 return ret;
166 }
167
168 OSStatus
169 Client::endTransaction(Port serverPort)
170 {
171 if (!serverPort)
172 return errAuthorizationInternal;
173 secdebug("agentclient", "Requesting end of transaction (port %u)", serverPort.port());
174 return sa_request_client_txEnd(serverPort);
175 }
176
177 void
178 Client::setState(PluginState inState)
179 {
180 // validate state transition: might be more useful where change is requested if that implies anything to interpreting what to do.
181 // Mutex
182 mState = inState;
183 }
184
185 void Client::teardown() throw()
186 {
187 // remove this from the global list
188 {
189 StLock<Mutex> _(Clients::gAllClientsMutex());
190 Clients::allClients().erase(this);
191 }
192
193 Clients::gClients().remove(this);
194
195 try {
196 if (mStagePort)
197 mStagePort.destroy();
198 if (mClientPort)
199 mClientPort.destroy();
200 } catch (...) { secdebug("agentclient", "ignoring problems tearing down ports for client %p", this); }
201 }
202
203
204 AuthItemSet
205 Client::clientHints(SecurityAgent::RequestorType type, std::string &path, pid_t clientPid, uid_t clientUid)
206 {
207 AuthItemSet clientHints;
208
209 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type)));
210 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(path)));
211 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid)));
212 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid)));
213
214 return clientHints;
215 }
216
217
218 #pragma mark request operations
219
220 OSStatus Client::contact(mach_port_t jobId, Bootstrap processBootstrap, mach_port_t userPrefs)
221 {
222 kern_return_t ret = sa_request_client_contact(mServerPort, mClientPort, jobId, processBootstrap, userPrefs);
223 if (ret)
224 {
225 Syslog::error("SecurityAgent::Client::contact(): kern_return error %s",
226 mach_error_string(ret));
227 }
228 return ret;
229 }
230
231 OSStatus Client::create(const char *inPluginId, const char *inMechanismId, const SessionId inSessionId)
232 {
233 // securityd is already notified when the agent/authhost dies through SIGCHILD, and we only really care about the stage port, but we will track if dying happens during create with a DPN. If two threads are both in the time between the create message and the didcreate answer and the host dies, one will be stuck - too risky and I will win the lottery before that: Chablis.
234 {
235 kern_return_t ret;
236 mach_port_t old_port = MACH_PORT_NULL;
237 ret = mach_port_request_notification(mach_task_self(), mServerPort, MACH_NOTIFY_DEAD_NAME, 0, mClientPort, MACH_MSG_TYPE_MAKE_SEND_ONCE, &old_port);
238 if (!ret && (MACH_PORT_NULL != old_port))
239 mach_port_deallocate(mach_task_self(), old_port);
240 }
241
242 secdebug("agentclient", "asking server at port %d to create %s:%s; replies to %d", mServerPort.port(), inPluginId, inMechanismId, mClientPort.port()); // XXX/cs
243 kern_return_t ret = sa_request_client_create(mServerPort, mClientPort, inSessionId, inPluginId, inMechanismId);
244
245 if (ret)
246 return ret;
247
248 // wait for message (either didCreate, reportError)
249 do
250 {
251 // one scenario that could happen here (and only here) is:
252 // host died before create finished - in which case we'll get a DPN
253 try { receive(); } catch (...) { setState(dead); }
254 }
255 while ((state() != created) &&
256 (state() != dead));
257
258 // verify that we got didCreate
259 if (state() == created)
260 return noErr;
261
262 // and not reportError
263 if (state() == dead)
264 return Client::getError();
265
266 // something we don't deal with
267 secdebug("agentclient", "we got an error on create"); // XXX/cs
268 return errAuthorizationInternal;
269 }
270
271 // client maintains their own copy of the current data
272 OSStatus Client::invoke()
273 {
274 if ((state() != created) &&
275 (state() != active) &&
276 (state() != interrupting))
277 return errAuthorizationInternal;
278
279 AuthorizationValueVector *arguments;
280 AuthorizationItemSet *hints, *context;
281 size_t argumentSize, hintSize, contextSize;
282
283 mInHints.copy(hints, hintSize);
284 mInContext.copy(context, contextSize);
285 mArguments.copy(&arguments, &argumentSize);
286
287 setState(current);
288
289 check(sa_request_client_invoke(mStagePort.port(),
290 arguments, argumentSize, arguments, // data, size, offset
291 hints, hintSize, hints,
292 context, contextSize, context));
293
294 receive();
295
296 free (arguments);
297 free (hints);
298 free (context);
299
300 switch(state())
301 {
302 case active:
303 switch(result())
304 {
305 case kAuthorizationResultUndefined:
306 MacOSError::throwMe(errAuthorizationInternal);
307 default:
308 return noErr;
309 }
310 case dead:
311 return mErrorState;
312 case current:
313 return noErr;
314 default:
315 break;
316 }
317 return errAuthorizationInternal;
318 }
319
320 OSStatus
321 Client::deactivate()
322 {
323 // check state is current
324 if (state() != current)
325 return errAuthorizationInternal;
326
327 secdebug("agentclient", "deactivating mechanism at request port %d", mStagePort.port());
328
329 // tell mechanism to deactivate
330 check(sa_request_client_deactivate(mStagePort.port()));
331
332 setState(deactivating);
333
334 receive();
335
336 // if failed destroy it
337 return noErr;
338 }
339
340 OSStatus
341 Client::destroy()
342 {
343 if (state() == active || state() == created || state() == current)
344 {
345 secdebug("agentclient", "destroying mechanism at request port %d", mStagePort.port());
346 // tell mechanism to destroy
347 if (mStagePort)
348 sa_request_client_destroy(mStagePort.port());
349
350 setState(dead);
351
352 return noErr;
353 }
354
355 return errAuthorizationInternal;
356 }
357
358 // kill host: do not pass go, do not collect $200
359 OSStatus
360 Client::terminate()
361 {
362 check(sa_request_client_terminate(mServerPort.port()));
363
364 return noErr;
365 }
366
367 void
368 Client::receive()
369 {
370 bool gotReply = false;
371 while (!gotReply)
372 gotReply = Clients::gClients().receive();
373 }
374
375 #pragma mark result operations
376
377 void Client::setResult(const AuthorizationResult inResult, const AuthorizationItemSet *inHints, const AuthorizationItemSet *inContext)
378 {
379 if (state() != current)
380 return;
381 // construct AuthItemSet for hints and context (deep copy - previous contents are released)
382 mOutHints = (*inHints);
383 mOutContext = (*inContext);
384 mResult = inResult;
385 setState(active);
386 }
387
388 void Client::setError(const OSStatus inMechanismError)
389 {
390 setState(dead);
391
392 mErrorState = inMechanismError;
393 }
394
395 OSStatus Client::getError()
396 {
397 return mErrorState;
398 }
399
400 void Client::requestInterrupt()
401 {
402 if (state() != active)
403 return;
404
405 setState(interrupting);
406 }
407
408 void Client::didDeactivate()
409 {
410 if (state() != deactivating)
411 return;
412
413 // check state for deactivating
414 // change state
415 setState(active);
416 }
417
418 void Client::setStagePort(const mach_port_t inStagePort)
419 {
420 mStagePort = Port(inStagePort);
421 mStagePort.requestNotify(mClientPort, MACH_NOTIFY_DEAD_NAME, 0);
422 }
423
424
425 void Client::didCreate(const mach_port_t inStagePort)
426 {
427 // it can be dead, because the host died, we'll always try to revive it once
428 if ((state() != init) && (state() != dead))
429 return;
430
431 setStagePort(inStagePort);
432 setState(created);
433 }
434
435 #pragma mark client instances
436
437 ThreadNexus<Clients> Clients::gClients;
438 ModuleNexus<set<Client*> > Clients::allClients;
439
440 bool
441 Clients::compare(const Client * client, mach_port_t instance)
442 {
443 if (client->instance() == instance) return true;
444 return false;
445 }
446
447 // throw so the agent client operation is aborted
448 Client&
449 Clients::find(mach_port_t instanceReplyPort) const
450 {
451 StLock<Mutex> _(mLock);
452 for (set<Client*>::const_iterator foundClient = mClients.begin();
453 foundClient != mClients.end();
454 foundClient++)
455 {
456 Client *client = *foundClient;
457 if (client->instance() == instanceReplyPort)
458 return *client;
459 }
460
461 // can't be receiving for a client we didn't create
462 MacOSError::throwMe(errAuthorizationInternal);
463 }
464
465 bool
466 Clients::receive()
467 {
468 try
469 {
470 // maximum known message size (variable sized elements are already forced OOL)
471 Message in(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem));
472 Message out(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem));
473
474 in.receive(mClientPortSet, 0, 0);
475
476 // got the message, now demux it; call secagentreply_server to handle any call
477 // this is asynchronous, so no reply message, although not apparent
478 if (!::secagentreply_server(in, out))
479 {
480 // port death notification
481 if (MACH_NOTIFY_DEAD_NAME == in.msgId())
482 {
483 find(in.remotePort()).setError(errAuthorizationInternal);
484 return true;
485 }
486 return false;
487
488 }
489 else
490 return true;
491 }
492 catch (Security::MachPlusPlus::Error &e)
493 {
494 secdebug("agentclient", "interpret error %ul", e.error);
495 switch (e.error) {
496 case MACH_MSG_SUCCESS: // peachy
497 break;
498 case MIG_SERVER_DIED: // explicit can't-send-it's-dead
499 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
500 default: // some random Mach error
501 MachPlusPlus::Error::throwMe(e.error);
502 }
503 }
504 catch (...)
505 {
506 MacOSError::throwMe(errAuthorizationInternal);
507 }
508 return false;
509 }
510
511 ModuleNexus<RecursiveMutex> Clients::gAllClientsMutex;
512
513 void
514 Clients::killAllClients()
515 {
516 // grab the lock for the client list -- we need to make sure no one modifies the structure while we are iterating it.
517 StLock<Mutex> _(gAllClientsMutex());
518
519 set<Client*>::iterator clientIterator = allClients().begin();
520 while (clientIterator != allClients().end())
521 {
522 set<Client*>::iterator thisClient = clientIterator++;
523 if ((*thisClient)->getTerminateOnSleep())
524 {
525 (*thisClient)->terminate();
526 }
527 }
528 }
529
530
531
532 } /* end namesapce SecurityAgent */
533
534 #pragma mark demux requests replies
535 // external C symbols for the mig message handling code to call into
536
537 #define COPY_IN(type,name) type *name, mach_msg_type_number_t name##Length, type *name##Base
538
539 // callbacks that key off instanceReplyPort to find the right agentclient instance
540 // to deliver the message to.
541
542 // they make the data readable to the receiver (relocate internal references)
543
544 kern_return_t sa_reply_server_didCreate(mach_port_t instanceReplyPort, mach_port_t instanceRequestPort)
545 {
546 secdebug("agentclient", "got didCreate at port %u; requests go to port %u", instanceReplyPort, instanceRequestPort);
547 SecurityAgent::Clients::gClients().find(instanceReplyPort).didCreate(instanceRequestPort);
548 return KERN_SUCCESS;
549 }
550
551 kern_return_t sa_reply_server_setResult(mach_port_t instanceReplyPort, AuthorizationResult result,
552 COPY_IN(AuthorizationItemSet,inHints) ,
553 COPY_IN(AuthorizationItemSet,inContext) )
554 {
555 secdebug("agentclient", "got setResult at port %u; result %u", instanceReplyPort, (unsigned int)result);
556
557 // relink internal references according to current place in memory
558 try { SecurityAgent::relocate(inHints, inHintsBase, inHintsLength); }
559 catch (MacOSError &e) { return e.osStatus(); }
560 catch (...) { return errAuthorizationInternal; }
561
562 try { SecurityAgent::relocate(inContext, inContextBase, inContextLength); }
563 catch (MacOSError &e) { return e.osStatus(); }
564 catch (...) { return errAuthorizationInternal; }
565
566 SecurityAgent::Clients::gClients().find(instanceReplyPort).setResult(result, inHints, inContext);
567
568 return KERN_SUCCESS;
569 }
570
571 kern_return_t sa_reply_server_requestInterrupt(mach_port_t instanceReplyPort)
572 {
573 secdebug("agentclient", "got requestInterrupt at port %u", instanceReplyPort);
574 SecurityAgent::Clients::gClients().find(instanceReplyPort).requestInterrupt();
575 return KERN_SUCCESS;
576 }
577
578 kern_return_t sa_reply_server_didDeactivate(mach_port_t instanceReplyPort)
579 {
580 secdebug("agentclient", "got didDeactivate at port %u", instanceReplyPort);
581 SecurityAgent::Clients::gClients().find(instanceReplyPort).didDeactivate();
582 return KERN_SUCCESS;
583 }
584
585 kern_return_t sa_reply_server_reportError(mach_port_t instanceReplyPort, OSStatus status)
586 {
587 secdebug("agentclient", "got reportError at port %u; error is %u", instanceReplyPort, (unsigned int)status);
588 SecurityAgent::Clients::gClients().find(instanceReplyPort).setError(status);
589 return KERN_SUCCESS;
590 }
591
592 kern_return_t sa_reply_server_didStartTx(mach_port_t replyPort, kern_return_t retval)
593 {
594 // no instance ports here: this goes straight to server
595 secdebug("agentclient", "got didStartTx");
596 return retval;
597 }