X-Git-Url: https://git.saurik.com/apple/securityd.git/blobdiff_plain/eeadf2e6470f45ea0275a6019635573f2a7b5a2c..4cd1cad0dea00daa03e1b54fdf2797a02373ad5b:/src/child.cpp?ds=sidebyside diff --git a/src/child.cpp b/src/child.cpp index 27b9deb..812c611 100644 --- a/src/child.cpp +++ b/src/child.cpp @@ -3,8 +3,6 @@ * * @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 @@ -28,330 +26,112 @@ // child - track a single child process and its belongings // #include "child.h" -#include +#include "dtrace.h" #include -#include - -kern_return_t -ucsp_server_handleSignal(mach_port_t sport, - mach_port_t task_port, - int signal_number) -{ - try { - if (task_port != mach_task_self()) { - Syslog::error("handleSignal: recieved from someone other than myself"); - } else { - ChildManager::childManager.handleSignal(signal_number); - } - } catch(...) {} - mach_port_deallocate(mach_task_self(), task_port); - - return 0; -} - -kern_return_t -ucsp_server_registerChild(mach_port_t sport, - mach_port_t rport, - mach_port_t task_port) -{ - mach_port_t childPort = rport; - try { - pid_t pid; - kern_return_t kt = pid_for_task(task_port, &pid); - if (kt) { - Syslog::error("registerChild: pid_for_task returned: %d", kt); - } else { - ChildManager::childManager.registerChild(pid, childPort); - } - } catch(...) {} - - if (childPort) { - // Dealloc the childPort unless registerChild set it to zero, which indicates it took over ownership. - mach_port_deallocate(mach_task_self(), childPort); - } - - if (task_port) - mach_port_deallocate(mach_task_self(), task_port); - - return 0; -} // -// A Child object represents a UNIX process that was forked by us -// and may have some state associated with it. -// Although some children may be created per session (such as SecurityAgent instances) -// in general child processes are PerGlobal. +// We use a static Mutex to coordinate checkin // -class Child -{ -public: - Child(pid_t pid); - ~Child(); - - void waitStatus(int status); - int waitStatus() const { return mStatus; } - - void childPort(mach_port_t &childPort); - - // Return our childPort and transfer ownership of it. - mach_port_t childPort() { mach_port_t childPort = mChildPort; mChildPort = 0; return childPort; } - - void eraseFromMap(); - void insertInMap(); - - // Wait for the child to register or die. If it registered mChildPort will be non zero, it will be 0 if it didn't. - void waitForRegister(); - -private: - Mutex mLockInternal; - StLock mLock; - pid_t mPid; - - // Service port for this child. - // This should only be nonzero while this object owns the port. Once it is retrieved ownership is handed off. - mach_port_t mChildPort; - - int mStatus; // Exit status if child died. - bool mInMap; -}; - +Mutex ServerChild::mCheckinLock; // -// Construct a Child object. +// Make and break ServerChildren // -Child::Child(pid_t pid) : - mLock(mLockInternal), mPid(pid), mChildPort(0), mStatus(0), mInMap(false) +ServerChild::ServerChild() + : mCheckinCond(mCheckinLock) { } -Child::~Child() -{ - try { - eraseFromMap(); - if (mChildPort) { - // Dealloc our mChildPort someone else took over ownership. - mach_port_deallocate(mach_task_self(), mChildPort); - } - } catch (...) {} -} - -void -Child::eraseFromMap() -{ - if (mInMap) - ChildManager::childManager.eraseChild(mPid); -} - -void -Child::insertInMap() -{ - ChildManager::childManager.insertChild(mPid, this); - mInMap = true; -} - -void -Child::waitStatus(int status) -{ - mStatus = status; - mLock.unlock(); // Unlock the lock to unblock waitForRegister() -} - -void -Child::childPort(mach_port_t &childPort) -{ - mChildPort = childPort; - childPort = 0; // Tell our caller that we consumed the child port. - - mLock.unlock(); // Unlock the lock to unblock waitForRegister() -} - -void -Child::waitForRegister() -{ - // Try to lock the lock again (this won't succeed until we get a register or wait result). - mLock.lock(); - // Unlock as soon as we get the lock. - mLock.unlock(); -} - // -// ChildManager - Singleton Child Manager class +// If the ServerChild is destroyed, kill its process, nice or hard. // - -// The signleton ChildManager. -ChildManager ChildManager::childManager; - -ChildManager::ChildManager() -{ -} - -ChildManager::~ChildManager() -{ -} - -bool -ChildManager::forkChild(mach_port_t &outChildPort, int &outWaitStatus) -{ - StLock lock(mLock, false); - pid_t pid; - - /* Retry fork 10 times on failure after that just give up. */ - for (int tries = 0; tries < 10; ++tries) - { - lock.lock(); // aquire the lock - pid = fork(); - if (pid != pid_t(-1)) - break; - - /* Something went wrong. */ - lock.unlock(); // Release the lock so we aren't holding it during the usleep below. - - int err = errno; - if (err == EINTR) - continue; - - if (err == EAGAIN || err == ENOMEM) - usleep(100 * tries); - else - UnixError::throwMe(err); - } - - if (pid == 0) - { - // Child - return true. - return true; - } - else - { - Child child(pid); - child.insertInMap(); // Insert child into the map. - lock.unlock(); // Unlock as soon as we are done accessing mChildMap. - - child.waitForRegister(); - - // Remove child from the map. - child.eraseFromMap(); - - // Transfer ownership of child's childPort to our caller. - outChildPort = child.childPort(); - - if (!outChildPort) - outWaitStatus = child.waitStatus(); - - // Parent - return false - return false; - } -} - -void -ChildManager::handleSignal(int signal_number) -{ - if (signal_number == SIGCHLD) - handleSigChild(); -} - -void -ChildManager::registerChild(pid_t pid, mach_port_t &childPort) -{ - StLock _(mLock); - Child *child = findChild(pid); - if (child) - child->childPort(childPort); -} - -// Assumes mLock is not locked. -void -ChildManager::eraseChild(pid_t pid) -{ - StLock _(mLock); - mChildMap.erase(pid); -} - -// Assumes mLock is already locked. -void -ChildManager::insertChild(pid_t pid, Child *child) -{ - mChildMap[pid] = child; +// In case you wonder about the tango below, it's making sure we +// get to "It's dead, Jim" with the minimum number of checkChildren() +// calls while still working correctly if this is the only thread alive. +// +//@@@ We *could* define a "soft shutdown" MIG message to send to all +//@@@ ServerChildren in this situation. +// +ServerChild::~ServerChild() +{ + mServicePort.destroy(); + + if (state() == alive) { + this->kill(SIGTERM); // shoot it once + checkChildren(); // check for quick death + if (state() == alive) { + usleep(300000); // give it some grace + if (state() == alive) { // could have been reaped by another thread + checkChildren(); // check again + if (state() == alive) { // it... just... won't... die... + this->kill(SIGKILL); // take THAT! + checkChildren(); + if (state() == alive) // stuck zombie + abandon(); // leave the body behind + } + } + } + } } // -// Private functions +// Parent action during fork: wait until ready or dead, then return // - -Child * -ChildManager::findChild(pid_t pid) +void ServerChild::parentAction() { - ChildMap::iterator it = mChildMap.find(pid); - if (it == mChildMap.end()) - return NULL; + // wait for either checkin or (premature) death + secdebug("serverchild", "%p (pid %d) waiting for checkin", this, pid()); + StLock _(mCheckinLock); + while (!ready() && state() == alive) + mCheckinCond.wait(); - return it->second; + // so what happened? + if (state() == dead) { + // our child died + secdebug("serverchild", "%p (pid %d) died before checking in", this, pid()); + SECURITYD_CHILD_STILLBORN(this->pid()); + } else if (ready()) { + // child has checked in and is ready for service + secdebug("serverchild", "%p (pid %d) ready for service on port %d", + this, pid(), mServicePort.port()); + SECURITYD_CHILD_READY(this->pid()); + } else + assert(false); // how did we ever get here?! } -void -ChildManager::handleSigChild() -{ - for (int tries = 0; tries < 10; ++tries) - { - int status; - pid_t pid = waitpid(-1, &status, WNOHANG|WUNTRACED); - switch (pid) - { - case 0: - if (tries == 0) - Syslog::notice("Spurious SIGCHLD ignored"); - break; - case -1: - { - int err = errno; - if (err == ECHILD) - { - if (tries == 0) - Syslog::notice("Spurious SIGCHLD ignored"); - break; - } - - if (err != EINTR) - { - Syslog::error("waitpid after SIGCHLD failed: %s", strerror(err)); - break; - } - } - default: - if (WIFEXITED(status) || WIFSIGNALED(status)) - { - if (WIFEXITED(status)) - secdebug("child", "child with pid: %d exited: %d", pid, WEXITSTATUS(status)); - else if (WCOREDUMP(status)) - secdebug("child", "child with pid: %d terminated by signal: %d and dumped core", pid, WTERMSIG(status)); - else - secdebug("child", "child with pid: %d terminated by signal: %d", pid, WTERMSIG(status)); - - waitStatusForPid(pid, status); - } - else if (WSTOPSIG(status)) - { - secdebug("child", "child with pid: %d stopped by signal: %d", pid, WSTOPSIG(status)); - } - else - { - Syslog::error("child with pid: %d bogus waitpid status: %d", pid, status); - } - tries = 1; - } - } -} - -void -ChildManager::waitStatusForPid(pid_t pid, int status) -{ - StLock _(mLock); - Child *child = findChild(pid); - if (child) - child->waitStatus(status); +// +// Death action during fork: release the waiting creator thread, if any +// +void ServerChild::dying() +{ + SECURITYD_CHILD_DYING(this->pid()); + secdebug("serverchild", "%p is dead; resuming parent thread (if any)", this); + mCheckinCond.signal(); +} + + +void ServerChild::checkIn(Port servicePort, pid_t pid) +{ + if (ServerChild *child = Child::find(pid)) { + // Child was alive when last seen. Store service port and signal parent thread + { + StLock _(mCheckinLock); + child->mServicePort = servicePort; + servicePort.modRefs(MACH_PORT_RIGHT_SEND, +1); // retain send right + secdebug("serverchild", "%p (pid %d) checking in; resuming parent thread", + child, pid); + } + SECURITYD_CHILD_CHECKIN(pid, servicePort); + child->mCheckinCond.signal(); + } else { + // Child has died; is wrong kind; or spurious checkin. + // If it was a proper child, death notifications will wake up the parent thread + secdebug("serverchild", "pid %d not in child set; checkin ignored", pid); + SECURITYD_CHILD_CHECKIN(pid, 0); + } }