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/* 13For now all the calls into agentclient will be synchronous, with timeouts 14 15On 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 17If we receive a reply that is not confirming attempts to abort, we'll process these and return them to the caller. 18 19Alternatively, there can be an answer that isn't the answer we expected: setError, where the server aborts the transaction. 20 21We 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 39using LowLevelMemoryUtilities::increment; 40using LowLevelMemoryUtilities::difference; 41using Security::DataWalkers::walk; 42 43using Authorization::AuthItemSet; 44using Authorization::AuthItemRef; 45using Authorization::AuthValueOverlay; 46 47#include "agentclient.h" 48 49namespace SecurityAgent { 50 51class CheckingReconstituteWalker { 52public: 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 66template <class T> 67void 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 77static const bool needsRelinking = true; 78static const bool needsSize = false; 79 80private: 81void *mBase; // old base address 82void *mLimit; // old last byte address + 1 83off_t mOffset; // relocation offset 84}; 85 86template <class T> 87void 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 95void 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 108void 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 123Client::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 136void 137Client::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 147Client::~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. 158OSStatus 159Client::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 168OSStatus 169Client::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 177void 178Client::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 185void 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 204AuthItemSet 205Client::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 220OSStatus 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 231OSStatus 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 272OSStatus 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 320OSStatus 321Client::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 340OSStatus 341Client::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 359OSStatus 360Client::terminate() 361{ 362 check(sa_request_client_terminate(mServerPort.port())); 363 364 return noErr; 365} 366 367void 368Client::receive() 369{ 370 bool gotReply = false; 371 while (!gotReply) 372 gotReply = Clients::gClients().receive(); 373} 374 375#pragma mark result operations 376 377void 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 388void Client::setError(const OSStatus inMechanismError) 389{ 390 setState(dead); 391 392 mErrorState = inMechanismError; 393} 394 395OSStatus Client::getError() 396{ 397 return mErrorState; 398} 399 400void Client::requestInterrupt() 401{ 402 if (state() != active) 403 return; 404 405 setState(interrupting); 406} 407 408void Client::didDeactivate() 409{ 410 if (state() != deactivating) 411 return; 412 413 // check state for deactivating 414 // change state 415 setState(active); 416} 417 418void Client::setStagePort(const mach_port_t inStagePort) 419{ 420 mStagePort = Port(inStagePort); 421 mStagePort.requestNotify(mClientPort, MACH_NOTIFY_DEAD_NAME, 0); 422} 423 424 425void 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 437ThreadNexus<Clients> Clients::gClients; 438ModuleNexus<set<Client*> > Clients::allClients; 439 440bool 441Clients::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 448Client& 449Clients::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 465bool 466Clients::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 511ModuleNexus<RecursiveMutex> Clients::gAllClientsMutex; 512 513void 514Clients::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 544kern_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 551kern_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 571kern_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 578kern_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 585kern_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 592kern_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} 598