X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/securityd/src/session.cpp diff --git a/securityd/src/session.cpp b/securityd/src/session.cpp new file mode 100644 index 00000000..37472cad --- /dev/null +++ b/securityd/src/session.cpp @@ -0,0 +1,689 @@ +/* + * Copyright (c) 2000-2009,2011-2013 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// session - authentication session domains +// +// 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. +// +// 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 +// +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(const AuditInfo &audit, Server &server) + : mAudit(audit), mSecurityAgent(NULL), mAuthHost(NULL), mKeybagState(0) +{ + // 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()); +} + + +// +// Destroy a Session +// +Session::~Session() +{ + SECURITYD_SESSION_DESTROY(this, this->sessionId()); + Syslog::notice("Session %d destroyed", this->sessionId()); +} + + +Server &Session::server() const +{ + return parent(); +} + + +// +// Locate a session object by session identifier +// +Session &Session::find(pid_t id, bool create) +{ + 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 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(SessionId id) +{ + // remove session from session map + 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); // do we need to take this so early? + SECURITYD_SESSION_KILL(this, this->sessionId()); + invalidateSessionAuthHosts(); + + // invalidate shared credentials + { + StLock _(mCredsLock); + + IFDEBUG(if (!mSessionCreds.empty()) + secdebug("SSauth", "session %p clearing %d shared credentials", + this, int(mSessionCreds.size()))); + for (CredentialSet::iterator it = mSessionCreds.begin(); it != mSessionCreds.end(); it++) + (*it)->invalidate(); + } + + // base kill processing + PerSession::kill(); +} + + +// +// 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::updateAudit() const +{ + CommonCriteria::AuditInfo info; + try { + info.get(mAudit.sessionId()); + } catch (...) { + return; + } + mAudit = info; +} + +void Session::verifyKeyStorePassphrase(int32_t retries) +{ + QueryKeybagPassphrase keybagQuery(*this, retries); + keybagQuery.inferHints(Server::process()); + if (keybagQuery.query() != SecurityAgent::noReason) { + CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); + } +} + +void Session::changeKeyStorePassphrase() +{ + 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); + } +} + +void Session::resetKeyStorePassphrase(const CssmData &passphrase) +{ + service_context_t context = get_current_service_context(); + service_client_kb_reset(&context, passphrase.data(), (int)passphrase.length()); +} + +service_context_t Session::get_current_service_context() +{ + // 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; +} + +void Session::keybagClearState(int state) +{ + mKeybagState &= ~state; +} + +void Session::keybagSetState(int state) +{ + mKeybagState |= state; +} + +bool Session::keybagGetState(int state) +{ + return mKeybagState & state; +} + + +// +// Manage authorization client processes +// +void Session::invalidateSessionAuthHosts() +{ + 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(); +} + +// +// On system sleep, call sleepProcessing on all DbCommons of all Sessions +// +void Session::processSystemSleep() +{ + SecurityAgentXPCQuery::killAllXPCClients(); + + StLock _(mSessionLock); + for (SessionMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++) + it->second->allReferences(&DbCommon::sleepProcessing); +} + + +// +// On "lockAll", call sleepProcessing on all DbCommons of this session (only) +// +void Session::processLockAll() +{ + allReferences(&DbCommon::lockProcessing); +} + + +// +// 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. +// +RootSession::RootSession(uint64_t attributes, Server &server) + : Session(AuditInfo::current(), server) +{ + ref(); // eternalize + mAudit.ai_flags |= attributes; // merge imposed attributes +} + + +// +// Dynamic sessions use the audit session context of the first-contact client caller. +// +DynamicSession::DynamicSession(const AuditInfo &audit) + : Session(audit, Server::active()) +{ +} + + +// +// Authorization operations +// +OSStatus Session::authCreate(const AuthItemSet &rights, + const AuthItemSet &environment, + AuthorizationFlags flags, + AuthorizationBlob &newHandle, + const audit_token_t &auditToken) +{ + // invoke the authorization computation engine + CredentialSet resultCreds; + + // this will acquire the object lock, so we delay acquiring it (@@@ no longer needed) + auto_ptr auth(new AuthorizationToken(*this, resultCreds, auditToken, (flags&kAuthorizationFlagLeastPrivileged))); + + SECURITYD_AUTH_CREATE(this, auth.get()); + + // Make a copy of the mSessionCreds + CredentialSet sessionCreds; + { + StLock _(mCredsLock); + sessionCreds = mSessionCreds; + } + + AuthItemSet outRights; + OSStatus result = Server::authority().authorize(rights, environment, flags, + &sessionCreds, &resultCreds, outRights, *auth); + newHandle = auth->handle(); + + // merge resulting creds into shared pool + if ((flags & kAuthorizationFlagExtendRights) && + !(flags & kAuthorizationFlagDestroyRights)) + { + StLock _(mCredsLock); + mergeCredentials(resultCreds); + auth->mergeCredentials(resultCreds); + } + + // Make sure that this isn't done until the auth(AuthorizationToken) is guaranteed to + // not be destroyed anymore since it's destructor asserts it has no processes + Server::process().addAuthorization(auth.get()); + auth.release(); + return result; +} + +void Session::authFree(const AuthorizationBlob &authBlob, AuthorizationFlags flags) +{ + AuthorizationToken::Deleter deleter(authBlob); + AuthorizationToken &auth = deleter; + Process &process = Server::process(); + process.checkAuthorization(&auth); + + if (flags & kAuthorizationFlagDestroyRights) { + // explicitly invalidate all shared credentials and remove them from the session + for (CredentialSet::const_iterator it = auth.begin(); it != auth.end(); it++) + if ((*it)->isShared()) + (*it)->invalidate(); + } + + // now get rid of the authorization itself + if (process.removeAuthorization(&auth)) + deleter.remove(); +} + +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; + CredentialSet effective; + { + StLock _(mCredsLock); + effective = auth.effectiveCreds(); + } + OSStatus result = Server::authority().authorize(rights, environment, flags, + &effective, &resultCreds, grantedRights, auth); + + // merge resulting creds into shared pool + if ((flags & kAuthorizationFlagExtendRights) && !(flags & kAuthorizationFlagDestroyRights)) + { + StLock _(mCredsLock); + mergeCredentials(resultCreds); + auth.mergeCredentials(resultCreds); + } + + secdebug("SSauth", "Authorization %p copyRights asked for %d got %d", + &auth, int(rights.size()), int(grantedRights.size())); + return result; +} + +OSStatus Session::authGetInfo(const AuthorizationBlob &authBlob, + const char *tag, + AuthItemSet &contextInfo) +{ + AuthorizationToken &auth = authorization(authBlob); + secdebug("SSauth", "Authorization %p get-info", &auth); + contextInfo = auth.infoSet(tag); + return noErr; +} + +OSStatus Session::authExternalize(const AuthorizationBlob &authBlob, + AuthorizationExternalForm &extForm) +{ + const AuthorizationToken &auth = authorization(authBlob); + StLock _(*this); + if (auth.mayExternalize(Server::process())) { + memset(&extForm, 0, sizeof(extForm)); + AuthorizationExternalBlob &extBlob = + reinterpret_cast(extForm); + extBlob.blob = auth.handle(); + extBlob.session = this->sessionId(); + secdebug("SSauth", "Authorization %p externalized", &auth); + return noErr; + } else + return errAuthorizationExternalizeNotAllowed; +} + +OSStatus Session::authInternalize(const AuthorizationExternalForm &extForm, + AuthorizationBlob &authBlob) +{ + // interpret the external form + const AuthorizationExternalBlob &extBlob = + reinterpret_cast(extForm); + + // locate source authorization + AuthorizationToken &sourceAuth = AuthorizationToken::find(extBlob.blob); + + // check for permission and do it + if (sourceAuth.mayInternalize(Server::process(), true)) { + StLock _(*this); + authBlob = extBlob.blob; + Server::process().addAuthorization(&sourceAuth); + secdebug("SSauth", "Authorization %p internalized", &sourceAuth); + return noErr; + } else + return errAuthorizationInternalizeNotAllowed; +} + + +// +// 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. +// +void Session::setupAttributes(SessionCreationFlags flags, SessionAttributeBits attrs) +{ + MacOSError::throwMe(errSessionAuthorizationDenied); +} + +uid_t Session::originatorUid() +{ + if (mAudit.uid() == AU_DEFAUDITID) { + StLock _(*this); + updateAudit(); + } + return mAudit.uid(); +} + +// +// Authorization database I/O +// +OSStatus Session::authorizationdbGet(AuthorizationString inRightName, CFDictionaryRef *rightDict) +{ + string rightName(inRightName); + return Server::authority().getRule(rightName, rightDict); +} + + +OSStatus Session::authorizationdbSet(const AuthorizationBlob &authBlob, AuthorizationString inRightName, CFDictionaryRef rightDict) +{ + CredentialSet resultCreds; + AuthorizationToken &auth = authorization(authBlob); + CredentialSet effective; + + { + StLock _(mCredsLock); + effective = auth.effectiveCreds(); + } + + OSStatus result = Server::authority().setRule(inRightName, rightDict, &effective, &resultCreds, auth); + + { + StLock _(mCredsLock); + mergeCredentials(resultCreds); + auth.mergeCredentials(resultCreds); + } + + secdebug("SSauth", "Authorization %p authorizationdbSet %s (result=%d)", + &authorization(authBlob), inRightName, int32_t(result)); + return result; +} + + +OSStatus Session::authorizationdbRemove(const AuthorizationBlob &authBlob, AuthorizationString inRightName) +{ + CredentialSet resultCreds; + AuthorizationToken &auth = authorization(authBlob); + CredentialSet effective; + + { + StLock _(mCredsLock); + effective = auth.effectiveCreds(); + } + + OSStatus result = Server::authority().removeRule(inRightName, &effective, &resultCreds, auth); + + { + StLock _(mCredsLock); + mergeCredentials(resultCreds); + auth.mergeCredentials(resultCreds); + } + + secdebug("SSauth", "Authorization %p authorizationdbRemove %s (result=%d)", + &authorization(authBlob), inRightName, int32_t(result)); + return result; +} + + +// +// Merge a set of credentials into the shared-session credential pool +// +// must hold mCredsLock +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()) { + CredentialSet::iterator old = mSessionCreds.find(*it); + if (old == mSessionCreds.end()) { + mSessionCreds.insert(*it); + } else { + // replace "new" with "old" in input set to retain synchronization + (*old)->merge(**it); + updatedCredentials.erase(*it); + updatedCredentials.insert(*old); + } + } + creds.swap(updatedCredentials); +} + + +// +// Locate an AuthorizationToken given a blob +// +AuthorizationToken &Session::authorization(const AuthorizationBlob &blob) +{ + AuthorizationToken &auth = AuthorizationToken::find(blob); + Server::process().checkAuthorization(&auth); + 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) +{ + StLock _(mAuthHostLock); + + if (hostType == privilegedAuthHost) + { + if (restart || !mAuthHost || (mAuthHost->state() != Security::UnixPlusPlus::Child::alive)) + { + if (mAuthHost) + PerSession::kill(*mAuthHost); + mAuthHost = new AuthHostInstance(*this, hostType); + } + return mAuthHost; + } + else /* if (hostType == securityAgent) */ + { + if (restart || !mSecurityAgent || (mSecurityAgent->state() != Security::UnixPlusPlus::Child::alive)) + { + if (mSecurityAgent) + PerSession::kill(*mSecurityAgent); + mSecurityAgent = new AuthHostInstance(*this, hostType); + } + return mSecurityAgent; + } +} + +void DynamicSession::setUserPrefs(CFDataRef userPrefsDict) +{ + if (Server::process().uid() != 0) + MacOSError::throwMe(errSessionAuthorizationDenied); + StLock _(*this); + mSessionAgentPrefs = userPrefsDict; +} + +CFDataRef DynamicSession::copyUserPrefs() +{ + StLock _(*this); + if (mSessionAgentPrefs) + CFRetain(mSessionAgentPrefs); + return mSessionAgentPrefs; +} + + +// +// Debug dumping +// +#if defined(DEBUGDUMP) + +void Session::dumpNode() +{ + PerSession::dumpNode(); + Debug::dump(" auid=%d attrs=%#x authhost=%p securityagent=%p", + this->sessionId(), uint32_t(this->attributes()), mAuthHost, mSecurityAgent); +} + +#endif //DEBUGDUMP