/ OSX / libsecurity_codesigning / lib / evaluationmanager.cpp
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