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) 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 132void 133Client::activate(Port serverPort) 134{ 135 if (!serverPort) 136 MacOSError::throwMe(errAuthorizationInternal); 137 138 secdebug("agentclient", "using server at port %d", serverPort.port()); 139 mServerPort = serverPort; 140} 141 142 143Client::~Client() 144{ 145 teardown(); 146} 147 148 149// start/endTransaction calls stand outside the usual client protocol: they 150// don't participate in the state-management or multiplexing-by-port tangoes. 151// (These calls could take advantage of activate(), but callers would be 152// instantiating an entire Client object for the sake of mServerPort.) 153// Conversely, SecurityAgent::Client does not cache transaction state. 154OSStatus 155Client::startTransaction(Port serverPort) 156{ 157 if (!serverPort) 158 return errAuthorizationInternal; 159 kern_return_t ret = sa_request_client_txStart(serverPort); 160 secdebug("agentclient", "Transaction started (port %u)", serverPort.port()); 161 return ret; 162} 163 164OSStatus 165Client::endTransaction(Port serverPort) 166{ 167 if (!serverPort) 168 return errAuthorizationInternal; 169 secdebug("agentclient", "Requesting end of transaction (port %u)", serverPort.port()); 170 return sa_request_client_txEnd(serverPort); 171} 172 173void 174Client::setState(PluginState inState) 175{ 176 // validate state transition: might be more useful where change is requested if that implies anything to interpreting what to do. 177 // Mutex 178 mState = inState; 179} 180 181void Client::teardown() throw() 182{ 183 Clients::gClients().remove(this); 184 185 try { 186 if (mStagePort) 187 mStagePort.destroy(); 188 if (mClientPort) 189 mClientPort.destroy(); 190 } catch (...) { secdebug("agentclient", "ignoring problems tearing down ports for client %p", this); } 191} 192 193 194AuthItemSet 195Client::clientHints(SecurityAgent::RequestorType type, std::string &path, pid_t clientPid, uid_t clientUid) 196{ 197 AuthItemSet clientHints; 198 199 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(type), &type))); 200 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(path))); 201 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(clientPid), &clientPid))); 202 clientHints.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(clientUid), &clientUid))); 203 204 return clientHints; 205} 206 207 208#pragma mark request operations 209 210OSStatus Client::contact(mach_port_t jobId, Bootstrap processBootstrap, mach_port_t userPrefs) 211{ 212 kern_return_t ret = sa_request_client_contact(mServerPort, mClientPort, jobId, processBootstrap, userPrefs); 213 if (ret) 214 { 215 Syslog::error("SecurityAgent::Client::contact(): kern_return error %s", 216 mach_error_string(ret)); 217 } 218 return ret; 219} 220 221OSStatus Client::create(const char *inPluginId, const char *inMechanismId, const SessionId inSessionId) 222{ 223 // 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. 224 { 225 kern_return_t ret; 226 mach_port_t old_port = MACH_PORT_NULL; 227 ret = mach_port_request_notification(mach_task_self(), mServerPort, MACH_NOTIFY_DEAD_NAME, 0, mClientPort, MACH_MSG_TYPE_MAKE_SEND_ONCE, &old_port); 228 if (!ret && (MACH_PORT_NULL != old_port)) 229 mach_port_deallocate(mach_task_self(), old_port); 230 } 231 232 secdebug("agentclient", "asking server at port %d to create %s:%s; replies to %d", mServerPort.port(), inPluginId, inMechanismId, mClientPort.port()); // XXX/cs 233 kern_return_t ret = sa_request_client_create(mServerPort, mClientPort, inSessionId, inPluginId, inMechanismId); 234 235 if (ret) 236 return ret; 237 238 // wait for message (either didCreate, reportError) 239 do 240 { 241 // one scenario that could happen here (and only here) is: 242 // host died before create finished - in which case we'll get a DPN 243 try { receive(); } catch (...) { setState(dead); } 244 } 245 while ((state() != created) && 246 (state() != dead)); 247 248 // verify that we got didCreate 249 if (state() == created) 250 return noErr; 251 252 // and not reportError 253 if (state() == dead) 254 return Client::getError(); 255 256 // something we don't deal with 257 secdebug("agentclient", "we got an error on create"); // XXX/cs 258 return errAuthorizationInternal; 259} 260 261// client maintains their own copy of the current data 262OSStatus Client::invoke() 263{ 264 if ((state() != created) && 265 (state() != active) && 266 (state() != interrupting)) 267 return errAuthorizationInternal; 268 269 AuthorizationValueVector *arguments; 270 AuthorizationItemSet *hints, *context; 271 size_t argumentSize, hintSize, contextSize; 272 273 mInHints.copy(hints, hintSize); 274 mInContext.copy(context, contextSize); 275 mArguments.copy(&arguments, &argumentSize); 276 277 setState(current); 278 279 check(sa_request_client_invoke(mStagePort.port(), 280 arguments, argumentSize, arguments, // data, size, offset 281 hints, hintSize, hints, 282 context, contextSize, context)); 283 284 receive(); 285 286 free (arguments); 287 free (hints); 288 free (context); 289 290 switch(state()) 291 { 292 case active: 293 switch(result()) 294 { 295 case kAuthorizationResultUndefined: 296 MacOSError::throwMe(errAuthorizationInternal); 297 default: 298 return noErr; 299 } 300 case dead: 301 return mErrorState; 302 case current: 303 return noErr; 304 default: 305 break; 306 } 307 return errAuthorizationInternal; 308} 309 310OSStatus 311Client::deactivate() 312{ 313 // check state is current 314 if (state() != current) 315 return errAuthorizationInternal; 316 317 secdebug("agentclient", "deactivating mechanism at request port %d", mStagePort.port()); 318 319 // tell mechanism to deactivate 320 check(sa_request_client_deactivate(mStagePort.port())); 321 322 setState(deactivating); 323 324 receive(); 325 326 // if failed destroy it 327 return noErr; 328} 329 330OSStatus 331Client::destroy() 332{ 333 if (state() == active || state() == created || state() == current) 334 { 335 secdebug("agentclient", "destroying mechanism at request port %d", mStagePort.port()); 336 // tell mechanism to destroy 337 if (mStagePort) 338 sa_request_client_destroy(mStagePort.port()); 339 340 setState(dead); 341 342 return noErr; 343 } 344 345 return errAuthorizationInternal; 346} 347 348// kill host: do not pass go, do not collect $200 349OSStatus 350Client::terminate() 351{ 352 check(sa_request_client_terminate(mServerPort.port())); 353 354 return noErr; 355} 356 357void 358Client::receive() 359{ 360 bool gotReply = false; 361 while (!gotReply) 362 gotReply = Clients::gClients().receive(); 363} 364 365#pragma mark result operations 366 367void Client::setResult(const AuthorizationResult inResult, const AuthorizationItemSet *inHints, const AuthorizationItemSet *inContext) 368{ 369 if (state() != current) 370 return; 371 // construct AuthItemSet for hints and context (deep copy - previous contents are released) 372 mOutHints = (*inHints); 373 mOutContext = (*inContext); 374 mResult = inResult; 375 setState(active); 376} 377 378void Client::setError(const OSStatus inMechanismError) 379{ 380 setState(dead); 381 382 mErrorState = inMechanismError; 383} 384 385OSStatus Client::getError() 386{ 387 return mErrorState; 388} 389 390void Client::requestInterrupt() 391{ 392 if (state() != active) 393 return; 394 395 setState(interrupting); 396} 397 398void Client::didDeactivate() 399{ 400 if (state() != deactivating) 401 return; 402 403 // check state for deactivating 404 // change state 405 setState(active); 406} 407 408void Client::setStagePort(const mach_port_t inStagePort) 409{ 410 mStagePort = Port(inStagePort); 411 mStagePort.requestNotify(mClientPort, MACH_NOTIFY_DEAD_NAME, 0); 412} 413 414 415void Client::didCreate(const mach_port_t inStagePort) 416{ 417 // it can be dead, because the host died, we'll always try to revive it once 418 if ((state() != init) && (state() != dead)) 419 return; 420 421 setStagePort(inStagePort); 422 setState(created); 423} 424 425#pragma mark client instances 426 427ThreadNexus<Clients> Clients::gClients; 428 429bool 430Clients::compare(const Client * client, mach_port_t instance) 431{ 432 if (client->instance() == instance) return true; 433 return false; 434} 435 436// throw so the agent client operation is aborted 437Client& 438Clients::find(mach_port_t instanceReplyPort) const 439{ 440 StLock<Mutex> _(mLock); 441 for (set<Client*>::const_iterator foundClient = mClients.begin(); 442 foundClient != mClients.end(); 443 foundClient++) 444 { 445 Client *client = *foundClient; 446 if (client->instance() == instanceReplyPort) 447 return *client; 448 } 449 450 // can't be receiving for a client we didn't create 451 MacOSError::throwMe(errAuthorizationInternal); 452} 453 454bool 455Clients::receive() 456{ 457 try 458 { 459 // maximum known message size (variable sized elements are already forced OOL) 460 Message in(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem)); 461 Message out(sizeof(union __ReplyUnion__sa_reply_client_secagentreply_subsystem)); 462 463 in.receive(mClientPortSet, 0, 0); 464 465 // got the message, now demux it; call secagentreply_server to handle any call 466 // this is asynchronous, so no reply message, although not apparent 467 if (!::secagentreply_server(in, out)) 468 { 469 // port death notification 470 if (MACH_NOTIFY_DEAD_NAME == in.msgId()) 471 { 472 find(in.remotePort()).setError(errAuthorizationInternal); 473 return true; 474 } 475 return false; 476 477 } 478 else 479 return true; 480 } 481 catch (Security::MachPlusPlus::Error &e) 482 { 483 secdebug("agentclient", "interpret error %ul", e.error); 484 switch (e.error) { 485 case MACH_MSG_SUCCESS: // peachy 486 break; 487 case MIG_SERVER_DIED: // explicit can't-send-it's-dead 488 CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION); 489 default: // some random Mach error 490 MachPlusPlus::Error::throwMe(e.error); 491 } 492 } 493 catch (...) 494 { 495 MacOSError::throwMe(errAuthorizationInternal); 496 } 497 return false; 498} 499 500} /* end namesapce SecurityAgent */ 501 502#pragma mark demux requests replies 503// external C symbols for the mig message handling code to call into 504 505#define COPY_IN(type,name) type *name, mach_msg_type_number_t name##Length, type *name##Base 506 507// callbacks that key off instanceReplyPort to find the right agentclient instance 508// to deliver the message to. 509 510// they make the data readable to the receiver (relocate internal references) 511 512kern_return_t sa_reply_server_didCreate(mach_port_t instanceReplyPort, mach_port_t instanceRequestPort) 513{ 514 secdebug("agentclient", "got didCreate at port %u; requests go to port %u", instanceReplyPort, instanceRequestPort); 515 SecurityAgent::Clients::gClients().find(instanceReplyPort).didCreate(instanceRequestPort); 516 return KERN_SUCCESS; 517} 518 519kern_return_t sa_reply_server_setResult(mach_port_t instanceReplyPort, AuthorizationResult result, 520 COPY_IN(AuthorizationItemSet,inHints) , 521 COPY_IN(AuthorizationItemSet,inContext) ) 522{ 523 secdebug("agentclient", "got setResult at port %u; result %u", instanceReplyPort, (unsigned int)result); 524 525 // relink internal references according to current place in memory 526 try { SecurityAgent::relocate(inHints, inHintsBase, inHintsLength); } 527 catch (MacOSError &e) { return e.osStatus(); } 528 catch (...) { return errAuthorizationInternal; } 529 530 try { SecurityAgent::relocate(inContext, inContextBase, inContextLength); } 531 catch (MacOSError &e) { return e.osStatus(); } 532 catch (...) { return errAuthorizationInternal; } 533 534 SecurityAgent::Clients::gClients().find(instanceReplyPort).setResult(result, inHints, inContext); 535 536 return KERN_SUCCESS; 537} 538 539kern_return_t sa_reply_server_requestInterrupt(mach_port_t instanceReplyPort) 540{ 541 secdebug("agentclient", "got requestInterrupt at port %u", instanceReplyPort); 542 SecurityAgent::Clients::gClients().find(instanceReplyPort).requestInterrupt(); 543 return KERN_SUCCESS; 544} 545 546kern_return_t sa_reply_server_didDeactivate(mach_port_t instanceReplyPort) 547{ 548 secdebug("agentclient", "got didDeactivate at port %u", instanceReplyPort); 549 SecurityAgent::Clients::gClients().find(instanceReplyPort).didDeactivate(); 550 return KERN_SUCCESS; 551} 552 553kern_return_t sa_reply_server_reportError(mach_port_t instanceReplyPort, OSStatus status) 554{ 555 secdebug("agentclient", "got reportError at port %u; error is %u", instanceReplyPort, (unsigned int)status); 556 SecurityAgent::Clients::gClients().find(instanceReplyPort).setError(status); 557 return KERN_SUCCESS; 558} 559 560kern_return_t sa_reply_server_didStartTx(mach_port_t replyPort, kern_return_t retval) 561{ 562 // no instance ports here: this goes straight to server 563 secdebug("agentclient", "got didStartTx"); 564 return retval; 565} 566