#include "resources.h"
#include "detachedrep.h"
#include "csdatabase.h"
-#include "csutilities.h"
#include "dirscanner.h"
#include <CoreFoundation/CFURLAccess.h>
#include <Security/SecPolicyPriv.h>
#include <security_utilities/logging.h>
#include <dirent.h>
#include <sstream>
+#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
namespace Security {
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<CFDataRef> codeDirectory = rep->codeDirectory();
SecStaticCode::~SecStaticCode() throw()
try {
::free(const_cast<Requirement *>(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,
void SecStaticCode::prepareProgress(unsigned int workload)
{
- {
- StLock<Mutex> _(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
void SecStaticCode::reportProgress(unsigned amount /* = 1 */)
{
if (mMonitor && (mValidationFlags & kSecCSReportProgress)) {
- {
- // if cancellation is pending, abort now
- StLock<Mutex> _(mCancelLock);
- if (mCancelPending)
- MacOSError::throwMe(errSecCSCancelled);
- }
// update progress and report
- mCurrentWork += amount;
- mMonitor(this->handle(false), CFSTR("progress"), CFTemp<CFDictionaryRef>("{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<CFDictionaryRef>("{current=%d,total=%d}", mCurrentWork, mTotalWork));
+ });
+ // if cancellation is pending, abort now
+ if (cancel)
+ MacOSError::throwMe(errSecCSCancelled);
}
}
//
void SecStaticCode::cancelValidation()
{
- StLock<Mutex> _(mCancelLock);
if (!(mValidationFlags & kSecCSReportProgress)) // not using progress reporting; cancel won't make it through
MacOSError::throwMe(errSecCSInvalidFlags);
- mCancelPending = true;
+ dispatch_sync(mProgressQueue, ^{
+ mCancelPending = true;
+ });
}
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();
(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;
}
if (!rules || !files)
MacOSError::throwMe(errSecCSResourcesInvalid);
+
// check for weak resource rules
bool strict = flags & kSecCSStrictValidate;
if (strict) {
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<CFMutableDictionaryRef> 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));
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);
return;
}
if (isOmitRule(value))
- forbiddenOmission |= !CFArrayContainsValue(allowed, range, key);
+ forbiddenOmission |= !CFArrayContainsValue(allowedRef, range, key);
});
return !coversAll || forbiddenOmission;
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<CodeDirectory> hasher(this->codeDirectory());
if (!(flags & kSecCSCheckNestedCode))
flags |= kSecCSBasicValidateOnly;
SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(cfString(path)));
- code->setMonitor(this->monitor());
+ code->initializeFromParent(*this);
code->staticValidate(flags, SecRequirement::required(req));
if (isFramework && (flags & kSecCSStrictValidate))
continue;
SecPointer<SecStaticCode> frameworkVersion = new SecStaticCode(DiskRep::bestGuess(real_full_path));
- frameworkVersion->setMonitor(this->monitor());
+ frameworkVersion->initializeFromParent(*this);
frameworkVersion->staticValidate(flags, SecRequirement::required(req));
}
}
void SecStaticCode::CollectingContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value)
{
+ StLock<Mutex> _(mLock);
if (mStatus == errSecSuccess)
mStatus = rc; // record first failure for eventual error return
if (type) {
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);
});
// perform strict validation if desired
if (flags & kSecCSStrictValidate)
- mRep->strictValidate(mTolerateErrors);
+ mRep->strictValidate(codeDirectory(), mTolerateErrors);
reportProgress();
// allow monitor intervention