]> git.saurik.com Git - apple/security.git/blobdiff - OSX/libsecurity_translocate/lib/SecTranslocateLSNotification.cpp
Security-57740.1.18.tar.gz
[apple/security.git] / OSX / libsecurity_translocate / lib / SecTranslocateLSNotification.cpp
diff --git a/OSX/libsecurity_translocate/lib/SecTranslocateLSNotification.cpp b/OSX/libsecurity_translocate/lib/SecTranslocateLSNotification.cpp
new file mode 100644 (file)
index 0000000..c4354e9
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2016 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@
+ */
+
+#include <string>
+#include <exception>
+
+#include <dlfcn.h>
+#include <dispatch/dispatch.h>
+#include <CoreServices/CoreServicesPriv.h>
+
+#include <security_utilities/cfutilities.h>
+#include <security_utilities/unix++.h>
+#include <security_utilities/logging.h>
+
+#include "SecTranslocate.h"
+#include "SecTranslocateLSNotification.hpp"
+#include "SecTranslocateUtilities.hpp"
+#include "SecTranslocateShared.hpp"
+
+#define LS_FRAMEWORK_PATH "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices"
+
+namespace Security {
+namespace SecTranslocate {
+
+/* Types for the LaunchServices symbols that I am Pseudo weak linking */
+typedef void (^LSNotificationHandler_t) (LSNotificationCode, CFAbsoluteTime, CFTypeRef, LSASNRef, LSSessionID, LSNotificationID);
+typedef LSNotificationID (*ScheduleNotificationOnQueueWithBlock_t) (LSSessionID, CFTypeRef, dispatch_queue_t,LSNotificationHandler_t);
+typedef OSStatus (*ModifyNotification_t)(LSNotificationID, UInt32, const LSNotificationCode *, UInt32, const LSNotificationCode *, CFTypeRef, CFTypeRef);
+typedef OSStatus (*UnscheduleNotificationFunction_t)(LSNotificationID);
+typedef LSASNRef (*ASNCreateWithPid_t)(CFAllocatorRef, int);
+typedef uint64_t (*ASNToUInt64_t)(LSASNRef);
+typedef Boolean (*IsApplicationRunning_t)(LSSessionID, LSASNRef);
+
+/* Class to contain all the Launch Services functions I need to weak link */
+class LaunchServicesProxy
+{
+public:
+    static LaunchServicesProxy* get();
+
+    inline LSNotificationID scheduleNotificationOnQueueWithBlock (LSSessionID s, CFTypeRef r, dispatch_queue_t q,LSNotificationHandler_t b) const
+        { return pScheduleNotificationOnQueueWithBlock ? pScheduleNotificationOnQueueWithBlock(s,r,q,b) : (LSNotificationID)kLSNotificationInvalidID; };
+    inline OSStatus modifyNotification(LSNotificationID i, UInt32 c, const LSNotificationCode * s, UInt32 l, const LSNotificationCode *n, CFTypeRef a, CFTypeRef r) const
+        { return pModifyNotification ? pModifyNotification(i,c,s,l,n,a,r) : kLSUnknownErr; };
+    inline OSStatus unscheduleNotificationFunction(LSNotificationID i) const
+        { return pUnscheduleNotificationFunction ? pUnscheduleNotificationFunction(i) : kLSUnknownErr; };
+    inline LSASNRef asnCreateWithPid(CFAllocatorRef a, int p) const
+        { return pASNCreateWithPid ? pASNCreateWithPid(a,p) : NULL; };
+    inline uint64_t asnToUInt64 (LSASNRef a) const
+        { return pASNToUInt64 ? pASNToUInt64(a) : 0; };
+    inline CFStringRef bundlePathKey() const { return pBundlePathKey ? *pBundlePathKey : NULL;};
+    inline Boolean isApplicationRunning(LSSessionID i, LSASNRef a) const {return pIsApplicationRunning ? pIsApplicationRunning(i,a): false;};
+
+private:
+    LaunchServicesProxy();
+
+    void* handle;
+    ScheduleNotificationOnQueueWithBlock_t pScheduleNotificationOnQueueWithBlock;
+    ModifyNotification_t pModifyNotification;
+    UnscheduleNotificationFunction_t pUnscheduleNotificationFunction;
+    ASNCreateWithPid_t pASNCreateWithPid;
+    ASNToUInt64_t pASNToUInt64;
+    CFStringRef *pBundlePathKey;
+    IsApplicationRunning_t pIsApplicationRunning;
+};
+
+/* resolve all the symbols. Throws if something isn't found. */
+LaunchServicesProxy::LaunchServicesProxy()
+{
+    handle = checkedDlopen(LS_FRAMEWORK_PATH, RTLD_LAZY | RTLD_NOLOAD);
+
+    pScheduleNotificationOnQueueWithBlock = (ScheduleNotificationOnQueueWithBlock_t) checkedDlsym(handle, "_LSScheduleNotificationOnQueueWithBlock");
+    pModifyNotification = (ModifyNotification_t) checkedDlsym(handle, "_LSModifyNotification");
+    pUnscheduleNotificationFunction = (UnscheduleNotificationFunction_t) checkedDlsym(handle, "_LSUnscheduleNotificationFunction");
+    pASNCreateWithPid = (ASNCreateWithPid_t) checkedDlsym(handle, "_LSASNCreateWithPid");
+    pASNToUInt64 = (ASNToUInt64_t) checkedDlsym(handle, "_LSASNToUInt64");
+    pBundlePathKey = (CFStringRef*) checkedDlsym(handle, "_kLSBundlePathKey");
+    pIsApplicationRunning = (IsApplicationRunning_t) checkedDlsym(handle, "_LSIsApplicationRunning");
+}
+
+/* Singleton getter for the proxy */
+LaunchServicesProxy* LaunchServicesProxy::get()
+{
+    static dispatch_once_t initialized;
+    static LaunchServicesProxy* me = NULL;
+    __block exception_ptr exception(0);
+
+    dispatch_once(&initialized, ^{
+        try
+        {
+            me = new LaunchServicesProxy();
+        }
+        catch (...)
+        {
+            Syslog::critical("SecTranslocate: error while creating LaunchServicesProxy");
+            exception = current_exception();
+        }
+    });
+
+    if (me == NULL)
+    {
+        if(exception)
+        {
+            rethrow_exception(exception); //already logged in this case
+        }
+        else
+        {
+            Syslog::critical("SecTranslocate: LaunchServicesProxy initialization has failed");
+            UnixError::throwMe(EINVAL);
+        }
+    }
+
+    return me;
+}
+
+/* Save the notification queue so we can do things async later */
+LSNotificationMonitor::LSNotificationMonitor(dispatch_queue_t q): notificationQ(q)
+{
+    if (notificationQ == NULL)
+    {
+        Syslog::critical("SecTranslocate::LSNotificationMonitor initialized without a queue.");
+        UnixError::throwMe(EINVAL);
+    }
+    
+    dispatch_retain(notificationQ);
+}
+
+/* Release the dispatch queue if this ever gets destroyed */
+LSNotificationMonitor::~LSNotificationMonitor()
+{
+    dispatch_release(notificationQ);
+}
+
+/*  Check to see if a path is translocated. If it isn't or no path is provided then return 
+    an empty string. If it is, return the path as a c++ string. */
+string LSNotificationMonitor::stringIfTranslocated(CFStringRef appPath)
+{
+    if(appPath == NULL)
+    {
+        Syslog::error("SecTranslocate: no appPath provided");
+        return "";
+    }
+
+    CFRef<CFURLRef> appURL = makeCFURL(appPath);
+    bool isTranslocated = false;
+
+    string out = cfString(appURL);
+
+    if (!SecTranslocateIsTranslocatedURL(appURL, &isTranslocated, NULL))
+    {
+        Syslog::error("SecTranslocate: path for asn doesn't exist or isn't accessible: %s",out.c_str());
+        return "";
+    }
+
+    if(!isTranslocated)
+    {
+        Syslog::error("SecTranslocate: asn is not translocated: %s",out.c_str());
+        return "";
+    }
+
+    return out;
+}
+
+/* register for a notification about the death of the requested PID with launch services if the pid is translocated */
+void LSNotificationMonitor::checkIn(pid_t pid)
+{
+    dispatch_async(notificationQ, ^(){
+        try
+        {
+            LaunchServicesProxy* lsp = LaunchServicesProxy::get();
+
+            CFRef<LSASNRef> asn = lsp->asnCreateWithPid(kCFAllocatorDefault, pid);
+
+            if(lsp->isApplicationRunning(kLSDefaultSessionID, asn))
+            {
+                LSNotificationID nid = lsp->scheduleNotificationOnQueueWithBlock(kLSDefaultSessionID,
+                                                                                 cfEmptyArray(),
+                                                                                 notificationQ,
+                                                                                 ^ (LSNotificationCode notification,
+                                                                                    CFAbsoluteTime notificationTime,
+                                                                                    CFTypeRef dataRef,
+                                                                                    LSASNRef affectedASNRef,
+                                                                                    LSSessionID session,
+                                                                                    LSNotificationID notificationID){
+                    if( notification == kLSNotifyApplicationDeath && dataRef)
+                    {
+                        this->asnDied(dataRef);
+                    }
+
+                    lsp->unscheduleNotificationFunction(notificationID);
+                });
+                LSNotificationCode notificationCode = kLSNotifyApplicationDeath;
+                lsp->modifyNotification(nid, 1, &notificationCode, 0, NULL, asn, NULL);
+            }
+            else
+            {
+                Syslog::warning("SecTranslocate: pid %d checked in, but it is not running",pid);
+            }
+        }
+        catch(...)
+        {
+            Syslog::error("SecTranslocate: checkin failed for pid %d",pid);
+        }
+    });
+}
+
+/* use the supplied dictionary to perform volume cleanup. If the dictionary contains a bundle path
+  and that bundle path still exists and is translocated, then unmount it. Otherwise trigger a
+  unmount of any translocation point that doesn't point to an existant volume. */
+void LSNotificationMonitor::asnDied(CFTypeRef data) const
+{
+    string path;
+    try
+    {
+        CFDictionaryRef dict = NULL;
+        if(CFGetTypeID(data) == CFDictionaryGetTypeID())
+        {
+            dict = (CFDictionaryRef)data;
+        }
+        else
+        {
+            Syslog::error("SecTranslocate: no data dictionary at app death");
+            return;
+        }
+
+        LaunchServicesProxy* lsp = LaunchServicesProxy::get();
+        path = stringIfTranslocated((CFStringRef)CFDictionaryGetValue(dict,lsp->bundlePathKey()));
+    }
+    catch(...)
+    {
+        Syslog::error("SecTranslocate: asn death processing failed");
+        return;
+    }
+
+    /* wait 5 seconds after death */
+    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC);
+
+    dispatch_after(when, notificationQ, ^() {
+        try
+        {
+            if(path.empty())
+            {
+                /* we got an asn death notification but the path either wasn't translocated or didn't exist
+                   in case it didn't exist try to clean up stale translocation points.
+                   Calling this function with no parameter defaults to a blank volume which causes
+                   only translocation points that point to non-existant volumes to be cleaned up. */
+                destroyTranslocatedPathsForUserOnVolume();
+            }
+            else
+            {
+                /* remove the translocation point for the app */
+                destroyTranslocatedPathForUser(path);
+            }
+        }
+        catch(...)
+        {
+            Syslog::error("SecTranslocate: problem deleting translocation after app death: %s", path.c_str());
+        }
+    });
+}
+
+} //namespace SecTranslocate
+} //namespace Security