]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_utilities/lib/mach++.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_utilities / lib / mach++.cpp
diff --git a/Security/libsecurity_utilities/lib/mach++.cpp b/Security/libsecurity_utilities/lib/mach++.cpp
new file mode 100644 (file)
index 0000000..f0ba24b
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 2000-2004,2011-2012,2014 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@
+ */
+
+
+//
+// mach++ - C++ bindings for useful Mach primitives
+//
+#include <security_utilities/mach++.h>
+#include <mach/mach_error.h>
+#include <security_utilities/debugging.h>
+#include <Security/cssmapple.h>                // error codes
+#include <servers/bootstrap_defs.h>    // debug
+#include <bootstrap_priv.h>
+
+namespace Security {
+namespace MachPlusPlus {
+
+
+//
+// Mach subsystem exceptions, a subclass of CssmCommonError
+//
+Error::Error(kern_return_t err) : error(err)
+{
+       SECURITY_EXCEPTION_THROW_MACH(this, err);
+}
+
+Error::~Error() throw()
+{ }
+
+
+OSStatus Error::osStatus() const
+{
+       switch (error) {
+       case MIG_BAD_ARGUMENTS:
+       case MIG_TYPE_ERROR:
+       case MIG_REMOTE_ERROR:
+               return CSSMERR_CSSM_SERVICE_NOT_AVAILABLE;              // IPC mismatch of some sort
+       default:
+               return -1;              //@@@ some "internal error" code, perhaps?
+       }
+}
+
+int Error::unixError() const
+{
+       switch (error) {
+       case MIG_BAD_ARGUMENTS:
+       case MIG_TYPE_ERROR:
+       case MIG_REMOTE_ERROR:
+               return ERPCMISMATCH;            // IPC mismatch of some sort
+       default:
+               return -1;              //@@@ some "internal error" code, perhaps?
+       }
+}
+
+void Error::check(kern_return_t status)
+{
+       if (status != KERN_SUCCESS)
+               Error::throwMe(status);
+}
+
+void Error::throwMe(kern_return_t err)
+{
+       throw Error(err);
+}
+
+
+//
+// Memory management
+//
+void *allocate(size_t size)
+{
+       vm_address_t address;
+       check(vm_allocate(mach_task_self(), &address, size, true));
+       return reinterpret_cast<void *>(address);
+}
+
+void deallocate(vm_address_t address, size_t size)
+{
+       check(vm_deallocate(mach_task_self(), address, size));
+}
+
+
+//
+// Port functions
+//
+mach_port_urefs_t Port::getRefs(mach_port_right_t right)
+{
+       mach_port_urefs_t count;
+       check(::mach_port_get_refs(self(), mPort, right, &count));
+       return count;
+}
+
+mach_port_t Port::requestNotify(mach_port_t notify, mach_msg_id_t type, mach_port_mscount_t sync)
+{
+    mach_port_t previous;
+    check(mach_port_request_notification(self(), mPort, type, sync, notify,
+        MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous));
+
+#if !defined(NDEBUG)
+       const char *typeName;
+       switch (type) {
+       case MACH_NOTIFY_PORT_DELETED:  typeName = "port deleted"; break;
+       case MACH_NOTIFY_PORT_DESTROYED:typeName = "port destroyed"; break;
+       case MACH_NOTIFY_NO_SENDERS:    typeName = "no senders"; break;
+       case MACH_NOTIFY_SEND_ONCE:             typeName = "send once"; break;
+       case MACH_NOTIFY_DEAD_NAME:             typeName = "dead name"; break;
+       default:                                                typeName = "???"; break;
+       }
+       if (notify == MACH_PORT_NULL)
+               secdebug("port", "%d cancel notify %s", port(), typeName);
+       else
+               secdebug("port", "%d request notify %s to %d (sync %d)", port(), typeName, notify, sync);
+#endif //!NDEBUG
+
+    return previous;
+}
+
+mach_port_t Port::cancelNotify(mach_msg_id_t type)
+{
+    // Mach won't let us unset the DPN port if we are already dead
+    // (EVEN if the DPN has already been sent!) So just ignore that case...
+    if (isDead())
+        return MACH_PORT_NULL;
+       return requestNotify(MACH_PORT_NULL, type);
+}
+
+mach_port_msgcount_t Port::qlimit() const
+{
+       mach_port_limits_t limits;
+       mach_msg_type_number_t infoCount = 1;
+       check(::mach_port_get_attributes(self(), mPort, MACH_PORT_LIMITS_INFO,
+               mach_port_info_t(&limits), &infoCount));
+       assert(infoCount == 1);
+       return limits.mpl_qlimit;
+}
+
+void Port::qlimit(mach_port_msgcount_t limit)
+{
+       mach_port_limits_t limits;
+       limits.mpl_qlimit = limit;
+       check(::mach_port_set_attributes(self(), mPort, MACH_PORT_LIMITS_INFO,
+               mach_port_info_t(&limits), MACH_PORT_LIMITS_INFO_COUNT));
+}
+
+
+//
+// PortSet features
+//
+set<Port> PortSet::members() const
+{
+       mach_port_array_t members;
+       mach_msg_type_number_t count;
+       check(::mach_port_get_set_status(self(), mPort, &members, &count));
+       try {
+               set<Port> result;
+               copy(members, members+count, inserter(result, result.begin()));
+               vm_deallocate(self(), vm_address_t(members), count * sizeof(members[0]));
+               return result;
+       } catch (...) {
+               vm_deallocate(self(), vm_address_t(members), count * sizeof(members[0]));
+               throw;
+       }
+}
+
+
+bool PortSet::contains(Port member) const
+{
+       set<Port> memberSet = members();
+       return memberSet.find(member) != memberSet.end();
+}
+
+
+//
+// Task port features
+//
+TaskPort::TaskPort(pid_t pid)
+{
+       check(::task_for_pid(self(), pid, &mPort));
+}
+
+pid_t TaskPort::pid() const
+{
+    pid_t pid;
+    check(::pid_for_task(mPort, &pid));
+    return pid;
+}
+
+
+//
+// Bootstrap port management
+//
+mach_port_t Bootstrap::checkIn(const char *name) const
+{
+       mach_port_t port;
+       check(::bootstrap_check_in(mPort, makeName(name), &port));
+       return port;
+}
+
+mach_port_t Bootstrap::checkInOptional(const char *name) const
+{
+       mach_port_t port;
+       switch (kern_return_t err = ::bootstrap_check_in(mPort, makeName(name), &port)) {
+       case BOOTSTRAP_SERVICE_ACTIVE:
+    case BOOTSTRAP_UNKNOWN_SERVICE:
+       case BOOTSTRAP_NOT_PRIVILEGED:
+        return MACH_PORT_NULL;
+       default:
+               check(err);
+       }
+       return port;
+}
+
+void Bootstrap::registerAs(mach_port_t port, const char *name) const
+{
+       secdebug("bootstrap", "creating service port %d in %d:%s", port, this->port(), name);
+       check(::bootstrap_register(mPort, makeName(name), port));
+}
+
+mach_port_t Bootstrap::lookup(const char *name) const
+{
+       mach_port_t port;
+       check(::bootstrap_look_up(mPort, makeName(name), &port));
+       return port;
+}
+
+mach_port_t Bootstrap::lookup2(const char *name) const
+{
+       mach_port_t port;
+       check(::bootstrap_look_up2(mPort, makeName(name), &port, 0, BOOTSTRAP_PRIVILEGED_SERVER));
+       return port;
+}
+
+mach_port_t Bootstrap::lookupOptional(const char *name) const
+{
+       mach_port_t port;
+       kern_return_t err = ::bootstrap_look_up(mPort, makeName(name), &port);
+    if (err == BOOTSTRAP_UNKNOWN_SERVICE)
+        return 0;
+    check(err);
+       return port;
+}
+
+
+Bootstrap Bootstrap::subset(Port requestor)
+{
+    mach_port_t sub;
+    check(::bootstrap_subset(mPort, requestor, &sub));
+    return sub;
+}
+
+
+//
+// ReceivePorts
+//
+ReceivePort::ReceivePort(const char *name, const Bootstrap &bootstrap, bool tryCheckin /* = true */)
+{
+       if (tryCheckin)
+               mPort = bootstrap.checkInOptional(name);
+       if (!mPort) {
+               allocate();
+               // Bootstrap registration requires a send right to (copy) send.
+               // Make a temporary one, send it, then take it away again, to avoid
+               // messing up the caller's send right accounting.
+               insertRight(MACH_MSG_TYPE_MAKE_SEND);
+               bootstrap.registerAs(mPort, name);
+               modRefs(MACH_PORT_RIGHT_SEND, -1);
+       }
+}
+
+
+//
+// Stack-based bootstrap switcher
+//
+ModuleNexus<Mutex> StBootstrap::critical;
+
+StBootstrap::StBootstrap(const Bootstrap &newBoot, const TaskPort &task) 
+    : mTask(task), locker(critical())
+{
+    mOldBoot = Bootstrap();
+    mTask.bootstrap(newBoot);
+    secdebug("StBoot", "bootstrap for %d switched to %d", mTask.port(), newBoot.port());
+}
+
+StBootstrap::~StBootstrap()
+{
+    mTask.bootstrap(mOldBoot);
+    secdebug("StBoot", "bootstrap for %d returned to %d", mTask.port(), mOldBoot.port());
+}
+
+
+//
+// Mach message buffers
+//
+Message::Message(void *buffer, mach_msg_size_t size)
+       : mBuffer(NULL), mRelease(false)
+{
+       setBuffer(buffer, size);
+}
+
+Message::Message(mach_msg_size_t size)
+       : mBuffer(NULL), mRelease(false)
+{
+       setBuffer(size);
+}
+
+Message::Message()
+       : mBuffer(NULL), mRelease(false)
+{ }
+
+
+Message::~Message()
+{
+       release();
+}
+
+
+void Message::setBuffer(void *buffer, mach_msg_size_t size)
+{
+       release();
+       mBuffer = reinterpret_cast<mig_reply_error_t *>(buffer);
+       mSize = size;
+       mRelease = false;
+}
+
+void Message::setBuffer(mach_msg_size_t size)
+{
+       assert(size >= sizeof(mach_msg_header_t));
+       release();
+       mSize = size + MAX_TRAILER_SIZE;
+       mBuffer = reinterpret_cast<mig_reply_error_t *>(new char[mSize]);
+       mRelease = true;
+}
+
+
+void Message::release()
+{
+       if (mRelease)
+               delete[] reinterpret_cast<char *>(mBuffer);
+}
+       
+
+bool Message::check(kern_return_t status)
+{
+    switch (status) {
+    case KERN_SUCCESS:
+        return true;
+    case MACH_RCV_TIMED_OUT:
+    case MACH_SEND_TIMED_OUT:
+        return false;
+    default:
+        Error::throwMe(status);
+    }
+}
+
+
+bool Message::send(mach_msg_option_t options,
+    mach_msg_timeout_t timeout,
+    mach_port_name_t notify)
+{
+    return check(mach_msg_overwrite(*this,
+        options | MACH_SEND_MSG,
+        length(),
+        0, MACH_PORT_NULL,
+        timeout, notify,
+        NULL, 0));
+}
+
+bool Message::receive(mach_port_t receivePort,
+    mach_msg_option_t options,
+    mach_msg_timeout_t timeout,
+    mach_port_name_t notify)
+{
+    return check(mach_msg_overwrite(*this,
+        options | MACH_RCV_MSG,
+        length(),
+        mSize, receivePort,
+        timeout, notify,
+        NULL, 0));
+}
+
+bool Message::sendReceive(mach_port_t receivePort,
+    mach_msg_option_t options,
+    mach_msg_timeout_t timeout,
+    mach_port_name_t notify)
+{
+    return check(mach_msg_overwrite(*this,
+        options | MACH_SEND_MSG | MACH_RCV_MSG,
+        length(),
+        mSize, receivePort,
+        timeout, notify,
+        NULL, 0));
+}
+
+
+//
+// Debug dumping of ports etc.
+//
+#if defined(DEBUGDUMP)
+
+void Port::dump(const char *descr)
+{
+    if (mPort == MACH_PORT_NULL) {
+        Debug::dump("[%s==NULL]\n", descr ? descr : "port");
+    } else {
+        Debug::dump("[%s(%d)", descr ? descr : "port", mPort);
+        mach_port_type_t type;
+        if (kern_return_t err = mach_port_type(self(), mPort, &type)) {
+            Debug::dump(" !%s", mach_error_string(err));
+        } else {
+            if (type & MACH_PORT_TYPE_SEND)
+                Debug::dump(" send(%d)", getRefs(MACH_PORT_RIGHT_SEND));
+            if (type & MACH_PORT_TYPE_RECEIVE)
+                Debug::dump(" rcv");
+            if (type & MACH_PORT_TYPE_SEND_ONCE)
+                Debug::dump(" once(%d)", getRefs(MACH_PORT_RIGHT_SEND));
+            if (type & MACH_PORT_TYPE_PORT_SET)
+                Debug::dump(" set");
+            if (type & MACH_PORT_TYPE_DEAD_NAME)
+                Debug::dump(" dead(%d)", getRefs(MACH_PORT_RIGHT_SEND));        
+            if (type & MACH_PORT_TYPE_DNREQUEST)
+                Debug::dump(" dnreq");
+            // handle unknown/unexpected type flags
+            if (type & ~(MACH_PORT_TYPE_SEND|MACH_PORT_TYPE_RECEIVE|MACH_PORT_TYPE_SEND_ONCE|
+                    MACH_PORT_TYPE_PORT_SET|MACH_PORT_TYPE_DEAD_NAME|MACH_PORT_TYPE_DNREQUEST))
+                Debug::dump(" type(0x%x)", type);
+        }
+        Debug::dump("]\n");
+    }
+}
+
+
+#endif //DEBUGDUMP
+
+
+} // end namespace MachPlusPlus
+} // end namespace Security