2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
28 #include <dispatch/dispatch.h>
29 #include <CoreServices/CoreServicesPriv.h>
31 #include <security_utilities/cfutilities.h>
32 #include <security_utilities/unix++.h>
33 #include <security_utilities/logging.h>
35 #include "SecTranslocate.h"
36 #include "SecTranslocateLSNotification.hpp"
37 #include "SecTranslocateUtilities.hpp"
38 #include "SecTranslocateShared.hpp"
40 #define LS_FRAMEWORK_PATH "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices"
43 namespace SecTranslocate
{
45 /* Types for the LaunchServices symbols that I am Pseudo weak linking */
46 typedef void (^LSNotificationHandler_t
) (LSNotificationCode
, CFAbsoluteTime
, CFTypeRef
, LSASNRef
, LSSessionID
, LSNotificationID
);
47 typedef LSNotificationID (*ScheduleNotificationOnQueueWithBlock_t
) (LSSessionID
, CFTypeRef
, dispatch_queue_t
,LSNotificationHandler_t
);
48 typedef OSStatus (*ModifyNotification_t
)(LSNotificationID
, UInt32
, const LSNotificationCode
*, UInt32
, const LSNotificationCode
*, CFTypeRef
, CFTypeRef
);
49 typedef OSStatus (*UnscheduleNotificationFunction_t
)(LSNotificationID
);
50 typedef LSASNRef (*ASNCreateWithPid_t
)(CFAllocatorRef
, int);
51 typedef uint64_t (*ASNToUInt64_t
)(LSASNRef
);
52 typedef Boolean (*IsApplicationRunning_t
)(LSSessionID
, LSASNRef
);
54 /* Class to contain all the Launch Services functions I need to weak link */
55 class LaunchServicesProxy
58 static LaunchServicesProxy
* get();
60 inline LSNotificationID
scheduleNotificationOnQueueWithBlock (LSSessionID s
, CFTypeRef r
, dispatch_queue_t q
,LSNotificationHandler_t b
) const
61 { return pScheduleNotificationOnQueueWithBlock
? pScheduleNotificationOnQueueWithBlock(s
,r
,q
,b
) : (LSNotificationID
)kLSNotificationInvalidID
; };
62 inline OSStatus
modifyNotification(LSNotificationID i
, UInt32 c
, const LSNotificationCode
* s
, UInt32 l
, const LSNotificationCode
*n
, CFTypeRef a
, CFTypeRef r
) const
63 { return pModifyNotification
? pModifyNotification(i
,c
,s
,l
,n
,a
,r
) : kLSUnknownErr
; };
64 inline OSStatus
unscheduleNotificationFunction(LSNotificationID i
) const
65 { return pUnscheduleNotificationFunction
? pUnscheduleNotificationFunction(i
) : kLSUnknownErr
; };
66 inline LSASNRef
asnCreateWithPid(CFAllocatorRef a
, int p
) const
67 { return pASNCreateWithPid
? pASNCreateWithPid(a
,p
) : NULL
; };
68 inline uint64_t asnToUInt64 (LSASNRef a
) const
69 { return pASNToUInt64
? pASNToUInt64(a
) : 0; };
70 inline CFStringRef
bundlePathKey() const { return pBundlePathKey
? *pBundlePathKey
: NULL
;};
71 inline Boolean
isApplicationRunning(LSSessionID i
, LSASNRef a
) const {return pIsApplicationRunning
? pIsApplicationRunning(i
,a
): false;};
74 LaunchServicesProxy();
77 ScheduleNotificationOnQueueWithBlock_t pScheduleNotificationOnQueueWithBlock
;
78 ModifyNotification_t pModifyNotification
;
79 UnscheduleNotificationFunction_t pUnscheduleNotificationFunction
;
80 ASNCreateWithPid_t pASNCreateWithPid
;
81 ASNToUInt64_t pASNToUInt64
;
82 CFStringRef
*pBundlePathKey
;
83 IsApplicationRunning_t pIsApplicationRunning
;
86 /* resolve all the symbols. Throws if something isn't found. */
87 LaunchServicesProxy::LaunchServicesProxy()
89 handle
= checkedDlopen(LS_FRAMEWORK_PATH
, RTLD_LAZY
| RTLD_NOLOAD
);
91 pScheduleNotificationOnQueueWithBlock
= (ScheduleNotificationOnQueueWithBlock_t
) checkedDlsym(handle
, "_LSScheduleNotificationOnQueueWithBlock");
92 pModifyNotification
= (ModifyNotification_t
) checkedDlsym(handle
, "_LSModifyNotification");
93 pUnscheduleNotificationFunction
= (UnscheduleNotificationFunction_t
) checkedDlsym(handle
, "_LSUnscheduleNotificationFunction");
94 pASNCreateWithPid
= (ASNCreateWithPid_t
) checkedDlsym(handle
, "_LSASNCreateWithPid");
95 pASNToUInt64
= (ASNToUInt64_t
) checkedDlsym(handle
, "_LSASNToUInt64");
96 pBundlePathKey
= (CFStringRef
*) checkedDlsym(handle
, "_kLSBundlePathKey");
97 pIsApplicationRunning
= (IsApplicationRunning_t
) checkedDlsym(handle
, "_LSIsApplicationRunning");
100 /* Singleton getter for the proxy */
101 LaunchServicesProxy
* LaunchServicesProxy::get()
103 static dispatch_once_t initialized
;
104 static LaunchServicesProxy
* me
= NULL
;
105 __block exception_ptr
exception(0);
107 dispatch_once(&initialized
, ^{
110 me
= new LaunchServicesProxy();
114 Syslog::critical("SecTranslocate: error while creating LaunchServicesProxy");
115 exception
= current_exception();
123 rethrow_exception(exception
); //already logged in this case
127 Syslog::critical("SecTranslocate: LaunchServicesProxy initialization has failed");
128 UnixError::throwMe(EINVAL
);
135 /* Save the notification queue so we can do things async later */
136 LSNotificationMonitor::LSNotificationMonitor(dispatch_queue_t q
): notificationQ(q
)
138 if (notificationQ
== NULL
)
140 Syslog::critical("SecTranslocate::LSNotificationMonitor initialized without a queue.");
141 UnixError::throwMe(EINVAL
);
144 dispatch_retain(notificationQ
);
147 /* Release the dispatch queue if this ever gets destroyed */
148 LSNotificationMonitor::~LSNotificationMonitor()
150 dispatch_release(notificationQ
);
153 /* Check to see if a path is translocated. If it isn't or no path is provided then return
154 an empty string. If it is, return the path as a c++ string. */
155 string
LSNotificationMonitor::stringIfTranslocated(CFStringRef appPath
)
159 Syslog::error("SecTranslocate: no appPath provided");
163 CFRef
<CFURLRef
> appURL
= makeCFURL(appPath
);
164 bool isTranslocated
= false;
166 string out
= cfString(appURL
);
168 if (!SecTranslocateIsTranslocatedURL(appURL
, &isTranslocated
, NULL
))
170 Syslog::error("SecTranslocate: path for asn doesn't exist or isn't accessible: %s",out
.c_str());
176 Syslog::error("SecTranslocate: asn is not translocated: %s",out
.c_str());
183 /* register for a notification about the death of the requested PID with launch services if the pid is translocated */
184 void LSNotificationMonitor::checkIn(pid_t pid
)
186 dispatch_async(notificationQ
, ^(){
189 LaunchServicesProxy
* lsp
= LaunchServicesProxy::get();
191 CFRef
<LSASNRef
> asn
= lsp
->asnCreateWithPid(kCFAllocatorDefault
, pid
);
193 if(lsp
->isApplicationRunning(kLSDefaultSessionID
, asn
))
195 LSNotificationID nid
= lsp
->scheduleNotificationOnQueueWithBlock(kLSDefaultSessionID
,
198 ^ (LSNotificationCode notification
,
199 CFAbsoluteTime notificationTime
,
201 LSASNRef affectedASNRef
,
203 LSNotificationID notificationID
){
204 if( notification
== kLSNotifyApplicationDeath
&& dataRef
)
206 this->asnDied(dataRef
);
209 lsp
->unscheduleNotificationFunction(notificationID
);
211 LSNotificationCode notificationCode
= kLSNotifyApplicationDeath
;
212 lsp
->modifyNotification(nid
, 1, ¬ificationCode
, 0, NULL
, asn
, NULL
);
216 Syslog::warning("SecTranslocate: pid %d checked in, but it is not running",pid
);
221 Syslog::error("SecTranslocate: checkin failed for pid %d",pid
);
226 /* use the supplied dictionary to perform volume cleanup. If the dictionary contains a bundle path
227 and that bundle path still exists and is translocated, then unmount it. Otherwise trigger a
228 unmount of any translocation point that doesn't point to an existant volume. */
229 void LSNotificationMonitor::asnDied(CFTypeRef data
) const
234 CFDictionaryRef dict
= NULL
;
235 if(CFGetTypeID(data
) == CFDictionaryGetTypeID())
237 dict
= (CFDictionaryRef
)data
;
241 Syslog::error("SecTranslocate: no data dictionary at app death");
245 LaunchServicesProxy
* lsp
= LaunchServicesProxy::get();
246 path
= stringIfTranslocated((CFStringRef
)CFDictionaryGetValue(dict
,lsp
->bundlePathKey()));
250 Syslog::error("SecTranslocate: asn death processing failed");
254 /* wait 5 seconds after death */
255 dispatch_time_t when
= dispatch_time(DISPATCH_TIME_NOW
, 5LL * NSEC_PER_SEC
);
257 dispatch_after(when
, notificationQ
, ^() {
262 /* we got an asn death notification but the path either wasn't translocated or didn't exist
263 in case it didn't exist try to clean up stale translocation points.
264 Calling this function with no parameter defaults to a blank volume which causes
265 only translocation points that point to non-existant volumes to be cleaned up. */
266 destroyTranslocatedPathsForUserOnVolume();
270 /* remove the translocation point for the app */
271 destroyTranslocatedPathForUser(path
);
276 Syslog::error("SecTranslocate: problem deleting translocation after app death: %s", path
.c_str());
281 } //namespace SecTranslocate
282 } //namespace Security