]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_codesigning/lib/evaluationmanager.cpp
Security-57337.20.44.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / evaluationmanager.cpp
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 }));
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
342 void EvaluationManager::finalizeTask(EvaluationTask *task, SecAssessmentFlags flags, CFMutableDictionaryRef result)
343 {
344 task->waitForCompletion(flags, result);
345
346 std::exception_ptr pendingException = task->mExceptionToRethrow;
347
348 removeTask(task);
349
350 if (pendingException) std::rethrow_exception(pendingException);
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