]>
Commit | Line | Data |
---|---|---|
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, ¬ificationCode, 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 |