X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/securityd/src/server.cpp diff --git a/securityd/src/server.cpp b/securityd/src/server.cpp new file mode 100644 index 00000000..c7f0e26c --- /dev/null +++ b/securityd/src/server.cpp @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2000-2010,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@ + */ + + +// +// server - securityd main server object +// +#include // MIG ucsp service +#include "self.h" // MIG self service +#include +#include +#include "server.h" +#include "session.h" +#include "acls.h" +#include "notifications.h" +#include "child.h" +#include +#include +#include "pcscmonitor.h" + +#include "agentquery.h" + + +using namespace MachPlusPlus; + +// +// Construct an Authority +// +Authority::Authority(const char *configFile) +: Authorization::Engine(configFile) +{ +} + +Authority::~Authority() +{ +} + + +// +// Construct the server object +// +Server::Server(Authority &authority, CodeSignatures &signatures, const char *bootstrapName) + : MachServer(bootstrapName), + mBootstrapName(bootstrapName), + mCSPModule(gGuidAppleCSP, mCssm), mCSP(mCSPModule), + mAuthority(authority), + mCodeSignatures(signatures), + mVerbosity(0), + mWaitForClients(true), mShuttingDown(false) +{ + // make me eternal (in the object mesh) + ref(); + + // engage the subsidiary port handler for sleep notifications + add(sleepWatcher); +} + + +// +// Clean up the server object +// +Server::~Server() +{ + //@@@ more later +} + + +// +// Locate a connection by reply port and make it the current connection +// of this thread. The connection will be marked busy, and can be accessed +// by calling Server::connection() [no argument] until it is released by +// calling Connection::endWork(). +// +Connection &Server::connection(mach_port_t port, audit_token_t &auditToken) +{ + Server &server = active(); + StLock _(server); + Connection *conn = server.mConnections.get(port, CSSM_ERRCODE_INVALID_CONTEXT_HANDLE); + conn->process().checkSession(auditToken); + active().mCurrentConnection() = conn; + conn->beginWork(auditToken); + return *conn; +} + +Connection &Server::connection(bool tolerant) +{ + Connection *conn = active().mCurrentConnection(); + assert(conn); // have to have one + if (!tolerant) + conn->checkWork(); + return *conn; +} + +void Server::requestComplete(CSSM_RETURN &rcode) +{ + // note: there may not be an active connection if connection setup failed + if (RefPointer &conn = active().mCurrentConnection()) { + conn->endWork(rcode); + conn = NULL; + } + IFDUMPING("state", NodeCore::dumpAll()); +} + + +// +// Shorthand for "current" process and session. +// This is the process and session for the current connection. +// +Process &Server::process() +{ + return connection().process(); +} + +Session &Server::session() +{ + return connection().process().session(); +} + +RefPointer Server::key(KeyHandle key) +{ + return U32HandleObject::findRef(key, CSSMERR_CSP_INVALID_KEY_REFERENCE); +} + +RefPointer Server::database(DbHandle db) +{ + return find(db, CSSMERR_DL_INVALID_DB_HANDLE); +} + +RefPointer Server::keychain(DbHandle db) +{ + return find(db, CSSMERR_DL_INVALID_DB_HANDLE); +} + +RefPointer Server::optionalDatabase(DbHandle db, bool persistent) +{ + if (persistent && db != noDb) + return database(db); + else + return &process().localStore(); +} + + +// +// Locate an ACL bearer (database or key) by handle +// The handle might be used across IPC, so we clamp it accordingly +// +AclSource &Server::aclBearer(AclKind kind, U32HandleObject::Handle handle) +{ + AclSource &bearer = U32HandleObject::find(handle, CSSMERR_CSSM_INVALID_ADDIN_HANDLE); + if (kind != bearer.acl().aclKind()) + CssmError::throwMe(CSSMERR_CSSM_INVALID_HANDLE_USAGE); + return bearer; +} + + +// +// Run the server. This will not return until the server is forced to exit. +// +void Server::run() +{ + MachServer::run(0x10000, + MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT)); +} + + +// +// Handle thread overflow. MachServer will call this if it has hit its thread +// limit and yet still needs another thread. +// +void Server::threadLimitReached(UInt32 limit) +{ + Syslog::notice("securityd has reached its thread limit (%ld) - service deadlock is possible", + limit); +} + + +// +// The primary server run-loop function. +// Invokes the MIG-generated main dispatch function (ucsp_server), as well +// as the self-send dispatch (self_server). +// For debug builds, look up request names in a MIG-generated table +// for better debug-log messages. +// +boolean_t ucsp_server(mach_msg_header_t *, mach_msg_header_t *); +boolean_t self_server(mach_msg_header_t *, mach_msg_header_t *); + + +boolean_t Server::handle(mach_msg_header_t *in, mach_msg_header_t *out) +{ + return ucsp_server(in, out) || self_server(in, out); +} + + +// +// Set up a new Connection. This establishes the environment (process et al) as needed +// and registers a properly initialized Connection object to run with. +// Type indicates how "deep" we need to initialize (new session, process, or connection). +// Everything at and below that level is constructed. This is straight-forward except +// in the case of session re-initialization (see below). +// +void Server::setupConnection(ConnectLevel type, Port replyPort, Port taskPort, + const audit_token_t &auditToken, const ClientSetupInfo *info) +{ + AuditToken audit(auditToken); + + // first, make or find the process based on task port + StLock _(*this); + RefPointer &proc = mProcesses[taskPort]; + if (proc && proc->session().sessionId() != audit.sessionId()) + proc->changeSession(audit.sessionId()); + if (proc && type == connectNewProcess) { + // the client has amnesia - reset it + assert(info); + proc->reset(taskPort, info, audit); + proc->changeSession(audit.sessionId()); + } + if (!proc) { + if (type == connectNewThread) // client error (or attack) + CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); + assert(info); + proc = new Process(taskPort, info, audit); + notifyIfDead(taskPort); + mPids[proc->pid()] = proc; + } + + // now, establish a connection and register it in the server + Connection *connection = new Connection(*proc, replyPort); + if (mConnections.contains(replyPort)) // malicious re-entry attempt? + CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ error code? (client error) + mConnections[replyPort] = connection; + notifyIfDead(replyPort); +} + + +// +// Synchronously end a Connection. +// This is due to a request from the client, so no thread races are possible. +// In practice, this is optional since the DPN for the client thread reply port +// will destroy the connection anyway when the thread dies. +// +void Server::endConnection(Port replyPort) +{ + StLock _(*this); + PortMap::iterator it = mConnections.find(replyPort); + assert(it != mConnections.end()); + it->second->terminate(); + mConnections.erase(it); +} + + +// +// Handling dead-port notifications. +// This receives DPNs for all kinds of ports we're interested in. +// +void Server::notifyDeadName(Port port) +{ + // We need the lock to get a proper iterator on mConnections or mProcesses, + // but must release it before we call abort or kill, as these might take + // unbounded time, including calls out to token daemons etc. + + StLock serverLock(*this); + secdebug("SSports", "port %d is dead", port.port()); + + // is it a connection? + PortMap::iterator conIt = mConnections.find(port); + if (conIt != mConnections.end()) { + SECURITYD_PORTS_DEAD_CONNECTION(port); + RefPointer con = conIt->second; + mConnections.erase(conIt); + serverLock.unlock(); + con->abort(); + return; + } + + // is it a process? + PortMap::iterator procIt = mProcesses.find(port); + if (procIt != mProcesses.end()) { + SECURITYD_PORTS_DEAD_PROCESS(port); + RefPointer proc = procIt->second; + mPids.erase(proc->pid()); + mProcesses.erase(procIt); + serverLock.unlock(); + // The kill may take some time; make sure there is a spare thread around + // to prevent deadlocks + StLock _(*this); + proc->kill(); + return; + } + + // well, what IS IT?! + SECURITYD_PORTS_DEAD_ORPHAN(port); + secdebug("server", "spurious dead port notification for port %d", port.port()); +} + + +// +// Handling no-senders notifications. +// This is currently only used for (subsidiary) service ports +// +void Server::notifyNoSenders(Port port, mach_port_mscount_t) +{ + SECURITYD_PORTS_DEAD_SESSION(port); +} + + +// +// Handling signals. +// These are sent as Mach messages from ourselves to escape the limitations of +// the signal handler environment. +// +kern_return_t self_server_handleSignal(mach_port_t sport, + mach_port_t taskPort, int sig) +{ + try { + SECURITYD_SIGNAL_HANDLED(sig); + if (taskPort != mach_task_self()) { + Syslog::error("handleSignal: received from someone other than myself"); + return KERN_SUCCESS; + } + switch (sig) { + case SIGCHLD: + ServerChild::checkChildren(); + break; + case SIGINT: + SECURITYD_SHUTDOWN_NOW(); + Syslog::notice("securityd terminated due to SIGINT"); + _exit(0); + case SIGTERM: + Server::active().beginShutdown(); + break; + case SIGPIPE: + fprintf(stderr, "securityd ignoring SIGPIPE received"); + break; + +#if defined(DEBUGDUMP) + case SIGUSR1: + NodeCore::dumpAll(); + break; +#endif //DEBUGDUMP + + case SIGUSR2: + { + extern PCSCMonitor *gPCSC; + gPCSC->startSoftTokens(); + break; + } + + default: + assert(false); + } + } catch(...) { + secdebug("SS", "exception handling a signal (ignored)"); + } + mach_port_deallocate(mach_task_self(), taskPort); + return KERN_SUCCESS; +} + + +kern_return_t self_server_handleSession(mach_port_t sport, + mach_port_t taskPort, uint32_t event, uint64_t ident) +{ + try { + if (taskPort != mach_task_self()) { + Syslog::error("handleSession: received from someone other than myself"); + return KERN_SUCCESS; + } + if (event == AUE_SESSION_CLOSE) + Session::destroy(ident); + } catch(...) { + secdebug("SS", "exception handling a signal (ignored)"); + } + mach_port_deallocate(mach_task_self(), taskPort); + return KERN_SUCCESS; +} + + +// +// Notifier for system sleep events +// +void Server::SleepWatcher::systemWillSleep() +{ + SECURITYD_POWER_SLEEP(); + Session::processSystemSleep(); + for (set::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++) + (*it)->systemWillSleep(); +} + +void Server::SleepWatcher::systemIsWaking() +{ + SECURITYD_POWER_WAKE(); + for (set::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++) + (*it)->systemIsWaking(); +} + +void Server::SleepWatcher::systemWillPowerOn() +{ + SECURITYD_POWER_ON(); + Server::active().longTermActivity(); + for (set::const_iterator it = mPowerClients.begin(); it != mPowerClients.end(); it++) + (*it)->systemWillPowerOn(); +} + +void Server::SleepWatcher::add(PowerWatcher *client) +{ + assert(mPowerClients.find(client) == mPowerClients.end()); + mPowerClients.insert(client); +} + +void Server::SleepWatcher::remove(PowerWatcher *client) +{ + assert(mPowerClients.find(client) != mPowerClients.end()); + mPowerClients.erase(client); +} + + +// +// Expose the process/pid map to the outside +// +Process *Server::findPid(pid_t pid) const +{ + PidMap::const_iterator it = mPids.find(pid); + return (it == mPids.end()) ? NULL : it->second; +} + + +// +// Set delayed shutdown mode +// +void Server::waitForClients(bool waiting) +{ + mWaitForClients = waiting; +} + + +// +// Begin shutdown processing. +// We relinquish our primary state authority. From now on, we'll be +// kept alive (only) by our current clients. +// +static FILE *reportFile; + +void Server::beginShutdown() +{ + StLock _(*this); + if (!mWaitForClients) { + SECURITYD_SHUTDOWN_NOW(); + _exit(0); + } else { + if (!mShuttingDown) { + mShuttingDown = true; + Session::invalidateAuthHosts(); + SECURITYD_SHUTDOWN_BEGIN(); + if (verbosity() >= 2) { + reportFile = fopen("/var/log/securityd-shutdown.log", "w"); + shutdownSnitch(); + } + } + } +} + + +// +// During shutdown, we report residual clients to dtrace, and allow a state dump +// for debugging. +// We don't bother locking for the shuttingDown() check; it's a latching boolean +// and we'll be good enough without a lock. +// +void Server::eventDone() +{ + if (this->shuttingDown()) { + StLock lazy(*this, false); // lazy lock acquisition + if (SECURITYD_SHUTDOWN_COUNT_ENABLED()) { + lazy.lock(); + SECURITYD_SHUTDOWN_COUNT(mProcesses.size(), VProc::Transaction::debugCount()); + } + if (verbosity() >= 2) { + lazy.lock(); + shutdownSnitch(); + } + IFDUMPING("shutdown", NodeCore::dumpAll()); + } +} + + +void Server::shutdownSnitch() +{ + time_t now; + time(&now); + fprintf(reportFile, "%.24s %d residual clients:\n", ctime(&now), int(mPids.size())); + for (PidMap::const_iterator it = mPids.begin(); it != mPids.end(); ++it) + if (SecCodeRef clientCode = it->second->processCode()) { + CFRef path; + OSStatus rc = SecCodeCopyPath(clientCode, kSecCSDefaultFlags, &path.aref()); + if (path) + fprintf(reportFile, " %s (%d)\n", cfString(path).c_str(), it->first); + else + fprintf(reportFile, "pid=%d (error %d)\n", it->first, int32_t(rc)); + } + fprintf(reportFile, "\n"); + fflush(reportFile); +} + +bool Server::inDarkWake() +{ + return IOPMIsADarkWake(IOPMConnectionGetSystemCapabilities()); +} + +// +// Initialize the CSSM/MDS subsystem. +// This was once done lazily on demand. These days, we are setting up the +// system MDS here, and CSSM is pretty much always needed, so this is called +// early during program startup. Do note that the server may not (yet) be running. +// +void Server::loadCssm(bool mdsIsInstalled) +{ + if (!mCssm->isActive()) { + StLock _(*this); + VProc::Transaction xact; + if (!mCssm->isActive()) { + if (!mdsIsInstalled) { // non-system securityd instance should not reinitialize MDS + secdebug("SS", "Installing MDS"); + IFDEBUG(if (geteuid() == 0)) + MDSClient::mds().install(); + } + secdebug("SS", "CSSM initializing"); + mCssm->init(); + mCSP->attach(); + secdebug("SS", "CSSM ready with CSP %s", mCSP->guid().toString().c_str()); + } + } +} + + +// +// LongtermActivity/lock combo +// +LongtermStLock::LongtermStLock(Mutex &lck) + : StLock(lck, false) // don't take the lock yet +{ + if (lck.tryLock()) { // uncontested + this->mActive = true; + } else { // contested - need backup thread + Server::active().longTermActivity(); + this->lock(); + } +}