/*
- * 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@
*
- * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
- *
* 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
//
// 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 <pwd.h>
+#include <signal.h> // SIGTERM
+#include <Security/AuthorizationPriv.h> // kAuthorizationFlagLeastPrivileged
#include "session.h"
#include "connection.h"
#include "database.h"
#include "server.h"
+#include <security_utilities/logging.h>
+#include <agentquery.h>
+
+using namespace CommonCriteria;
//
// The static session map
//
-PortMap<Session> 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), mDying(false)
+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<Mutex> _(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()
{
- secdebug("SSsession", "%p CREATED: handle=0x%lx bootstrap=%d service=%d attrs=0x%lx",
- this, handle(), mBootstrap.port(), mServicePort.port(), mAttributes);
+ SECURITYD_SESSION_DESTROY(this, this->sessionId());
+ Syslog::notice("Session %d destroyed", this->sessionId());
}
-void Session::release()
+Server &Session::server() const
{
- // nothing by default
+ return parent<Server>();
}
//
-// The root session inherits the startup bootstrap and service port
+// Locate a session object by session identifier
//
-RootSession::RootSession(Port servicePort, SessionAttributeBits attrs)
- : Session(Bootstrap(), servicePort, sessionIsRoot | sessionWasInitialized | attrs)
+Session &Session::find(pid_t id, bool create)
{
- ref(); // eternalize
-
- // self-install (no thread safety issues here)
- mSessions[mServicePort] = this;
+ if (id == callerSecuritySession)
+ return Server::session();
+ StLock<Mutex> _(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> session = new DynamicSession(info);
+ mSessions.insert(make_pair(id, session));
+ return *session;
}
//
-// Dynamic sessions use the given bootstrap and re-register in it
+// 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.
//
-DynamicSession::DynamicSession(const Bootstrap &bootstrap)
- : ReceivePort(Server::active().bootstrapName(), bootstrap),
- Session(bootstrap, *this)
+void Session::destroy(SessionId id)
{
- // tell the server to listen to our port
- Server::active().add(*this);
+ // remove session from session map
+ bool unlocked = false;
+ RefPointer<Session> session = NULL;
+ {
+ StLock<Mutex> _(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<Session> 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<Mutex> _(*this); // do we need to take this so early?
+ SECURITYD_SESSION_KILL(this, this->sessionId());
+ invalidateSessionAuthHosts();
- // register for port notifications
- Server::active().notifyIfDead(bootstrapPort()); //@@@??? still needed?
- Server::active().notifyIfUnused(*this);
+ // invalidate shared credentials
+ {
+ StLock<Mutex> _(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();
+}
- // self-register
- StLock<Mutex> _(mSessions);
- assert(!mSessions[*this]); // can't be registered already (we just made it)
- mSessions[*this] = this;
+
+//
+// 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;
}
-DynamicSession::~DynamicSession()
+void Session::verifyKeyStorePassphrase(int32_t retries)
{
- // remove our service port from the server
- Server::active().remove(*this);
+ 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 DynamicSession::release()
+void Session::resetKeyStorePassphrase(const CssmData &passphrase)
{
- mBootstrap.destroy();
+ 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;
+}
-//
-// Destroy a Session
-//
-Session::~Session()
+void Session::keybagClearState(int state)
{
- secdebug("SSsession", "%p DESTROYED: handle=0x%lx bootstrap=%d",
- this, handle(), mBootstrap.port());
+ mKeybagState &= ~state;
+}
+
+void Session::keybagSetState(int state)
+{
+ mKeybagState |= state;
+}
+
+bool Session::keybagGetState(int state)
+{
+ return mKeybagState & state;
}
//
-// Locate a session object by service port or (Session API) identifier
+// Manage authorization client processes
//
-Session &Session::find(Port servicePort)
+void Session::invalidateSessionAuthHosts()
{
- StLock<Mutex> _(mSessions);
- PortMap<Session>::const_iterator it = mSessions.find(servicePort);
- assert(it != mSessions.end());
- return *it->second;
+ StLock<Mutex> _(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;
}
-Session &Session::find(SecuritySessionId id)
+void Session::invalidateAuthHosts()
{
- switch (id) {
- case callerSecuritySession:
- return Server::session();
- default:
- return HandleObject::find<Session>(id, CSSMERR_CSSM_INVALID_ADDIN_HANDLE);
- }
+ StLock<Mutex> _(mSessionLock);
+ for (SessionMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++)
+ it->second->invalidateSessionAuthHosts();
}
-
//
-// Act on a death notification for a session's (sub)bootstrap port.
-// We may not destroy the Session outright here (due to processes that use it),
-// but we do clear out its accumulated wealth.
+// On system sleep, call sleepProcessing on all DbCommons of all Sessions
//
-void Session::destroy(Port servPort)
+void Session::processSystemSleep()
{
- // remove session from session map
- StLock<Mutex> _(mSessions);
- PortMap<Session>::iterator it = mSessions.find(servPort);
- assert(it != mSessions.end());
- Session *session = it->second;
- session->kill();
- mSessions.erase(it);
+ SecurityAgentXPCQuery::killAllXPCClients();
+
+ StLock<Mutex> _(mSessionLock);
+ for (SessionMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++)
+ it->second->allReferences(&DbCommon::sleepProcessing);
}
-void Session::kill()
+
+//
+// On "lockAll", call sleepProcessing on all DbCommons of this session (only)
+//
+void Session::processLockAll()
{
- release();
+ allReferences(&DbCommon::lockProcessing);
+}
- StLock<Mutex> _(mLock);
-
- // this session is now officially dying
- mDying = true;
-
- // invalidate shared credentials
- {
- StLock<Mutex> _(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();
+
+//
+// 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
}
//
-// Relay lockAllDatabases to all known sessions
+// Dynamic sessions use the audit session context of the first-contact client caller.
//
-void Session::processSystemSleep()
+DynamicSession::DynamicSession(const AuditInfo &audit)
+ : Session(audit, Server::active())
{
- StLock<Mutex> _(mSessions);
- for (PortMap<Session>::const_iterator it = mSessions.begin(); it != mSessions.end(); it++)
- it->second->allReferences<DbCommon>(&DbCommon::sleepProcessing);
}
const AuthItemSet &environment,
AuthorizationFlags flags,
AuthorizationBlob &newHandle,
- const security_token_t &securityToken)
+ const audit_token_t &auditToken)
{
// invoke the authorization computation engine
CredentialSet resultCreds;
- // this will acquire mLock, so we delay acquiring it
- auto_ptr<AuthorizationToken> auth(new AuthorizationToken(*this, resultCreds, securityToken));
+ // this will acquire the object lock, so we delay acquiring it (@@@ no longer needed)
+ auto_ptr<AuthorizationToken> auth(new AuthorizationToken(*this, resultCreds, auditToken, (flags&kAuthorizationFlagLeastPrivileged)));
+ SECURITYD_AUTH_CREATE(this, auth.get());
+
// Make a copy of the mSessionCreds
CredentialSet sessionCreds;
{
StLock<Mutex> _(mCredsLock);
sessionCreds = mSessionCreds;
}
-
+
AuthItemSet outRights;
OSStatus result = Server::authority().authorize(rights, environment, flags,
&sessionCreds, &resultCreds, outRights, *auth);
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<Mutex> _(mCredsLock);
}
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;
}
AuthorizationExternalForm &extForm)
{
const AuthorizationToken &auth = authorization(authBlob);
- StLock<Mutex> _(mLock);
+ StLock<Mutex> _(*this);
if (auth.mayExternalize(Server::process())) {
memset(&extForm, 0, sizeof(extForm));
AuthorizationExternalBlob &extBlob =
reinterpret_cast<AuthorizationExternalBlob &>(extForm);
extBlob.blob = auth.handle();
- extBlob.session = bootstrapPort();
+ extBlob.session = this->sessionId();
secdebug("SSauth", "Authorization %p externalized", &auth);
return noErr;
} else
// check for permission and do it
if (sourceAuth.mayInternalize(Server::process(), true)) {
- StLock<Mutex> _(mLock);
+ StLock<Mutex> _(*this);
authBlob = extBlob.blob;
Server::process().addAuthorization(&sourceAuth);
secdebug("SSauth", "Authorization %p internalized", &sourceAuth);
}
+//
+// Accessor method for setting audit session flags.
+//
+void Session::setAttributes(SessionAttributeBits bits)
+{
+ StLock<Mutex> _(*this);
+ updateAudit();
+// assert((bits & ~settableAttributes) == 0);
+ mAudit.ai_flags = bits;
+ mAudit.set();
+}
+
//
-// Set up a (new-ish) Session.
-// This call must be made from a process within the session, and it must be the first
-// such process to make the call.
+// The default session setup operation always fails.
+// Subclasses can override this to support session setup calls.
//
-void Session::setup(SessionCreationFlags flags, SessionAttributeBits attrs)
+void Session::setupAttributes(SessionCreationFlags flags, SessionAttributeBits attrs)
{
- // check current process object - it may have been cached before the client's bootstrap switch
- Process *process = &Server::process();
- process->session().setupAttributes(attrs);
+ MacOSError::throwMe(errSessionAuthorizationDenied);
}
-
-void Session::setupAttributes(SessionAttributeBits attrs)
+uid_t Session::originatorUid()
{
- secdebug("SSsession", "%p setup attrs=0x%lx", this, attrs);
- if (attrs & ~settableAttributes)
- MacOSError::throwMe(errSessionInvalidAttributes);
- if (attribute(sessionWasInitialized))
- MacOSError::throwMe(errSessionAuthorizationDenied);
- setAttributes(attrs | sessionWasInitialized);
+ if (mAudit.uid() == AU_DEFAUDITID) {
+ StLock<Mutex> _(*this);
+ updateAudit();
+ }
+ return mAudit.uid();
}
-
+//
+// Authorization database I/O
+//
OSStatus Session::authorizationdbGet(AuthorizationString inRightName, CFDictionaryRef *rightDict)
{
string rightName(inRightName);
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;
}
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;
}
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);
} else {
// replace "new" with "old" in input set to retain synchronization
(*old)->merge(**it);
- creds.erase(it);
- creds.insert(*old);
+ updatedCredentials.erase(*it);
+ updatedCredentials.insert(*old);
}
}
+ creds.swap(updatedCredentials);
}
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
+// <libsecurity_authorization/Authorization.h> 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<AuthHostInstance>
+Session::authhost(const AuthHostType hostType, const bool restart)
+{
+ StLock<Mutex> _(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<Mutex> _(*this);
+ mSessionAgentPrefs = userPrefsDict;
+}
+
+CFDataRef DynamicSession::copyUserPrefs()
+{
+ StLock<Mutex> _(*this);
+ if (mSessionAgentPrefs)
+ CFRetain(mSessionAgentPrefs);
+ return mSessionAgentPrefs;
+}
+
//
// Debug dumping
void Session::dumpNode()
{
PerSession::dumpNode();
- if (mDying)
- Debug::dump(" DYING");
- Debug::dump(" boot=%d service=%d attrs=0x%lx",
- mBootstrap.port(), mServicePort.port(), mAttributes);
+ Debug::dump(" auid=%d attrs=%#x authhost=%p securityagent=%p",
+ this->sessionId(), uint32_t(this->attributes()), mAuthHost, mSecurityAgent);
}
#endif //DEBUGDUMP