--- /dev/null
+/*
+ * 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, ¬ificationCode, 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