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