X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_utilities/lib/machserver.cpp diff --git a/libsecurity_utilities/lib/machserver.cpp b/libsecurity_utilities/lib/machserver.cpp new file mode 100644 index 00000000..c71336b7 --- /dev/null +++ b/libsecurity_utilities/lib/machserver.cpp @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2000-2007 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@ + */ + + +// +// machserver - C++ shell for writing Mach 3 servers +// +#include "machserver.h" +#include +#include +#include +#include +#include "mach_notify.h" +#include +#include + +#if defined(USECFCURRENTTIME) +# include +#else +# include +#endif + +namespace Security { +namespace MachPlusPlus { + + +// +// Global per-thread information +// +ModuleNexus< ThreadNexus > MachServer::thread; + + +// +// Create a server object. +// The resulting object is not "active", and any number of server objects +// can be in this "prepared" state at the same time. +// +MachServer::MachServer() +{ setup("(anonymous)"); } + +MachServer::MachServer(const char *name) + : mServerPort(name, bootstrap) +{ setup(name); } + +MachServer::MachServer(const char *name, const Bootstrap &boot) + : bootstrap(boot), mServerPort(name, bootstrap) +{ setup(name); } + +void MachServer::setup(const char *name) +{ + workerTimeout = 60 * 2; // 2 minutes default timeout + maxWorkerCount = 100; // sanity check limit + useFloatingThread = false; // tight thread management + + mPortSet += mServerPort; +} + +MachServer::~MachServer() +{ + // The ReceivePort members will clean themselves up. + // The bootstrap server will clear us from its map when our receive port dies. +} + + +// +// Add and remove extra listening ports. +// Messages directed to those ports are dispatched through the main handler. +// To get automatic call-out to another handler, use the Handler class. +// +void MachServer::add(Port receiver) +{ + SECURITY_MACHSERVER_PORT_ADD(receiver); + mPortSet += receiver; +} + +void MachServer::remove(Port receiver) +{ + SECURITY_MACHSERVER_PORT_REMOVE(receiver); + mPortSet -= receiver; +} + + +// +// Register for mach port notifications +// +void MachServer::notifyIfDead(Port port, bool doNotify) const +{ + if (doNotify) + port.requestNotify(mServerPort); + else + port.cancelNotify(); +} + +void MachServer::notifyIfUnused(Port port, bool doNotify) const +{ + if (doNotify) + port.requestNotify(port, MACH_NOTIFY_NO_SENDERS, true); + else + port.cancelNotify(MACH_NOTIFY_NO_SENDERS); +} + + +// +// Initiate service. +// This call will take control of the current thread and use it to service +// incoming requests. The thread will not be released until an error happens, which +// will cause an exception to be thrown. In other words, this never returns normally. +// We may also be creating additional threads to service concurrent requests +// as appropriate. +// @@@ Msg-errors in additional threads are not acted upon. +// +void MachServer::run(size_t maxSize, mach_msg_options_t options) +{ + // establish server-global (thread-shared) parameters + mMaxSize = maxSize; + mMsgOptions = options; + + // establish the thread pool state + // (don't need managerLock since we're the only thread as of yet) + idleCount = workerCount = 1; + nextCheckTime = Time::now() + workerTimeout; + leastIdleWorkers = 1; + highestWorkerCount = 1; + + // run server loop in initial (immortal) thread + SECURITY_MACHSERVER_START_THREAD(false); + runServerThread(false); + SECURITY_MACHSERVER_END_THREAD(false); + + // primary server thread exited somehow (not currently possible) + assert(false); +} + + +// +// This is the core of a server thread at work. It takes over the thread until +// (a) an error occurs, throwing an exception +// (b) low-load timeout happens, causing a normal return (doTimeout only) +// This code was once based on mach_msg_server.c, but it is getting harder to notice +// the lingering resemblance. +// +extern "C" boolean_t cdsa_notify_server(mach_msg_header_t *in, mach_msg_header_t *out); + +void MachServer::runServerThread(bool doTimeout) +{ + // allocate request/reply buffers + Message bufRequest(mMaxSize); + Message bufReply(mMaxSize); + + // all exits from runServerThread are through exceptions + try { + // register as a worker thread + perThread().server = this; + + for (;;) { + // progress hook + eventDone(); + + // process all pending timers + while (processTimer()) ; + + // check for worker idle timeout + { StLock _(managerLock); + // record idle thread low-water mark in scan interval + if (idleCount < leastIdleWorkers) + leastIdleWorkers = idleCount; + + // perform self-timeout processing + if (doTimeout) { + if (workerCount > maxWorkerCount) // someone reduced maxWorkerCount recently... + break; // ... so release this thread immediately + Time::Absolute rightNow = Time::now(); + if (rightNow >= nextCheckTime) { // reaping period complete; process + UInt32 idlers = leastIdleWorkers; + SECURITY_MACHSERVER_REAP(workerCount, idlers); + nextCheckTime = rightNow + workerTimeout; + leastIdleWorkers = INT_MAX; + if (idlers > 1) // multiple idle threads throughout measuring interval... + break; // ... so release this thread now + } + } + } + + // determine next timeout (if any) + bool indefinite = false; + Time::Interval timeout = workerTimeout; + { StLock _(managerLock); + if (timers.empty()) { + indefinite = !doTimeout; + } else { + timeout = max(Time::Interval(0), timers.next() - Time::now()); + if (doTimeout && workerTimeout < timeout) + timeout = workerTimeout; + } + } + if (SECURITY_MACHSERVER_RECEIVE_ENABLED()) + SECURITY_MACHSERVER_RECEIVE(indefinite ? 0 : timeout.seconds()); + + // receive next IPC request (or wait for timeout) + mach_msg_return_t mr = indefinite ? + mach_msg_overwrite(bufRequest, + MACH_RCV_MSG | mMsgOptions, + 0, mMaxSize, mPortSet, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, + (mach_msg_header_t *) 0, 0) + : + mach_msg_overwrite(bufRequest, + MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_INTERRUPT | mMsgOptions, + 0, mMaxSize, mPortSet, + mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL, + (mach_msg_header_t *) 0, 0); + + switch (mr) { + case MACH_MSG_SUCCESS: + // process received request message below + break; + default: + SECURITY_MACHSERVER_RECEIVE_ERROR(mr); + continue; + } + + // process received message + if (bufRequest.msgId() >= MACH_NOTIFY_FIRST && + bufRequest.msgId() <= MACH_NOTIFY_LAST) { + // mach kernel notification message + // we assume this is quick, so no thread arbitration here + cdsa_notify_server(bufRequest, bufReply); + } else { + // normal request message + StLock _(*this); + SECURITY_MACHSERVER_BEGIN(bufRequest.localPort(), bufRequest.msgId()); + + // try subsidiary handlers first + bool handled = false; + for (HandlerSet::const_iterator it = mHandlers.begin(); + it != mHandlers.end(); it++) + if (bufRequest.localPort() == (*it)->port()) { + (*it)->handle(bufRequest, bufReply); + handled = true; + } + if (!handled) { + // unclaimed, send to main handler + handle(bufRequest, bufReply); + } + + SECURITY_MACHSERVER_END(); + } + + // process reply generated by handler + if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) && + bufReply.returnCode() != KERN_SUCCESS) { + if (bufReply.returnCode() == MIG_NO_REPLY) + continue; + // don't destroy the reply port right, so we can send an error message + bufRequest.remotePort(MACH_PORT_NULL); + mach_msg_destroy(bufRequest); + } + + if (bufReply.remotePort() == MACH_PORT_NULL) { + // no reply port, so destroy the reply + if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX) + bufReply.destroy(); + continue; + } + + /* + * We don't want to block indefinitely because the client + * isn't receiving messages from the reply port. + * If we have a send-once right for the reply port, then + * this isn't a concern because the send won't block. + * If we have a send right, we need to use MACH_SEND_TIMEOUT. + * To avoid falling off the kernel's fast RPC path unnecessarily, + * we only supply MACH_SEND_TIMEOUT when absolutely necessary. + */ + mr = mach_msg_overwrite(bufReply, + (MACH_MSGH_BITS_REMOTE(bufReply.bits()) == + MACH_MSG_TYPE_MOVE_SEND_ONCE) ? + MACH_SEND_MSG | mMsgOptions : + MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions, + bufReply.length(), 0, MACH_PORT_NULL, + 0, MACH_PORT_NULL, NULL, 0); + switch (mr) { + case MACH_MSG_SUCCESS: + break; + default: + SECURITY_MACHSERVER_SEND_ERROR(mr, bufReply.remotePort()); + bufReply.destroy(); + break; + } + + + // clean up after the transaction + releaseDeferredAllocations(); + } + perThread().server = NULL; + + } catch (...) { + perThread().server = NULL; + throw; + } +} + + +// +// Manage subsidiary port handlers +// +void MachServer::add(Handler &handler) +{ + assert(mHandlers.find(&handler) == mHandlers.end()); + assert(handler.port() != MACH_PORT_NULL); + mHandlers.insert(&handler); + mPortSet += handler.port(); +} + +void MachServer::remove(Handler &handler) +{ + assert(mHandlers.find(&handler) != mHandlers.end()); + mHandlers.erase(&handler); + mPortSet -= handler.port(); +} + + +// +// Abstract auxiliary message handlers +// +MachServer::Handler::~Handler() +{ /* virtual */ } + + +// +// Implement a Handler that sends no reply +// +boolean_t MachServer::NoReplyHandler::handle(mach_msg_header_t *in, mach_msg_header_t *out) +{ + // set up reply message to be valid (enough) and read "do not send reply" + out->msgh_bits = 0; + out->msgh_remote_port = MACH_PORT_NULL; + out->msgh_size = sizeof(mig_reply_error_t); + ((mig_reply_error_t *)out)->RetCode = MIG_NO_REPLY; + + // call input-only handler + return handle(in); +} + + +// +// Register a memory block for deferred release. +// +void MachServer::releaseWhenDone(Allocator &alloc, void *memory) +{ + if (memory) { + set &releaseSet = perThread().deferredAllocations; + assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end()); + SECURITY_MACHSERVER_ALLOC_REGISTER(memory, &alloc); + releaseSet.insert(Allocation(memory, alloc)); + } +} + + +// +// Run through the accumulated deferred allocations and release them. +// This is done automatically on every pass through the server loop; +// it must be called by subclasses that implement their loop in some +// other way. +// @@@X Needs to be thread local +// +void MachServer::releaseDeferredAllocations() +{ + set &releaseSet = perThread().deferredAllocations; + for (set::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) { + SECURITY_MACHSERVER_ALLOC_RELEASE(it->addr, it->allocator); + + // before we release the deferred allocation, zap it so that secrets aren't left in memory + size_t memSize = malloc_size(it->addr); + bzero(it->addr, memSize); + it->allocator->free(it->addr); + } + releaseSet.erase(releaseSet.begin(), releaseSet.end()); +} + + +// +// The handler function calls this if it realizes that it might be blocked +// (or doing something that takes a long time). We respond by ensuring that +// at least one more thread is ready to serve requests. +// Calls the threadLimitReached callback in the server object if the thread +// limit has been exceeded and a needed new thread was not created. +// +void MachServer::longTermActivity() +{ + if (!useFloatingThread) { + StLock _(managerLock); + ensureReadyThread(); + } +} + +void MachServer::busy() +{ + StLock _(managerLock); + idleCount--; + if (useFloatingThread) + ensureReadyThread(); +} + +void MachServer::idle() +{ + StLock _(managerLock); + idleCount++; +} + + +void MachServer::ensureReadyThread() +{ + if (idleCount == 0) { + if (workerCount >= maxWorkerCount) { + this->threadLimitReached(workerCount); // call remedial handler + } + if (workerCount < maxWorkerCount) { // threadLimit() may have raised maxWorkerCount + (new LoadThread(*this))->run(); + } + } +} + + +// +// The callback hook for our subclasses. +// The default does nothing, thereby denying further thread creation. +// You could do something like maxThreads(limit+1) here to grant an variance; +// or throw an exception to avoid possible deadlocks (this would abort the current +// request but not otherwise impact the server's operation). +// +void MachServer::threadLimitReached(UInt32 limit) +{ +} + + +// +// What our (non-primary) load threads do +// +void MachServer::LoadThread::action() +{ + //@@@ race condition?! can server exit before helpers thread gets here? + + // register the worker thread and go + server.addThread(this); + try { + SECURITY_MACHSERVER_START_THREAD(true); + server.runServerThread(true); + SECURITY_MACHSERVER_END_THREAD(false); + } catch (...) { + // fell out of server loop by error. Let the thread go quietly + SECURITY_MACHSERVER_END_THREAD(true); + } + server.removeThread(this); +} + + +// +// Thread accounting +// +void MachServer::addThread(Thread *thread) +{ + StLock _(managerLock); + workerCount++; + idleCount++; + workers.insert(thread); +} + +void MachServer::removeThread(Thread *thread) +{ + StLock _(managerLock); + workerCount--; + idleCount--; + workers.erase(thread); +} + + +// +// Timer management +// +MachServer::Timer::~Timer() +{ } + +void MachServer::Timer::select() +{ } + +void MachServer::Timer::unselect() +{ } + +bool MachServer::processTimer() +{ + Timer *top; + { StLock _(managerLock); // could have multiple threads trying this + if (!(top = static_cast(timers.pop(Time::now())))) + return false; // nothing (more) to be done now + } // drop lock; work has been retrieved + try { + SECURITY_MACHSERVER_TIMER_START(top, top->longTerm(), Time::now().internalForm()); + StLock _t(*top); + if (top->longTerm()) { + StLock _(*this); + top->action(); + } else { + top->action(); + } + SECURITY_MACHSERVER_TIMER_END(false); + } catch (...) { + SECURITY_MACHSERVER_TIMER_END(true); + } + return true; +} + +void MachServer::setTimer(Timer *timer, Time::Absolute when) +{ + StLock _(managerLock); + timers.schedule(timer, when); +} + +void MachServer::clearTimer(Timer *timer) +{ + StLock _(managerLock); + if (timer->scheduled()) + timers.unschedule(timer); +} + + +// +// Notification hooks and shims. Defaults do nothing. +// +void cdsa_mach_notify_dead_name(mach_port_t, mach_port_name_t port) +{ + try { + MachServer::active().notifyDeadName(port); + } catch (...) { + } +} + +void MachServer::notifyDeadName(Port) { } + +void cdsa_mach_notify_port_deleted(mach_port_t, mach_port_name_t port) +{ + try { + MachServer::active().notifyPortDeleted(port); + } catch (...) { + } +} + +void MachServer::notifyPortDeleted(Port) { } + +void cdsa_mach_notify_port_destroyed(mach_port_t, mach_port_name_t port) +{ + try { + MachServer::active().notifyPortDestroyed(port); + } catch (...) { + } +} + +void MachServer::notifyPortDestroyed(Port) { } + +void cdsa_mach_notify_send_once(mach_port_t port) +{ + try { + MachServer::active().notifySendOnce(port); + } catch (...) { + } +} + +void MachServer::notifySendOnce(Port) { } + +void cdsa_mach_notify_no_senders(mach_port_t port, mach_port_mscount_t count) +{ + try { + MachServer::active().notifyNoSenders(port, count); + } catch (...) { + } +} + +void MachServer::notifyNoSenders(Port, mach_port_mscount_t) { } + +void MachServer::eventDone() { } + + +} // end namespace MachPlusPlus + +} // end namespace Security