2 * Copyright (c) 2011-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include "evaluationmanager.h"
25 #include "policyengine.h"
26 #include <security_utilities/cfmunge.h>
27 #include <Security/SecEncodeTransform.h>
28 #include <Security/SecDigestTransform.h>
37 namespace CodeSigning
{
41 static CFStringRef
EvaluationTaskCreateKey(CFURLRef path
, AuthorityType type
)
43 CFErrorRef errors
= NULL
;
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();
50 CFRef
<SecTransformRef
> sha1
= SecDigestTransformCreate(kSecDigestSHA2
, 256, &errors
);
56 CFRef
<SecTransformRef
> b64
= SecEncodeTransformCreate(kSecBase64Encoding
, &errors
);
62 SecTransformSetAttribute(sha1
, kSecTransformInputAttributeName
, data
, &errors
);
68 SecTransformConnectTransforms(sha1
, kSecTransformOutputAttributeName
, b64
, kSecTransformInputAttributeName
, group
, &errors
);
74 CFRef
<CFDataRef
> keyData
= (CFDataRef
)SecTransformExecute(group
, &errors
);
80 return makeCFString(keyData
);
83 #pragma mark - EvaluationTask
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
94 CFURLRef
path() const { return mPath
.get(); }
95 AuthorityType
type() const { return mType
; }
96 bool isSharable() const { return mSharable
; }
97 void setUnsharable() { mSharable
= false; }
100 EvaluationTask(PolicyEngine
*engine
, CFURLRef path
, AuthorityType type
);
101 virtual ~EvaluationTask();
103 // Tasks cannot be copied.
104 EvaluationTask(EvaluationTask
const&) = delete;
105 EvaluationTask
& operator=(EvaluationTask
const&) = delete;
107 void performEvaluation(SecAssessmentFlags flags
, CFDictionaryRef context
);
108 void waitForCompletion(SecAssessmentFlags flags
, CFMutableDictionaryRef result
);
111 PolicyEngine
*mPolicyEngine
;
113 dispatch_queue_t mWorkQueue
;
114 dispatch_queue_t mFeedbackQueue
;
115 dispatch_semaphore_t mAssessmentLock
;
116 dispatch_once_t mAssessmentKicked
;
117 int32_t mReferenceCount
;
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
];
124 CFCopyRef
<CFURLRef
> mPath
;
125 CFCopyRef
<CFMutableDictionaryRef
> mResult
;
126 std::vector
<SecAssessmentFeedback
> mFeedback
;
128 std::exception_ptr mExceptionToRethrow
;
130 friend class EvaluationManager
;
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)
139 mXpcActivityName
[0] = 0;
141 mWorkQueue
= dispatch_queue_create("EvaluationTask", 0);
142 mFeedbackQueue
= dispatch_queue_create("EvaluationTaskFeedback", 0);
145 mResult
.take(makeCFMutableDictionary());
149 EvaluationTask::~EvaluationTask()
151 dispatch_release(mFeedbackQueue
);
152 dispatch_release(mWorkQueue
);
153 dispatch_release(mAssessmentLock
);
157 void EvaluationTask::performEvaluation(SecAssessmentFlags flags
, CFDictionaryRef context
)
159 bool performTheEvaluation
= false;
160 bool lowPriority
= flags
& kSecAssessmentFlagLowPriority
;
162 // each evaluation task performs at most a single evaluation
163 if (OSAtomicIncrement32Barrier(&mEvalCount
) == 1)
164 performTheEvaluation
= true;
166 // define a block to run when the assessment has feedback available
167 SecAssessmentFeedback relayFeedback
= ^Boolean(CFStringRef type
, CFDictionaryRef information
) {
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
);
180 this->setUnsharable(); // don't share an expiring evaluation task
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
);
193 // if we haven't already started the evaluation (we're the first interested
195 if (performTheEvaluation
) {
196 dispatch_semaphore_t startLock
= dispatch_semaphore_create(0);
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
);
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
);
210 // Unregister a possibly still scheduled activity, as it lost its point.
211 if (strlen(mXpcActivityName
)) {
212 xpc_activity_unregister(mXpcActivityName
);
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
);
223 // perform the evaluation
225 case kAuthorityExecute
:
226 mPolicyEngine
->evaluateCode(mPath
.get(), kAuthorityExecute
, flags
, contextOverride
.get(), mResult
.get(), true);
228 case kAuthorityInstall
:
229 mPolicyEngine
->evaluateInstall(mPath
.get(), flags
, contextOverride
.get(), mResult
.get());
231 case kAuthorityOpenDoc
:
232 mPolicyEngine
->evaluateDocOpen(mPath
.get(), flags
, contextOverride
.get(), mResult
.get());
235 MacOSError::throwMe(errSecCSInvalidAttributeValues
);
238 mExceptionToRethrow
= std::current_exception();
242 assert(assessmentBlock
!= NULL
);
244 dispatch_async(mWorkQueue
, assessmentBlock
);
245 Block_release(assessmentBlock
);
247 // wait for the assessment to start
248 dispatch_semaphore_wait(startLock
, DISPATCH_TIME_FOREVER
);
249 dispatch_release(startLock
);
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.
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
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());
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);
272 xpc_dictionary_set_string(criteria
, XPC_ACTIVITY_PRIORITY
, XPC_ACTIVITY_PRIORITY_MAINTENANCE
);
273 xpc_dictionary_set_bool(criteria
, XPC_ACTIVITY_ALLOW_BATTERY
, false);
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
);
280 xpc_release(criteria
);
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
292 void EvaluationTask::kick() {
293 dispatch_once(&mAssessmentKicked
, ^{
294 dispatch_semaphore_signal(mAssessmentLock
);
298 void EvaluationTask::waitForCompletion(SecAssessmentFlags flags
, CFMutableDictionaryRef result
)
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
;
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
,
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
);
318 assert(wait_block
!= NULL
);
319 dispatch_sync(mWorkQueue
, wait_block
);
320 Block_release(wait_block
);
328 static Boolean
evaluationTasksAreEqual(const EvaluationTask
*task1
, const EvaluationTask
*task2
)
330 if (!task1
->isSharable() || !task2
->isSharable()) return false;
331 if ((task1
->type() != task2
->type()) ||
332 (cfString(task1
->path()) != cfString(task2
->path())))
341 #pragma mark - EvaluationManager
344 EvaluationManager
*EvaluationManager::globalManager()
346 static EvaluationManager
*singleton
;
347 static dispatch_once_t onceToken
;
348 dispatch_once(&onceToken
, ^{
349 singleton
= new EvaluationManager();
355 EvaluationManager::EvaluationManager()
357 static CFDictionaryValueCallBacks evalTaskValueCallbacks
= kCFTypeDictionaryValueCallBacks
;
358 evalTaskValueCallbacks
.equal
= (CFDictionaryEqualCallBack
)evaluationTasksAreEqual
;
359 evalTaskValueCallbacks
.retain
= NULL
;
360 evalTaskValueCallbacks
.release
= NULL
;
361 mCurrentEvaluations
.take(
362 CFDictionaryCreateMutable(NULL
,
364 &kCFTypeDictionaryKeyCallBacks
,
365 &evalTaskValueCallbacks
));
367 mListLockQueue
= dispatch_queue_create("EvaluationManagerSyncronization", 0);
371 EvaluationManager::~EvaluationManager()
373 dispatch_release(mListLockQueue
);
377 EvaluationTask
*EvaluationManager::evaluationTask(PolicyEngine
*engine
, CFURLRef path
, AuthorityType type
, SecAssessmentFlags flags
, CFDictionaryRef context
, CFMutableDictionaryRef result
)
379 __block EvaluationTask
*evalTask
= NULL
;
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());
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
);
393 evalTask
->mReferenceCount
++;
397 evalTask
->performEvaluation(flags
, context
);
403 void EvaluationManager::finalizeTask(EvaluationTask
*task
, SecAssessmentFlags flags
, CFMutableDictionaryRef result
)
405 task
->waitForCompletion(flags
, result
);
407 std::exception_ptr pendingException
= task
->mExceptionToRethrow
;
411 if (pendingException
) std::rethrow_exception(pendingException
);
415 void EvaluationManager::removeTask(EvaluationTask
*task
)
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());
428 void EvaluationManager::kickTask(CFStringRef key
)
430 dispatch_sync(mListLockQueue
, ^{
431 EvaluationTask
*evalTask
= (EvaluationTask
*)CFDictionaryGetValue(mCurrentEvaluations
.get(),
433 if (evalTask
!= NULL
) {
439 } // end namespace CodeSigning
440 } // end namespace Security