X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/949d2ff02a32bac712f7993d03d527b4925ff882..60c433a9f80a92b51b33f65f1f58883e8fe843be:/Security/libsecurity_codesigning/lib/StaticCode.cpp diff --git a/Security/libsecurity_codesigning/lib/StaticCode.cpp b/Security/libsecurity_codesigning/lib/StaticCode.cpp index f7503184..b768c35e 100644 --- a/Security/libsecurity_codesigning/lib/StaticCode.cpp +++ b/Security/libsecurity_codesigning/lib/StaticCode.cpp @@ -34,7 +34,6 @@ #include "resources.h" #include "detachedrep.h" #include "csdatabase.h" -#include "csutilities.h" #include "dirscanner.h" #include #include @@ -51,6 +50,7 @@ #include #include #include +#include namespace Security { @@ -88,7 +88,8 @@ static inline OSStatus errorForSlot(CodeDirectory::SpecialSlot slot) SecStaticCode::SecStaticCode(DiskRep *rep) : mRep(rep), mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL), - mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mEvalDetails(NULL) + mProgressQueue("com.apple.security.validation-progress", false, DISPATCH_QUEUE_PRIORITY_DEFAULT), + mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mLimitedAsync(NULL), mEvalDetails(NULL) { CODESIGN_STATIC_CREATE(this, rep); CFRef codeDirectory = rep->codeDirectory(); @@ -104,12 +105,20 @@ SecStaticCode::SecStaticCode(DiskRep *rep) SecStaticCode::~SecStaticCode() throw() try { ::free(const_cast(mDesignatedReq)); - if (mResourcesValidContext) - delete mResourcesValidContext; + delete mResourcesValidContext; + delete mLimitedAsync; } catch (...) { return; } +// +// Initialize a nested SecStaticCode object from its parent +// +void SecStaticCode::initializeFromParent(const SecStaticCode& parent) { + setMonitor(parent.monitor()); + if (parent.mLimitedAsync) + mLimitedAsync = new LimitedAsync(*parent.mLimitedAsync); +} // // CF-level comparison of SecStaticCode objects compares CodeDirectory hashes if signed, @@ -148,10 +157,9 @@ CFTypeRef SecStaticCode::reportEvent(CFStringRef stage, CFDictionaryRef info) void SecStaticCode::prepareProgress(unsigned int workload) { - { - StLock _(mCancelLock); + dispatch_sync(mProgressQueue, ^{ mCancelPending = false; // not cancelled - } + }); if (mValidationFlags & kSecCSReportProgress) { mCurrentWork = 0; // nothing done yet mTotalWork = workload; // totally fake - we don't know how many files we'll get to chew @@ -161,15 +169,17 @@ void SecStaticCode::prepareProgress(unsigned int workload) void SecStaticCode::reportProgress(unsigned amount /* = 1 */) { if (mMonitor && (mValidationFlags & kSecCSReportProgress)) { - { - // if cancellation is pending, abort now - StLock _(mCancelLock); - if (mCancelPending) - MacOSError::throwMe(errSecCSCancelled); - } // update progress and report - mCurrentWork += amount; - mMonitor(this->handle(false), CFSTR("progress"), CFTemp("{current=%d,total=%d}", mCurrentWork, mTotalWork)); + __block bool cancel = false; + dispatch_sync(mProgressQueue, ^{ + if (mCancelPending) + cancel = true; + mCurrentWork += amount; + mMonitor(this->handle(false), CFSTR("progress"), CFTemp("{current=%d,total=%d}", mCurrentWork, mTotalWork)); + }); + // if cancellation is pending, abort now + if (cancel) + MacOSError::throwMe(errSecCSCancelled); } } @@ -204,10 +214,11 @@ void SecStaticCode::setValidationModifiers(CFDictionaryRef conditions) // void SecStaticCode::cancelValidation() { - StLock _(mCancelLock); if (!(mValidationFlags & kSecCSReportProgress)) // not using progress reporting; cancel won't make it through MacOSError::throwMe(errSecCSInvalidFlags); - mCancelPending = true; + dispatch_sync(mProgressQueue, ^{ + mCancelPending = true; + }); } @@ -784,7 +795,12 @@ void SecStaticCode::validateResources(SecCSFlags flags) if (!(flags & kSecCSCheckNestedCode) || mResourcesDeep) // was deep or need no deep scan doit = false; } + if (doit) { + if (mLimitedAsync == NULL) { + mLimitedAsync = new LimitedAsync(diskRep()->fd().mediumType() == kIOPropertyMediumTypeSolidStateKey); + } + try { // sanity first CFDictionaryRef sealedResources = resourceDictionary(); @@ -804,11 +820,9 @@ void SecStaticCode::validateResources(SecCSFlags flags) (char*)this->mainExecutablePath().c_str(), 0); // scan through the resources on disk, checking each against the resourceDirectory - if (mValidationFlags & kSecCSFullReport) - mResourcesValidContext = new CollectingContext(*this); // collect all failures in here - else - mResourcesValidContext = new ValidationContext(*this); // simple bug-out on first error + mResourcesValidContext = new CollectingContext(*this); // collect all failures in here + // use V2 resource seal if available, otherwise fall back to V1 CFDictionaryRef rules; CFDictionaryRef files; uint32_t version; @@ -823,6 +837,7 @@ void SecStaticCode::validateResources(SecCSFlags flags) } if (!rules || !files) MacOSError::throwMe(errSecCSResourcesInvalid); + // check for weak resource rules bool strict = flags & kSecCSStrictValidate; if (strict) { @@ -833,16 +848,29 @@ void SecStaticCode::validateResources(SecCSFlags flags) if (mTolerateErrors.find(errSecCSWeakResourceEnvelope) == mTolerateErrors.end()) MacOSError::throwMe(errSecCSWeakResourceEnvelope); } + + Dispatch::Group group; + Dispatch::Group &groupRef = group; // (into block) + + // scan through the resources on disk, checking each against the resourceDirectory __block CFRef resourceMap = makeCFMutableDictionary(files); string base = cfString(this->resourceBase()); ResourceBuilder resources(base, base, rules, codeDirectory()->hashType, strict, mTolerateErrors); diskRep()->adjustResources(resources); - resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const char *relpath, ResourceBuilder::Rule *rule) { - validateResource(files, relpath, ent->fts_info == FTS_SL, *mResourcesValidContext, flags, version); - reportProgress(); + + resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const string relpath, ResourceBuilder::Rule *rule) { CFDictionaryRemoveValue(resourceMap, CFTempString(relpath)); + bool isSymlink = (ent->fts_info == FTS_SL); + + void (^validate)() = ^{ + validateResource(files, relpath, isSymlink, *mResourcesValidContext, flags, version); + reportProgress(); + }; + + mLimitedAsync->perform(groupRef, validate); }); - + group.wait(); // wait until all async resources have been validated as well + unsigned leftovers = unsigned(CFDictionaryGetCount(resourceMap)); if (leftovers > 0) { secdebug("staticCode", "%d sealed resource(s) not found in code", int(leftovers)); @@ -917,6 +945,7 @@ bool SecStaticCode::hasWeakResourceRules(CFDictionaryRef rulesDict, uint32_t ver string catchAllRule = (version == 1) ? "^Resources/" : "^.*"; __block bool coversAll = false; __block bool forbiddenOmission = false; + CFArrayRef allowedRef = allowed.get(); // (into block) CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid); rules.apply(^(CFStringRef key, CFTypeRef value) { string pattern = cfString(key, errSecCSResourceRulesInvalid); @@ -925,7 +954,7 @@ bool SecStaticCode::hasWeakResourceRules(CFDictionaryRef rulesDict, uint32_t ver return; } if (isOmitRule(value)) - forbiddenOmission |= !CFArrayContainsValue(allowed, range, key); + forbiddenOmission |= !CFArrayContainsValue(allowedRef, range, key); }); return !coversAll || forbiddenOmission; @@ -1087,6 +1116,8 @@ void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool is if (cfString(seal.link()) != target) ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); } else if (seal.hash()) { // genuine file + if (isSymlink) + return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type AutoFileDesc fd(cfString(fullpath), O_RDONLY, FileDesc::modeMissingOk); // open optional file if (fd) { MakeHash hasher(this->codeDirectory()); @@ -1124,7 +1155,7 @@ void SecStaticCode::validateNestedCode(CFURLRef path, const ResourceSeal &seal, if (!(flags & kSecCSCheckNestedCode)) flags |= kSecCSBasicValidateOnly; SecPointer code = new SecStaticCode(DiskRep::bestGuess(cfString(path))); - code->setMonitor(this->monitor()); + code->initializeFromParent(*this); code->staticValidate(flags, SecRequirement::required(req)); if (isFramework && (flags & kSecCSStrictValidate)) @@ -1192,7 +1223,7 @@ void SecStaticCode::validateOtherVersions(CFURLRef path, SecCSFlags flags, SecRe continue; SecPointer frameworkVersion = new SecStaticCode(DiskRep::bestGuess(real_full_path)); - frameworkVersion->setMonitor(this->monitor()); + frameworkVersion->initializeFromParent(*this); frameworkVersion->staticValidate(flags, SecRequirement::required(req)); } } @@ -1488,6 +1519,7 @@ void SecStaticCode::ValidationContext::reportProblem(OSStatus rc, CFStringRef ty void SecStaticCode::CollectingContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value) { + StLock _(mLock); if (mStatus == errSecSuccess) mStatus = rc; // record first failure for eventual error return if (type) { @@ -1529,12 +1561,20 @@ void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req) setValidationFlags(flags); // initialize progress/cancellation state + if (flags & kSecCSReportProgress) prepareProgress(estimateResourceWorkload() + 2); // +1 head, +1 tail // core components: once per architecture (if any) this->staticValidateCore(flags, req); if (flags & kSecCSCheckAllArchitectures) handleOtherArchitectures(^(SecStaticCode* subcode) { + if (flags & kSecCSCheckGatekeeperArchitectures) { + Universal *fat = subcode->diskRep()->mainExecutableImage(); + assert(fat && fat->narrowed()); // handleOtherArchitectures gave us a focused architecture slice + Architecture arch = fat->bestNativeArch(); // actually, the ONLY one + if ((arch.cpuType() & ~CPU_ARCH_MASK) == CPU_TYPE_POWERPC) + return; // irrelevant to Gatekeeper + } subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) architecture subcode->staticValidateCore(flags, req); }); @@ -1549,7 +1589,7 @@ void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req) // perform strict validation if desired if (flags & kSecCSStrictValidate) - mRep->strictValidate(mTolerateErrors); + mRep->strictValidate(codeDirectory(), mTolerateErrors); reportProgress(); // allow monitor intervention