X-Git-Url: https://git.saurik.com/apple/securityd.git/blobdiff_plain/24a291535d44686bc7959c7e398fab040e3e08a7..4cd1cad0dea00daa03e1b54fdf2797a02373ad5b:/src/session.cpp diff --git a/src/session.cpp b/src/session.cpp index 7cabc91..b79f683 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * Copyright (c) 2000-2004,2008-2009 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -25,33 +25,58 @@ // // session - authentication session domains // -// A Session is defined by a mach_init bootstrap dictionary. These dictionaries are -// hierarchical and inherited, so they work well for characterization of processes -// that "belong" together. (Of course, if your mach_init is broken, you're in bad shape.) +// Security sessions are now by definition congruent to audit subsystem sessions. +// We represent these sessions within securityd as subclasses of class Session, +// but we reach for the kernel's data whenever we're not sure if our data is +// up to date. // -// Sessions are multi-threaded objects. +// Modifications to session state are made from client space using system calls. +// We discover them when we see changes in audit records as they come in with +// new requests. We cannot use system notifications for such changes because +// securityd is fully symmetrically multi-threaded, and thus may process new +// requests from clients before it gets those notifications. // +#include +#include // SIGTERM +#include // kAuthorizationFlagLeastPrivileged #include "session.h" #include "connection.h" #include "database.h" #include "server.h" +#include +#include + +using namespace CommonCriteria; // // The static session map // -PortMap Session::mSessions; +Session::SessionMap Session::mSessions; +Mutex Session::mSessionLock(Mutex::recursive); + + +const char Session::kUsername[] = "username"; +const char Session::kRealname[] = "realname"; // // Create a Session object from initial parameters (create) // -Session::Session(Bootstrap bootstrap, Port servicePort, SessionAttributeBits attrs) - : mBootstrap(bootstrap), mServicePort(servicePort), - mAttributes(attrs), mSecurityAgent(NULL), mAuthHost(NULL) +Session::Session(const AuditInfo &audit, Server &server) + : mAudit(audit), mSecurityAgent(NULL), mAuthHost(NULL), mKeybagState(0) { - secdebug("SSsession", "%p CREATED: handle=0x%lx bootstrap=%d service=%d attrs=0x%lx", - this, handle(), mBootstrap.port(), mServicePort.port(), mAttributes); + // link to Server as the global nexus in the object mesh + parent(server); + + // self-register + StLock _(mSessionLock); + assert(!mSessions[audit.sessionId()]); + mSessions[audit.sessionId()] = this; + + // log it + SECURITYD_SESSION_CREATE(this, this->sessionId(), &mAudit, sizeof(mAudit)); + Syslog::notice("Session %d created", this->sessionId()); } @@ -60,59 +85,85 @@ Session::Session(Bootstrap bootstrap, Port servicePort, SessionAttributeBits att // Session::~Session() { - secdebug("SSsession", "%p DESTROYED: handle=0x%lx bootstrap=%d", - this, handle(), mBootstrap.port()); + SECURITYD_SESSION_DESTROY(this, this->sessionId()); + Syslog::notice("Session %d destroyed", this->sessionId()); } -// -// Locate a session object by service port or (Session API) identifier -// -Session &Session::find(Port servicePort) +Server &Session::server() const { - StLock _(mSessions); - PortMap::const_iterator it = mSessions.find(servicePort); - assert(it != mSessions.end()); - return *it->second; + return parent(); } -Session &Session::find(SecuritySessionId id) + +// +// Locate a session object by session identifier +// +Session &Session::find(pid_t id, bool create) { - switch (id) { - case callerSecuritySession: - return Server::session(); - default: - return HandleObject::find(id, CSSMERR_CSSM_INVALID_ADDIN_HANDLE); - } + if (id == callerSecuritySession) + return Server::session(); + StLock _(mSessionLock); + SessionMap::iterator it = mSessions.find(id); + if (it != mSessions.end()) + return *it->second; + + // new session + if (!create) + CssmError::throwMe(errSessionInvalidId); + AuditInfo info; + info.get(id); + assert(info.sessionId() == id); + RefPointer session = new DynamicSession(info); + mSessions.insert(make_pair(id, session)); + return *session; } // -// Act on a death notification for a session's (sub)bootstrap port. +// Act on a death notification for a session's underlying audit session object. // We may not destroy the Session outright here (due to processes that use it), // but we do clear out its accumulated wealth. +// Note that we may get spurious death notifications for audit sessions that we +// never learned about. Ignore those. // -void Session::destroy(Port servPort) +void Session::destroy(SessionId id) { // remove session from session map - StLock _(mSessions); - PortMap::iterator it = mSessions.find(servPort); - assert(it != mSessions.end()); - RefPointer session = it->second; - mSessions.erase(it); - session->kill(); + bool unlocked = false; + RefPointer session = NULL; + { + StLock _(mSessionLock); + SessionMap::iterator it = mSessions.find(id); + if (it != mSessions.end()) { + session = it->second; + assert(session->sessionId() == id); + mSessions.erase(it); + + for (SessionMap::iterator kb_it = mSessions.begin(); kb_it != mSessions.end(); kb_it++) { + RefPointer kb_session = kb_it->second; + if (kb_session->originatorUid() == session->originatorUid()) { + if (kb_session->keybagGetState(session_keybag_unlocked)) unlocked = true; + } + } + } + } + + if (session.get()) { + if (!unlocked) { + service_context_t context = session->get_current_service_context(); + service_client_kb_lock(&context); + } + session->kill(); + } } + void Session::kill() { - StLock _(*this); - - // release authorization host objects - { - StLock _(mAuthHostLock); - mSecurityAgent = NULL; - mAuthHost = NULL; - } + StLock _(*this); // do we need to take this so early? + SECURITYD_SESSION_KILL(this, this->sessionId()); + invalidateSessionAuthHosts(); // invalidate shared credentials { @@ -131,141 +182,142 @@ void Session::kill() // -// On system sleep, call sleepProcessing on all DbCommons of all Sessions +// Refetch audit session data for the current audit session (to catch outside updates +// to the audit record). This is the price we're paying for not requiring an IPC to +// securityd when audit session data changes (this is desirable for delayering the +// software layer cake). +// If we ever disallow changes to (parts of the) audit session record in the kernel, +// we can loosen up on this continual re-fetching. // -void Session::processSystemSleep() +void Session::updateAudit() const { - StLock _(mSessions); - for (PortMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++) - it->second->allReferences(&DbCommon::sleepProcessing); + CommonCriteria::AuditInfo info; + try { + info.get(mAudit.sessionId()); + } catch (...) { + return; + } + mAudit = info; } - -// -// On "lockAll", call sleepProcessing on all DbCommons of this session (only) -// -void Session::processLockAll() +void Session::verifyKeyStorePassphrase(int32_t retries) { - allReferences(&DbCommon::lockProcessing); + QueryKeybagPassphrase keybagQuery(*this, retries); + keybagQuery.inferHints(Server::process()); + if (keybagQuery.query() != SecurityAgent::noReason) { + CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); + } } - -// -// The root session inherits the startup bootstrap and service port -// -RootSession::RootSession(Server &server, SessionAttributeBits attrs) - : Session(Bootstrap(), server.primaryServicePort(), - sessionIsRoot | sessionWasInitialized | attrs) +void Session::changeKeyStorePassphrase() { - parent(server); // the Server is our parent - ref(); // eternalize - - // self-install (no thread safety issues here) - mSessions[mServicePort] = this; + service_context_t context = get_current_service_context(); + QueryKeybagNewPassphrase keybagQuery(*this); + keybagQuery.inferHints(Server::process()); + CssmAutoData pass(Allocator::standard(Allocator::sensitive)); + CssmAutoData oldPass(Allocator::standard(Allocator::sensitive)); + SecurityAgent::Reason queryReason = keybagQuery.query(oldPass, pass); + if (queryReason == SecurityAgent::noReason) { + service_client_kb_change_secret(&context, oldPass.data(), (int)oldPass.length(), pass.data(), (int)pass.length()); + } else { + CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); + } } -uid_t RootSession::originatorUid() const +void Session::resetKeyStorePassphrase(const CssmData &passphrase) { - return 0; // it's root, obviously + service_context_t context = get_current_service_context(); + service_client_kb_reset(&context, passphrase.data(), (int)passphrase.length()); } -CFDataRef RootSession::copyUserPrefs() +service_context_t Session::get_current_service_context() { - return NULL; + // if this gets called from a timer there is no connection() object. + // need to check for valid connection object and pass the audit token along + service_context_t context = { sessionId(), originatorUid(), {} }; //*Server::connection().auditToken() + return context; } -// -// Dynamic sessions use the given bootstrap and re-register in it -// -DynamicSession::DynamicSession(TaskPort taskPort) - : ReceivePort(Server::active().bootstrapName(), taskPort.bootstrap()), - Session(taskPort.bootstrap(), *this), - mOriginatorTask(taskPort), mHaveOriginatorUid(false) +void Session::keybagClearState(int state) { - // link to Server as the global nexus in the object mesh - parent(Server::active()); - - // tell the server to listen to our port - Server::active().add(*this); - - // register for port notifications - Server::active().notifyIfDead(bootstrapPort()); //@@@??? still needed? - Server::active().notifyIfUnused(*this); + mKeybagState &= ~state; +} - // self-register - StLock _(mSessions); - assert(!mSessions[*this]); // can't be registered already (we just made it) - mSessions[*this] = this; - - secdebug("SSsession", "%p dynamic session originator=%d (pid=%d)", - this, mOriginatorTask.port(), taskPort.pid()); +void Session::keybagSetState(int state) +{ + mKeybagState |= state; } -DynamicSession::~DynamicSession() +bool Session::keybagGetState(int state) { - // remove our service port from the server - Server::active().remove(*this); + return mKeybagState & state; } -void DynamicSession::kill() +// +// Manage authorization client processes +// +void Session::invalidateSessionAuthHosts() { - StLock _(*this); - mBootstrap.destroy(); // release our bootstrap port - Session::kill(); // continue with parent kill + StLock _(mAuthHostLock); + + // if you got here, we don't care about pending operations: the auth hosts die + Syslog::warning("Killing auth hosts"); + if (mSecurityAgent) mSecurityAgent->UnixPlusPlus::Child::kill(SIGTERM); + if (mAuthHost) mAuthHost->UnixPlusPlus::Child::kill(SIGTERM); + mSecurityAgent = NULL; + mAuthHost = NULL; } +void Session::invalidateAuthHosts() +{ + StLock _(mSessionLock); + for (SessionMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++) + it->second->invalidateSessionAuthHosts(); +} // -// Set up a DynamicSession. -// This call must be made from a process within the session, and it must be the first -// such process to make the call. +// On system sleep, call sleepProcessing on all DbCommons of all Sessions // -void DynamicSession::setupAttributes(SessionCreationFlags flags, SessionAttributeBits attrs) +void Session::processSystemSleep() { - StLock _(*this); - secdebug("SSsession", "%p setup flags=0x%lx attrs=0x%lx", this, flags, attrs); - if (attrs & ~settableAttributes) - MacOSError::throwMe(errSessionInvalidAttributes); - checkOriginator(); - if (attribute(sessionWasInitialized)) - MacOSError::throwMe(errSessionAuthorizationDenied); - setAttributes(attrs | sessionWasInitialized); + SecurityAgentXPCQuery::killAllXPCClients(); + + StLock _(mSessionLock); + for (SessionMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++) + it->second->allReferences(&DbCommon::sleepProcessing); } // -// Check whether the calling process is the session originator. -// If it's not, throw. +// On "lockAll", call sleepProcessing on all DbCommons of this session (only) // -void DynamicSession::checkOriginator() +void Session::processLockAll() { - if (mOriginatorTask != Server::process().taskPort()) - MacOSError::throwMe(errSessionAuthorizationDenied); + allReferences(&DbCommon::lockProcessing); } // -// The "originator uid" is a uid value that can be provided by the session originator -// and retrieved by anyone. Securityd places no semantic meaning on this value. +// The root session corresponds to the audit session that security is running in. +// This is usually the initial system session; but in debug scenarios it may be +// an "ordinary" graphic login session. In such a debug case, we may add attribute +// flags to the session to make our (debugging) life easier. // -uid_t DynamicSession::originatorUid() const +RootSession::RootSession(uint64_t attributes, Server &server) + : Session(AuditInfo::current(), server) { - if (mHaveOriginatorUid) - return mOriginatorUid; - else - MacOSError::throwMe(errSessionValueNotSet); + ref(); // eternalize + mAudit.ai_flags |= attributes; // merge imposed attributes } -void DynamicSession::originatorUid(uid_t uid) +// +// Dynamic sessions use the audit session context of the first-contact client caller. +// +DynamicSession::DynamicSession(const AuditInfo &audit) + : Session(audit, Server::active()) { - checkOriginator(); - if (mHaveOriginatorUid) // must not re-set this - MacOSError::throwMe(errSessionAuthorizationDenied); - mHaveOriginatorUid = true; - mOriginatorUid = uid; - secdebug("SSsession", "%p session uid set to %d", this, uid); } @@ -282,8 +334,10 @@ OSStatus Session::authCreate(const AuthItemSet &rights, CredentialSet resultCreds; // this will acquire the object lock, so we delay acquiring it (@@@ no longer needed) - auto_ptr auth(new AuthorizationToken(*this, resultCreds, auditToken)); + auto_ptr auth(new AuthorizationToken(*this, resultCreds, auditToken, (flags&kAuthorizationFlagLeastPrivileged))); + SECURITYD_AUTH_CREATE(this, auth.get()); + // Make a copy of the mSessionCreds CredentialSet sessionCreds; { @@ -335,9 +389,17 @@ OSStatus Session::authGetRights(const AuthorizationBlob &authBlob, const AuthItemSet &rights, const AuthItemSet &environment, AuthorizationFlags flags, AuthItemSet &grantedRights) +{ + AuthorizationToken &auth = authorization(authBlob); + return auth.session().authGetRights(auth, rights, environment, flags, grantedRights); +} + +OSStatus Session::authGetRights(AuthorizationToken &auth, + const AuthItemSet &rights, const AuthItemSet &environment, + AuthorizationFlags flags, + AuthItemSet &grantedRights) { CredentialSet resultCreds; - AuthorizationToken &auth = authorization(authBlob); CredentialSet effective; { StLock _(mCredsLock); @@ -355,7 +417,7 @@ OSStatus Session::authGetRights(const AuthorizationBlob &authBlob, } secdebug("SSauth", "Authorization %p copyRights asked for %d got %d", - &authorization(authBlob), int(rights.size()), int(grantedRights.size())); + &auth, int(rights.size()), int(grantedRights.size())); return result; } @@ -379,7 +441,7 @@ OSStatus Session::authExternalize(const AuthorizationBlob &authBlob, AuthorizationExternalBlob &extBlob = reinterpret_cast(extForm); extBlob.blob = auth.handle(); - extBlob.session = bootstrapPort(); + extBlob.session = this->sessionId(); secdebug("SSauth", "Authorization %p externalized", &auth); return noErr; } else @@ -408,6 +470,18 @@ OSStatus Session::authInternalize(const AuthorizationExternalForm &extForm, } +// +// Accessor method for setting audit session flags. +// +void Session::setAttributes(SessionAttributeBits bits) +{ + StLock _(*this); + updateAudit(); +// assert((bits & ~settableAttributes) == 0); + mAudit.ai_flags = bits; + mAudit.set(); +} + // // The default session setup operation always fails. // Subclasses can override this to support session setup calls. @@ -417,6 +491,14 @@ void Session::setupAttributes(SessionCreationFlags flags, SessionAttributeBits a MacOSError::throwMe(errSessionAuthorizationDenied); } +uid_t Session::originatorUid() +{ + if (mAudit.uid() == AU_DEFAUDITID) { + StLock _(*this); + updateAudit(); + } + return mAudit.uid(); +} // // Authorization database I/O @@ -447,8 +529,8 @@ OSStatus Session::authorizationdbSet(const AuthorizationBlob &authBlob, Authoriz auth.mergeCredentials(resultCreds); } - secdebug("SSauth", "Authorization %p authorizationdbSet %s (result=%ld)", - &authorization(authBlob), inRightName, result); + secdebug("SSauth", "Authorization %p authorizationdbSet %s (result=%d)", + &authorization(authBlob), inRightName, int32_t(result)); return result; } @@ -472,8 +554,8 @@ OSStatus Session::authorizationdbRemove(const AuthorizationBlob &authBlob, Autho auth.mergeCredentials(resultCreds); } - secdebug("SSauth", "Authorization %p authorizationdbRemove %s (result=%ld)", - &authorization(authBlob), inRightName, result); + secdebug("SSauth", "Authorization %p authorizationdbRemove %s (result=%d)", + &authorization(authBlob), inRightName, int32_t(result)); return result; } @@ -487,7 +569,7 @@ void Session::mergeCredentials(CredentialSet &creds) secdebug("SSsession", "%p merge creds @%p", this, &creds); CredentialSet updatedCredentials = creds; for (CredentialSet::const_iterator it = creds.begin(); it != creds.end(); it++) - if (((*it)->isShared() && (*it)->isValid())) { + if ((*it)->isShared() && (*it)->isValid()) { CredentialSet::iterator old = mSessionCreds.find(*it); if (old == mSessionCreds.end()) { mSessionCreds.insert(*it); @@ -512,6 +594,42 @@ AuthorizationToken &Session::authorization(const AuthorizationBlob &blob) return auth; } +// +// Run the Authorization engine to check if a given right has been authorized, +// independent of an external client request. +// +OSStatus Session::authCheckRight(string &rightName, Connection &connection, bool allowUI) +{ + // dummy up the arguments for authCreate() + AuthorizationItem rightItem = { rightName.c_str(), 0, NULL, 0 }; + AuthorizationItemSet rightItemSet = { 1, &rightItem }; + AuthItemSet rightAuthItemSet(&rightItemSet); + AuthItemSet envAuthItemSet(kAuthorizationEmptyEnvironment); + AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagExtendRights; + if (true == allowUI) + flags |= kAuthorizationFlagInteractionAllowed; + AuthorizationBlob dummyHandle; + const audit_token_t *at = connection.auditToken(); + + return authCreate(rightAuthItemSet, envAuthItemSet, flags, dummyHandle, *at); +} + +// for places within securityd that don't want to #include +// or to fuss about exceptions +bool Session::isRightAuthorized(string &rightName, Connection &connection, bool allowUI) +{ + bool isAuthorized = false; + + try { + OSStatus status = authCheckRight(rightName, connection, allowUI); + if (errAuthorizationSuccess == status) + isAuthorized = true; + } + catch (...) { + } + return isAuthorized; +} + RefPointer Session::authhost(const AuthHostType hostType, const bool restart) { @@ -541,7 +659,8 @@ Session::authhost(const AuthHostType hostType, const bool restart) void DynamicSession::setUserPrefs(CFDataRef userPrefsDict) { - checkOriginator(); + if (Server::process().uid() != 0) + MacOSError::throwMe(errSessionAuthorizationDenied); StLock _(*this); mSessionAgentPrefs = userPrefsDict; } @@ -563,8 +682,8 @@ CFDataRef DynamicSession::copyUserPrefs() void Session::dumpNode() { PerSession::dumpNode(); - Debug::dump(" boot=%d service=%d attrs=0x%lx authhost=%p securityagent=%p", - mBootstrap.port(), mServicePort.port(), mAttributes, mAuthHost, mSecurityAgent); + Debug::dump(" auid=%d attrs=%#x authhost=%p securityagent=%p", + this->sessionId(), uint32_t(this->attributes()), mAuthHost, mSecurityAgent); } #endif //DEBUGDUMP