]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_translocate/lib/SecTranslocateLSNotification.cpp
Security-59754.80.3.tar.gz
[apple/security.git] / OSX / libsecurity_translocate / lib / SecTranslocateLSNotification.cpp
1 /*
2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include <string>
25 #include <exception>
26
27 #include <dlfcn.h>
28 #include <dispatch/dispatch.h>
29 #include <CoreServices/CoreServicesPriv.h>
30
31 #include <security_utilities/cfutilities.h>
32 #include <security_utilities/unix++.h>
33 #include <security_utilities/logging.h>
34
35 #include "SecTranslocate.h"
36 #include "SecTranslocateLSNotification.hpp"
37 #include "SecTranslocateUtilities.hpp"
38 #include "SecTranslocateShared.hpp"
39
40 #define LS_FRAMEWORK_PATH "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices"
41
42 namespace Security {
43 namespace SecTranslocate {
44
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);
53
54 /* Class to contain all the Launch Services functions I need to weak link */
55 class LaunchServicesProxy
56 {
57 public:
58 static LaunchServicesProxy* get();
59
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;};
72
73 private:
74 LaunchServicesProxy();
75
76 void* handle;
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;
84 };
85
86 /* resolve all the symbols. Throws if something isn't found. */
87 LaunchServicesProxy::LaunchServicesProxy()
88 {
89 handle = checkedDlopen(LS_FRAMEWORK_PATH, RTLD_LAZY | RTLD_NOLOAD);
90
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");
98 }
99
100 /* Singleton getter for the proxy */
101 LaunchServicesProxy* LaunchServicesProxy::get()
102 {
103 static dispatch_once_t initialized;
104 static LaunchServicesProxy* me = NULL;
105 __block exception_ptr exception(0);
106
107 dispatch_once(&initialized, ^{
108 try
109 {
110 me = new LaunchServicesProxy();
111 }
112 catch (...)
113 {
114 Syslog::critical("SecTranslocate: error while creating LaunchServicesProxy");
115 exception = current_exception();
116 }
117 });
118
119 if (me == NULL)
120 {
121 if(exception)
122 {
123 rethrow_exception(exception); //already logged in this case
124 }
125 else
126 {
127 Syslog::critical("SecTranslocate: LaunchServicesProxy initialization has failed");
128 UnixError::throwMe(EINVAL);
129 }
130 }
131
132 return me;
133 }
134
135 /* Save the notification queue so we can do things async later */
136 LSNotificationMonitor::LSNotificationMonitor(dispatch_queue_t q): notificationQ(q)
137 {
138 if (notificationQ == NULL)
139 {
140 Syslog::critical("SecTranslocate::LSNotificationMonitor initialized without a queue.");
141 UnixError::throwMe(EINVAL);
142 }
143
144 dispatch_retain(notificationQ);
145 }
146
147 /* Release the dispatch queue if this ever gets destroyed */
148 LSNotificationMonitor::~LSNotificationMonitor()
149 {
150 dispatch_release(notificationQ);
151 }
152
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)
156 {
157 if(appPath == NULL)
158 {
159 Syslog::error("SecTranslocate: no appPath provided");
160 return "";
161 }
162
163 CFRef<CFURLRef> appURL = makeCFURL(appPath);
164 bool isTranslocated = false;
165
166 string out = cfString(appURL);
167
168 if (!SecTranslocateIsTranslocatedURL(appURL, &isTranslocated, NULL))
169 {
170 Syslog::error("SecTranslocate: path for asn doesn't exist or isn't accessible: %s",out.c_str());
171 return "";
172 }
173
174 if(!isTranslocated)
175 {
176 Syslog::error("SecTranslocate: asn is not translocated: %s",out.c_str());
177 return "";
178 }
179
180 return out;
181 }
182
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)
185 {
186 dispatch_async(notificationQ, ^(){
187 try
188 {
189 LaunchServicesProxy* lsp = LaunchServicesProxy::get();
190
191 CFRef<LSASNRef> asn = lsp->asnCreateWithPid(kCFAllocatorDefault, pid);
192
193 if(lsp->isApplicationRunning(kLSDefaultSessionID, asn))
194 {
195 LSNotificationID nid = lsp->scheduleNotificationOnQueueWithBlock(kLSDefaultSessionID,
196 cfEmptyArray(),
197 notificationQ,
198 ^ (LSNotificationCode notification,
199 CFAbsoluteTime notificationTime,
200 CFTypeRef dataRef,
201 LSASNRef affectedASNRef,
202 LSSessionID session,
203 LSNotificationID notificationID){
204 if( notification == kLSNotifyApplicationDeath && dataRef)
205 {
206 this->asnDied(dataRef);
207 }
208
209 lsp->unscheduleNotificationFunction(notificationID);
210 });
211 LSNotificationCode notificationCode = kLSNotifyApplicationDeath;
212 lsp->modifyNotification(nid, 1, &notificationCode, 0, NULL, asn, NULL);
213 }
214 else
215 {
216 Syslog::warning("SecTranslocate: pid %d checked in, but it is not running",pid);
217 }
218 }
219 catch(...)
220 {
221 Syslog::error("SecTranslocate: checkin failed for pid %d",pid);
222 }
223 });
224 }
225
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
230 {
231 string path;
232 try
233 {
234 CFDictionaryRef dict = NULL;
235 if(CFGetTypeID(data) == CFDictionaryGetTypeID())
236 {
237 dict = (CFDictionaryRef)data;
238 }
239 else
240 {
241 Syslog::error("SecTranslocate: no data dictionary at app death");
242 return;
243 }
244
245 LaunchServicesProxy* lsp = LaunchServicesProxy::get();
246 path = stringIfTranslocated((CFStringRef)CFDictionaryGetValue(dict,lsp->bundlePathKey()));
247 }
248 catch(...)
249 {
250 Syslog::error("SecTranslocate: asn death processing failed");
251 return;
252 }
253
254 /* wait 5 seconds after death */
255 dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC);
256
257 dispatch_after(when, notificationQ, ^() {
258 try
259 {
260 if(path.empty())
261 {
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();
267 }
268 else
269 {
270 /* remove the translocation point for the app */
271 destroyTranslocatedPathForUser(path);
272 }
273 }
274 catch(...)
275 {
276 Syslog::error("SecTranslocate: problem deleting translocation after app death: %s", path.c_str());
277 }
278 });
279 }
280
281 } //namespace SecTranslocate
282 } //namespace Security