+++ /dev/null
-/*
- * Copyright (c) 2011-2014 Apple Inc. All Rights Reserved.
- *
- * @APPLE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_LICENSE_HEADER_END@
- */
-
-#include "evaluationmanager.h"
-#include "policyengine.h"
-#include <security_utilities/cfmunge.h>
-#include <xpc/xpc.h>
-#include <exception>
-#include <vector>
-
-
-
-
-namespace Security {
-namespace CodeSigning {
-
-
-
-
-#pragma mark - EvaluationTask
-
-
-//
-// An evaluation task object manages the assessment - either directly, or in the
-// form of waiting for another evaluation task to finish an assessment on the
-// same target.
-//
-class EvaluationTask
-{
-public:
- CFURLRef path() const { return mPath.get(); }
- AuthorityType type() const { return mType; }
- bool isSharable() const { return mSharable; }
- void setUnsharable() { mSharable = false; }
-
-private:
- EvaluationTask(PolicyEngine *engine, CFURLRef path, AuthorityType type);
- virtual ~EvaluationTask();
- void performEvaluation(SecAssessmentFlags flags, CFDictionaryRef context);
- void waitForCompletion(SecAssessmentFlags flags, CFMutableDictionaryRef result);
-
- PolicyEngine *mPolicyEngine;
- AuthorityType mType;
- dispatch_queue_t mWorkQueue;
- dispatch_queue_t mFeedbackQueue;
- dispatch_semaphore_t mAssessmentLock;
- __block dispatch_once_t mAssessmentKicked;
- int32_t mReferenceCount;
- int32_t mEvalCount;
-// This whole thing is a pre-existing crutch and must be fixed soon.
-#define UNOFFICIAL_MAX_XPC_ID_LENGTH 127
- char mXpcActivityName[UNOFFICIAL_MAX_XPC_ID_LENGTH];
- bool mSharable;
-
- CFCopyRef<CFURLRef> mPath;
- CFCopyRef<CFMutableDictionaryRef> mResult;
- std::vector<SecAssessmentFeedback> mFeedback;
-
- std::exception_ptr mExceptionToRethrow;
-
- friend class EvaluationManager;
-};
-
-
-EvaluationTask::EvaluationTask(PolicyEngine *engine, CFURLRef path, AuthorityType type) :
- mPolicyEngine(engine), mType(type), mAssessmentLock(dispatch_semaphore_create(0)),
- mAssessmentKicked(0), mReferenceCount(0), mEvalCount(0), mSharable(true),
- mExceptionToRethrow(0)
-{
- mXpcActivityName[0] = 0;
-
- mWorkQueue = dispatch_queue_create("EvaluationTask", 0);
- mFeedbackQueue = dispatch_queue_create("EvaluationTaskFeedback", 0);
-
- mPath = path;
- mResult.take(makeCFMutableDictionary());
-}
-
-
-EvaluationTask::~EvaluationTask()
-{
- dispatch_release(mFeedbackQueue);
- dispatch_release(mWorkQueue);
- dispatch_release(mAssessmentLock);
-}
-
-
-void EvaluationTask::performEvaluation(SecAssessmentFlags flags, CFDictionaryRef context)
-{
- bool performTheEvaluation = false;
- bool lowPriority = flags & kSecAssessmentFlagLowPriority;
-
- // each evaluation task performs at most a single evaluation
- if (OSAtomicIncrement32Barrier(&mEvalCount) == 1)
- performTheEvaluation = true;
-
- // define a block to run when the assessment has feedback available
- SecAssessmentFeedback relayFeedback = ^Boolean(CFStringRef type, CFDictionaryRef information) {
-
- __block Boolean proceed = true;
- dispatch_sync(mFeedbackQueue, ^{
- if (mFeedback.size() > 0) {
- proceed = false; // we need at least one interested party to proceed
- // forward the feedback to all registered listeners
- for (int i = 0; i < mFeedback.size(); ++i) {
- proceed |= mFeedback[i](type, information);
- }
- }
- });
- if (!proceed)
- this->setUnsharable(); // don't share an expiring evaluation task
- return proceed;
- };
-
-
- // if the calling context has a feedback block, register it to listen to
- // our feedback relay
- dispatch_sync(mFeedbackQueue, ^{
- SecAssessmentFeedback feedback = (SecAssessmentFeedback)CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback);
- if (feedback && CFGetTypeID(feedback) == CFGetTypeID(relayFeedback))
- mFeedback.push_back(feedback);
- });
-
- // if we haven't already started the evaluation (we're the first interested
- // party), do it now
- if (performTheEvaluation) {
- dispatch_semaphore_t startLock = dispatch_semaphore_create(0);
-
- // create the assessment block
- dispatch_async(mWorkQueue, dispatch_block_create_with_qos_class(DISPATCH_BLOCK_ENFORCE_QOS_CLASS, QOS_CLASS_UTILITY, 0, ^{
- // signal that the assessment is ready to start
- dispatch_semaphore_signal(startLock);
-
- // wait until we're permitted to start the assessment. if we're in low
- // priority mode, this will not happen until we're on AC power. if not
- // in low priority mode, we're either already free to perform the
- // assessment or we will be quite soon
- dispatch_semaphore_wait(mAssessmentLock, DISPATCH_TIME_FOREVER);
-
- // Unregister a possibly still scheduled activity, as it lost its point.
- if (strlen(mXpcActivityName)) {
- xpc_activity_unregister(mXpcActivityName);
- }
-
- // copy the original context into our own mutable dictionary and replace
- // (or assign) the feedback entry within it to our multi-receiver
- // feedback relay block
- CFRef<CFMutableDictionaryRef> contextOverride = makeCFMutableDictionary(context);
- CFDictionaryRemoveValue(contextOverride.get(), kSecAssessmentContextKeyFeedback);
- CFDictionaryAddValue(contextOverride.get(), kSecAssessmentContextKeyFeedback, relayFeedback);
-
- try {
- // perform the evaluation
- switch (mType) {
- case kAuthorityExecute:
- mPolicyEngine->evaluateCode(mPath.get(), kAuthorityExecute, flags, contextOverride.get(), mResult.get(), true);
- break;
- case kAuthorityInstall:
- mPolicyEngine->evaluateInstall(mPath.get(), flags, contextOverride.get(), mResult.get());
- break;
- case kAuthorityOpenDoc:
- mPolicyEngine->evaluateDocOpen(mPath.get(), flags, contextOverride.get(), mResult.get());
- break;
- default:
- MacOSError::throwMe(errSecCSInvalidAttributeValues);
- break;
- }
- } catch(...) {
- mExceptionToRethrow = std::current_exception();
- }
-
- }));
-
- // wait for the assessment to start
- dispatch_semaphore_wait(startLock, DISPATCH_TIME_FOREVER);
- dispatch_release(startLock);
-
- if (lowPriority) {
- // This whole thing is a crutch and should be handled differently.
- // Maybe by having just one activity that just kicks off all remaining
- // background assessments, CTS determines that it's a good time.
-
- // reduce the bundle path name to just the app component and generate an
- // xpc_activity identifier from it. this identifier should be smaller than
- // 128 characters due to rdar://problem/20094806
- string path = cfString(mPath);
- size_t bundleNamePosition = path.rfind('/');
- const char *bundleName = "/default";
- if (bundleNamePosition != string::npos)
- bundleName = path.c_str() + bundleNamePosition;
- snprintf(mXpcActivityName, UNOFFICIAL_MAX_XPC_ID_LENGTH, "com.apple.security.assess%s", bundleName);
-
- // schedule the assessment to be permitted to run (beyond start) -- this
- // will either happen once we're no longer on battery power, or
- // immediately, based on the flag value of kSecAssessmentFlagLowPriority
- xpc_object_t criteria = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_REPEATING, false);
- xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_DELAY, 0);
- xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_GRACE_PERIOD, 0);
-
- xpc_dictionary_set_string(criteria, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_MAINTENANCE);
- xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_ALLOW_BATTERY, false);
-
- xpc_activity_register(mXpcActivityName, criteria, ^(xpc_activity_t activity) {
- dispatch_once(&mAssessmentKicked, ^{
- dispatch_semaphore_signal(mAssessmentLock);
- });
- });
- xpc_release(criteria);
- }
- }
-
- // If this is a foreground assessment to begin with, or if an assessment
- // with an existing task has been requested in the foreground, kick it
- // immediately.
- if (!lowPriority) {
- dispatch_once(&mAssessmentKicked, ^{
- dispatch_semaphore_signal(mAssessmentLock);
- });
- }
-}
-
-
-
-void EvaluationTask::waitForCompletion(SecAssessmentFlags flags, CFMutableDictionaryRef result)
-{
- // if the caller didn't request low priority we will elevate the dispatch
- // queue priority via our wait block
- dispatch_qos_class_t qos_class = QOS_CLASS_USER_INITIATED;
- if (flags & kSecAssessmentFlagLowPriority)
- qos_class = QOS_CLASS_UTILITY;
-
- // wait for the assessment to complete; our wait block will queue up behind
- // the assessment and the copy its results
- dispatch_sync(mWorkQueue, dispatch_block_create_with_qos_class (DISPATCH_BLOCK_ENFORCE_QOS_CLASS, qos_class, 0, ^{
- // copy the class result back to the caller
- cfDictionaryApplyBlock(mResult.get(), ^(const void *key, const void *value){
- CFDictionaryAddValue(result, key, value);
- });
- }));
-
- if (mExceptionToRethrow) std::rethrow_exception(mExceptionToRethrow);
-}
-
-
-
-#pragma mark -
-
-
-static Boolean evaluationTasksAreEqual(const EvaluationTask *task1, const EvaluationTask *task2)
-{
- if (!task1->isSharable() || !task2->isSharable()) return false;
- if ((task1->type() != task2->type()) ||
- (cfString(task1->path()) != cfString(task2->path())))
- return false;
-
- return true;
-}
-
-
-
-
-#pragma mark - EvaluationManager
-
-
-EvaluationManager *EvaluationManager::globalManager()
-{
- static EvaluationManager *singleton;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- singleton = new EvaluationManager();
- });
- return singleton;
-}
-
-
-EvaluationManager::EvaluationManager()
-{
- static CFDictionaryValueCallBacks evalTaskValueCallbacks = kCFTypeDictionaryValueCallBacks;
- evalTaskValueCallbacks.equal = (CFDictionaryEqualCallBack)evaluationTasksAreEqual;
- evalTaskValueCallbacks.retain = NULL;
- evalTaskValueCallbacks.release = NULL;
- mCurrentEvaluations.take(
- CFDictionaryCreateMutable(NULL,
- 0,
- &kCFTypeDictionaryKeyCallBacks,
- &evalTaskValueCallbacks));
-
- mListLockQueue = dispatch_queue_create("EvaluationManagerSyncronization", 0);
-}
-
-
-EvaluationManager::~EvaluationManager()
-{
- dispatch_release(mListLockQueue);
-}
-
-
-EvaluationTask *EvaluationManager::evaluationTask(PolicyEngine *engine, CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
-{
- __block EvaluationTask *evalTask = NULL;
-
- dispatch_sync(mListLockQueue, ^{
- // is path already being evaluated?
- if (!(flags & kSecAssessmentFlagIgnoreActiveAssessments))
- evalTask = (EvaluationTask *)CFDictionaryGetValue(mCurrentEvaluations.get(), path);
- if (!evalTask) {
- // create a new task for the evaluation
- evalTask = new EvaluationTask(engine, path, type);
- if (flags & kSecAssessmentFlagIgnoreActiveAssessments)
- evalTask->setUnsharable();
- CFDictionaryAddValue(mCurrentEvaluations.get(), path, evalTask);
- }
- evalTask->mReferenceCount++;
- });
-
- if (evalTask)
- evalTask->performEvaluation(flags, context);
-
- return evalTask;
-}
-
-
-void EvaluationManager::waitForCompletion(EvaluationTask *task, SecAssessmentFlags flags, CFMutableDictionaryRef result)
-{
- task->waitForCompletion(flags, result);
-}
-
-
-void EvaluationManager::removeTask(EvaluationTask *task)
-{
- dispatch_sync(mListLockQueue, ^{
- // are we done with this evaluation task?
- if (--task->mReferenceCount == 0) {
- // yes -- remove it from our list and delete the object
- CFDictionaryRemoveValue(mCurrentEvaluations.get(), task->path());
- delete task;
- }
- });
-}
-
-
-
-} // end namespace CodeSigning
-} // end namespace Security
-