]> git.saurik.com Git - apple/security.git/blobdiff - OSX/include/security_codesigning/evaluationmanager.cpp
Security-57336.1.9.tar.gz
[apple/security.git] / 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 (file)
index 0000000..d64d6e1
--- /dev/null
@@ -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 <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
+