]>
Commit | Line | Data |
---|---|---|
5c19dc3a A |
1 | /* |
2 | * Copyright (c) 2011-2014 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 "evaluationmanager.h" | |
25 | #include "policyengine.h" | |
26 | #include <security_utilities/cfmunge.h> | |
27 | #include <xpc/xpc.h> | |
28 | #include <exception> | |
29 | #include <vector> | |
30 | ||
31 | ||
32 | ||
33 | ||
34 | namespace Security { | |
35 | namespace CodeSigning { | |
36 | ||
37 | ||
38 | ||
39 | ||
40 | #pragma mark - EvaluationTask | |
41 | ||
42 | ||
43 | // | |
44 | // An evaluation task object manages the assessment - either directly, or in the | |
45 | // form of waiting for another evaluation task to finish an assessment on the | |
46 | // same target. | |
47 | // | |
48 | class EvaluationTask | |
49 | { | |
50 | public: | |
51 | CFURLRef path() const { return mPath.get(); } | |
52 | AuthorityType type() const { return mType; } | |
53 | bool isSharable() const { return mSharable; } | |
54 | void setUnsharable() { mSharable = false; } | |
55 | ||
56 | private: | |
57 | EvaluationTask(PolicyEngine *engine, CFURLRef path, AuthorityType type); | |
58 | virtual ~EvaluationTask(); | |
59 | void performEvaluation(SecAssessmentFlags flags, CFDictionaryRef context); | |
60 | void waitForCompletion(SecAssessmentFlags flags, CFMutableDictionaryRef result); | |
61 | ||
62 | PolicyEngine *mPolicyEngine; | |
63 | AuthorityType mType; | |
64 | dispatch_queue_t mWorkQueue; | |
65 | dispatch_queue_t mFeedbackQueue; | |
66 | dispatch_semaphore_t mAssessmentLock; | |
67 | __block dispatch_once_t mAssessmentKicked; | |
68 | int32_t mReferenceCount; | |
69 | int32_t mEvalCount; | |
70 | // This whole thing is a pre-existing crutch and must be fixed soon. | |
71 | #define UNOFFICIAL_MAX_XPC_ID_LENGTH 127 | |
72 | char mXpcActivityName[UNOFFICIAL_MAX_XPC_ID_LENGTH]; | |
73 | bool mSharable; | |
74 | ||
75 | CFCopyRef<CFURLRef> mPath; | |
76 | CFCopyRef<CFMutableDictionaryRef> mResult; | |
77 | std::vector<SecAssessmentFeedback> mFeedback; | |
78 | ||
79 | std::exception_ptr mExceptionToRethrow; | |
80 | ||
81 | friend class EvaluationManager; | |
82 | }; | |
83 | ||
84 | ||
85 | EvaluationTask::EvaluationTask(PolicyEngine *engine, CFURLRef path, AuthorityType type) : | |
86 | mPolicyEngine(engine), mType(type), mAssessmentLock(dispatch_semaphore_create(0)), | |
87 | mAssessmentKicked(0), mReferenceCount(0), mEvalCount(0), mSharable(true), | |
88 | mExceptionToRethrow(0) | |
89 | { | |
90 | mXpcActivityName[0] = 0; | |
91 | ||
92 | mWorkQueue = dispatch_queue_create("EvaluationTask", 0); | |
93 | mFeedbackQueue = dispatch_queue_create("EvaluationTaskFeedback", 0); | |
94 | ||
95 | mPath = path; | |
96 | mResult.take(makeCFMutableDictionary()); | |
97 | } | |
98 | ||
99 | ||
100 | EvaluationTask::~EvaluationTask() | |
101 | { | |
102 | dispatch_release(mFeedbackQueue); | |
103 | dispatch_release(mWorkQueue); | |
104 | dispatch_release(mAssessmentLock); | |
105 | } | |
106 | ||
107 | ||
108 | void EvaluationTask::performEvaluation(SecAssessmentFlags flags, CFDictionaryRef context) | |
109 | { | |
110 | bool performTheEvaluation = false; | |
111 | bool lowPriority = flags & kSecAssessmentFlagLowPriority; | |
112 | ||
113 | // each evaluation task performs at most a single evaluation | |
114 | if (OSAtomicIncrement32Barrier(&mEvalCount) == 1) | |
115 | performTheEvaluation = true; | |
116 | ||
117 | // define a block to run when the assessment has feedback available | |
118 | SecAssessmentFeedback relayFeedback = ^Boolean(CFStringRef type, CFDictionaryRef information) { | |
119 | ||
120 | __block Boolean proceed = true; | |
121 | dispatch_sync(mFeedbackQueue, ^{ | |
122 | if (mFeedback.size() > 0) { | |
123 | proceed = false; // we need at least one interested party to proceed | |
124 | // forward the feedback to all registered listeners | |
125 | for (int i = 0; i < mFeedback.size(); ++i) { | |
126 | proceed |= mFeedback[i](type, information); | |
127 | } | |
128 | } | |
129 | }); | |
130 | if (!proceed) | |
131 | this->setUnsharable(); // don't share an expiring evaluation task | |
132 | return proceed; | |
133 | }; | |
134 | ||
135 | ||
136 | // if the calling context has a feedback block, register it to listen to | |
137 | // our feedback relay | |
138 | dispatch_sync(mFeedbackQueue, ^{ | |
139 | SecAssessmentFeedback feedback = (SecAssessmentFeedback)CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback); | |
140 | if (feedback && CFGetTypeID(feedback) == CFGetTypeID(relayFeedback)) | |
141 | mFeedback.push_back(feedback); | |
142 | }); | |
143 | ||
144 | // if we haven't already started the evaluation (we're the first interested | |
145 | // party), do it now | |
146 | if (performTheEvaluation) { | |
147 | dispatch_semaphore_t startLock = dispatch_semaphore_create(0); | |
148 | ||
149 | // create the assessment block | |
150 | dispatch_async(mWorkQueue, dispatch_block_create_with_qos_class(DISPATCH_BLOCK_ENFORCE_QOS_CLASS, QOS_CLASS_UTILITY, 0, ^{ | |
151 | // signal that the assessment is ready to start | |
152 | dispatch_semaphore_signal(startLock); | |
153 | ||
154 | // wait until we're permitted to start the assessment. if we're in low | |
155 | // priority mode, this will not happen until we're on AC power. if not | |
156 | // in low priority mode, we're either already free to perform the | |
157 | // assessment or we will be quite soon | |
158 | dispatch_semaphore_wait(mAssessmentLock, DISPATCH_TIME_FOREVER); | |
159 | ||
160 | // Unregister a possibly still scheduled activity, as it lost its point. | |
161 | if (strlen(mXpcActivityName)) { | |
162 | xpc_activity_unregister(mXpcActivityName); | |
163 | } | |
164 | ||
165 | // copy the original context into our own mutable dictionary and replace | |
166 | // (or assign) the feedback entry within it to our multi-receiver | |
167 | // feedback relay block | |
168 | CFRef<CFMutableDictionaryRef> contextOverride = makeCFMutableDictionary(context); | |
169 | CFDictionaryRemoveValue(contextOverride.get(), kSecAssessmentContextKeyFeedback); | |
170 | CFDictionaryAddValue(contextOverride.get(), kSecAssessmentContextKeyFeedback, relayFeedback); | |
171 | ||
172 | try { | |
173 | // perform the evaluation | |
174 | switch (mType) { | |
175 | case kAuthorityExecute: | |
176 | mPolicyEngine->evaluateCode(mPath.get(), kAuthorityExecute, flags, contextOverride.get(), mResult.get(), true); | |
177 | break; | |
178 | case kAuthorityInstall: | |
179 | mPolicyEngine->evaluateInstall(mPath.get(), flags, contextOverride.get(), mResult.get()); | |
180 | break; | |
181 | case kAuthorityOpenDoc: | |
182 | mPolicyEngine->evaluateDocOpen(mPath.get(), flags, contextOverride.get(), mResult.get()); | |
183 | break; | |
184 | default: | |
185 | MacOSError::throwMe(errSecCSInvalidAttributeValues); | |
186 | break; | |
187 | } | |
188 | } catch(...) { | |
189 | mExceptionToRethrow = std::current_exception(); | |
190 | } | |
191 | ||
192 | })); | |
193 | ||
194 | // wait for the assessment to start | |
195 | dispatch_semaphore_wait(startLock, DISPATCH_TIME_FOREVER); | |
196 | dispatch_release(startLock); | |
197 | ||
198 | if (lowPriority) { | |
199 | // This whole thing is a crutch and should be handled differently. | |
200 | // Maybe by having just one activity that just kicks off all remaining | |
201 | // background assessments, CTS determines that it's a good time. | |
202 | ||
203 | // reduce the bundle path name to just the app component and generate an | |
204 | // xpc_activity identifier from it. this identifier should be smaller than | |
205 | // 128 characters due to rdar://problem/20094806 | |
206 | string path = cfString(mPath); | |
207 | size_t bundleNamePosition = path.rfind('/'); | |
208 | const char *bundleName = "/default"; | |
209 | if (bundleNamePosition != string::npos) | |
210 | bundleName = path.c_str() + bundleNamePosition; | |
211 | snprintf(mXpcActivityName, UNOFFICIAL_MAX_XPC_ID_LENGTH, "com.apple.security.assess%s", bundleName); | |
212 | ||
213 | // schedule the assessment to be permitted to run (beyond start) -- this | |
214 | // will either happen once we're no longer on battery power, or | |
215 | // immediately, based on the flag value of kSecAssessmentFlagLowPriority | |
216 | xpc_object_t criteria = xpc_dictionary_create(NULL, NULL, 0); | |
217 | xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_REPEATING, false); | |
218 | xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_DELAY, 0); | |
219 | xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_GRACE_PERIOD, 0); | |
220 | ||
221 | xpc_dictionary_set_string(criteria, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_MAINTENANCE); | |
222 | xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_ALLOW_BATTERY, false); | |
223 | ||
224 | xpc_activity_register(mXpcActivityName, criteria, ^(xpc_activity_t activity) { | |
225 | dispatch_once(&mAssessmentKicked, ^{ | |
226 | dispatch_semaphore_signal(mAssessmentLock); | |
227 | }); | |
228 | }); | |
229 | xpc_release(criteria); | |
230 | } | |
231 | } | |
232 | ||
233 | // If this is a foreground assessment to begin with, or if an assessment | |
234 | // with an existing task has been requested in the foreground, kick it | |
235 | // immediately. | |
236 | if (!lowPriority) { | |
237 | dispatch_once(&mAssessmentKicked, ^{ | |
238 | dispatch_semaphore_signal(mAssessmentLock); | |
239 | }); | |
240 | } | |
241 | } | |
242 | ||
243 | ||
244 | ||
245 | void EvaluationTask::waitForCompletion(SecAssessmentFlags flags, CFMutableDictionaryRef result) | |
246 | { | |
247 | // if the caller didn't request low priority we will elevate the dispatch | |
248 | // queue priority via our wait block | |
249 | dispatch_qos_class_t qos_class = QOS_CLASS_USER_INITIATED; | |
250 | if (flags & kSecAssessmentFlagLowPriority) | |
251 | qos_class = QOS_CLASS_UTILITY; | |
252 | ||
253 | // wait for the assessment to complete; our wait block will queue up behind | |
254 | // the assessment and the copy its results | |
255 | dispatch_sync(mWorkQueue, dispatch_block_create_with_qos_class (DISPATCH_BLOCK_ENFORCE_QOS_CLASS, qos_class, 0, ^{ | |
256 | // copy the class result back to the caller | |
257 | cfDictionaryApplyBlock(mResult.get(), ^(const void *key, const void *value){ | |
258 | CFDictionaryAddValue(result, key, value); | |
259 | }); | |
260 | })); | |
5c19dc3a A |
261 | } |
262 | ||
263 | ||
264 | ||
265 | #pragma mark - | |
266 | ||
267 | ||
268 | static Boolean evaluationTasksAreEqual(const EvaluationTask *task1, const EvaluationTask *task2) | |
269 | { | |
270 | if (!task1->isSharable() || !task2->isSharable()) return false; | |
271 | if ((task1->type() != task2->type()) || | |
272 | (cfString(task1->path()) != cfString(task2->path()))) | |
273 | return false; | |
274 | ||
275 | return true; | |
276 | } | |
277 | ||
278 | ||
279 | ||
280 | ||
281 | #pragma mark - EvaluationManager | |
282 | ||
283 | ||
284 | EvaluationManager *EvaluationManager::globalManager() | |
285 | { | |
286 | static EvaluationManager *singleton; | |
287 | static dispatch_once_t onceToken; | |
288 | dispatch_once(&onceToken, ^{ | |
289 | singleton = new EvaluationManager(); | |
290 | }); | |
291 | return singleton; | |
292 | } | |
293 | ||
294 | ||
295 | EvaluationManager::EvaluationManager() | |
296 | { | |
297 | static CFDictionaryValueCallBacks evalTaskValueCallbacks = kCFTypeDictionaryValueCallBacks; | |
298 | evalTaskValueCallbacks.equal = (CFDictionaryEqualCallBack)evaluationTasksAreEqual; | |
299 | evalTaskValueCallbacks.retain = NULL; | |
300 | evalTaskValueCallbacks.release = NULL; | |
301 | mCurrentEvaluations.take( | |
302 | CFDictionaryCreateMutable(NULL, | |
303 | 0, | |
304 | &kCFTypeDictionaryKeyCallBacks, | |
305 | &evalTaskValueCallbacks)); | |
306 | ||
307 | mListLockQueue = dispatch_queue_create("EvaluationManagerSyncronization", 0); | |
308 | } | |
309 | ||
310 | ||
311 | EvaluationManager::~EvaluationManager() | |
312 | { | |
313 | dispatch_release(mListLockQueue); | |
314 | } | |
315 | ||
316 | ||
317 | EvaluationTask *EvaluationManager::evaluationTask(PolicyEngine *engine, CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) | |
318 | { | |
319 | __block EvaluationTask *evalTask = NULL; | |
320 | ||
321 | dispatch_sync(mListLockQueue, ^{ | |
322 | // is path already being evaluated? | |
323 | if (!(flags & kSecAssessmentFlagIgnoreActiveAssessments)) | |
324 | evalTask = (EvaluationTask *)CFDictionaryGetValue(mCurrentEvaluations.get(), path); | |
325 | if (!evalTask) { | |
326 | // create a new task for the evaluation | |
327 | evalTask = new EvaluationTask(engine, path, type); | |
328 | if (flags & kSecAssessmentFlagIgnoreActiveAssessments) | |
329 | evalTask->setUnsharable(); | |
330 | CFDictionaryAddValue(mCurrentEvaluations.get(), path, evalTask); | |
331 | } | |
332 | evalTask->mReferenceCount++; | |
333 | }); | |
334 | ||
335 | if (evalTask) | |
336 | evalTask->performEvaluation(flags, context); | |
337 | ||
338 | return evalTask; | |
339 | } | |
340 | ||
341 | ||
822b670c | 342 | void EvaluationManager::finalizeTask(EvaluationTask *task, SecAssessmentFlags flags, CFMutableDictionaryRef result) |
5c19dc3a A |
343 | { |
344 | task->waitForCompletion(flags, result); | |
822b670c A |
345 | |
346 | std::exception_ptr pendingException = task->mExceptionToRethrow; | |
347 | ||
348 | removeTask(task); | |
349 | ||
350 | if (pendingException) std::rethrow_exception(pendingException); | |
5c19dc3a A |
351 | } |
352 | ||
353 | ||
354 | void EvaluationManager::removeTask(EvaluationTask *task) | |
355 | { | |
356 | dispatch_sync(mListLockQueue, ^{ | |
357 | // are we done with this evaluation task? | |
358 | if (--task->mReferenceCount == 0) { | |
359 | // yes -- remove it from our list and delete the object | |
360 | CFDictionaryRemoveValue(mCurrentEvaluations.get(), task->path()); | |
361 | delete task; | |
362 | } | |
363 | }); | |
364 | } | |
365 | ||
366 | ||
367 | ||
368 | } // end namespace CodeSigning | |
369 | } // end namespace Security | |
370 |