X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/include/security_codesigning/evaluationmanager.cpp diff --git a/OSX/include/security_codesigning/evaluationmanager.cpp b/OSX/include/security_codesigning/evaluationmanager.cpp new file mode 100644 index 00000000..d64d6e1a --- /dev/null +++ b/OSX/include/security_codesigning/evaluationmanager.cpp @@ -0,0 +1,366 @@ +/* + * 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 +#include +#include +#include + + + + +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 mPath; + CFCopyRef mResult; + std::vector 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 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 +