// // Executable code. // Read from disk, evaluate properly, cache as indicated. // void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsigned) { // not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege FileQuarantine qtn(cfString(path).c_str()); if (qtn.flag(QTN_FLAG_HARD)) MacOSError::throwMe(errSecCSFileHardQuarantined); CFCopyRef<SecStaticCodeRef> code; MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref())); SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures; if (!(flags & kSecAssessmentFlagAllowWeak)) validationFlags |= kSecCSStrictValidate; adjustValidation(code); // deal with a very special case (broken 10.6/10.7 Applet bundles) OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, NULL); if (rc == errSecCSSignatureFailed) { if (!codeInvalidityExceptions(code, result)) { // invalidly signed, no exceptions -> error if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED()) SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false); MacOSError::throwMe(rc); } // recognized exception - treat as unsigned if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED()) SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true); rc = errSecCSUnsigned; } // ad-hoc sign unsigned code if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) { if (temporarySigning(code, type, path, 0)) { rc = errSecSuccess; // clear unsigned; we are now well-signed validationFlags |= kSecCSBasicValidateOnly; // no need to re-validate deep contents } } // prepare for deep traversal of (hopefully) good signatures SecAssessmentFeedback feedback = SecAssessmentFeedback(CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback)); MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) { string stage = cfString(cfStage); if (stage == "prepared") { if (!CFEqual(item, code)) // genuine nested (not top) code adjustValidation(item); } else if (stage == "progress") { if (feedback && CFEqual(item, code)) { // top level progress bool proceed = feedback(kSecAssessmentFeedbackProgress, info); if (!proceed) SecStaticCodeCancelValidation(code, kSecCSDefaultFlags); } } else if (stage == "validated") { SecStaticCodeSetCallback(item, kSecCSDefaultFlags, NULL, NULL); // clear callback to avoid unwanted recursion evaluateCodeItem(item, path, type, flags, item != code, result); if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict)) if (CFEqual(verdict, kCFBooleanFalse)) return makeCFNumber(OSStatus(errSecCSVetoed)); // (signal nested-code policy failure, picked up below) } return NULL; }));
//! \verbatim //! Quaternion() //! Quaternion( Radian rotation, Vector3 axis ) //! Quaternion( Real w, Real x, Real y, Real z ) //! Quaternion([Real w, Real x, Real y, Real z]) //! Quaternion({Real w, Real x, Real y, Real z}) //! \endverbatim void Quaternion::create( const FunctionCallbackInfo<v8::Value>& args ) { HandleScope handleScope( args.GetIsolate() ); if ( !args.IsConstructCall() ) { args.GetIsolate()->ThrowException( Util::allocString( "Function called as non-constructor" ) ); return; } Ogre::Quaternion qtn( Ogre::Quaternion::IDENTITY ); if ( args.Length() == 4 ) { qtn.w = args[0]->NumberValue(); qtn.x = args[1]->NumberValue(); qtn.y = args[2]->NumberValue(); qtn.z = args[3]->NumberValue(); } else if ( args.Length() == 2 ) { Vector3* axis = Util::extractVector3( 1, args ); qtn.FromAngleAxis( Ogre::Radian( args[0]->NumberValue() ), *axis ); } else if ( args.Length() == 1 ) { if ( args[0]->IsArray() ) { v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast( args[0] ); if ( arr->Length() == 4 ) { qtn.w = arr->Get( 0 )->NumberValue(); qtn.x = arr->Get( 1 )->NumberValue(); qtn.y = arr->Get( 2 )->NumberValue(); qtn.z = arr->Get( 3 )->NumberValue(); } } else if ( args[0]->IsObject() ) { v8::Local<v8::Value> val = args[0]->ToObject()->Get( Util::allocString( "w" ) ); if ( val->IsNumber() ) qtn.w = val->NumberValue(); val = args[0]->ToObject()->Get( Util::allocString( "x" ) ); if ( val->IsNumber() ) qtn.x = val->NumberValue(); val = args[0]->ToObject()->Get( Util::allocString( "y" ) ); if ( val->IsNumber() ) qtn.y = val->NumberValue(); val = args[0]->ToObject()->Get( Util::allocString( "z" ) ); if ( val->IsNumber() ) qtn.z = val->NumberValue(); } } Quaternion* object = new Quaternion( qtn ); object->wrap( args.This() ); args.GetReturnValue().Set( args.This() ); }
CFDictionaryRef PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) { if (type == kAuthorityOpenDoc) { // handle document-open differently: use quarantine flags for whitelisting authorizeUpdate(flags, context); if (!target || CFGetTypeID(target) != CFURLGetTypeID()) MacOSError::throwMe(errSecCSInvalidObjectRef); std::string spath = cfString(CFURLRef(target)).c_str(); FileQuarantine qtn(spath.c_str()); qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK); qtn.applyTo(spath.c_str()); return NULL; } return manipulateRules("DELETE FROM authority", target, type, flags, context); }
// // LaunchServices-layer document open. // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment. // void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) { if (context) { if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) { FileQuarantine qtn(cfString(path).c_str()); if (CFEqual(riskCategory, kLSRiskCategorySafe) || CFEqual(riskCategory, kLSRiskCategoryNeutral) || CFEqual(riskCategory, kLSRiskCategoryUnknown) || CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) { cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict); addAuthority(result, "_XProtect"); } else if (qtn.flag(QTN_FLAG_HARD)) { MacOSError::throwMe(errSecCSFileHardQuarantined); } else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) { cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict); addAuthority(result, "Prior Assessment"); } else if (!overrideAssessment()) { // no need to do more work if we're off try { evaluateCode(path, kAuthorityExecute, flags, context, result, false); } catch (...) { // some documents can't be code signed, so this may be quite benign } if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == NULL) { // no code signature to help us out cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); addAuthority(result, "_XProtect"); } } addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory); return; } } // insufficient information from LS - deny by default cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); addAuthority(result, "Insufficient Context"); }
// // Add a rule to the policy database // CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) { // default type to execution if (type == kAuthorityInvalid) type = kAuthorityExecute; authorizeUpdate(flags, context); CFDictionary ctx(context, errSecCSInvalidAttributeValues); CFCopyRef<CFTypeRef> target = inTarget; CFRef<CFDataRef> bookmark = NULL; std::string filter_unsigned; switch (type) { case kAuthorityExecute: normalizeTarget(target, ctx, &filter_unsigned); // bookmarks are untrusted and just a hint to callers bookmark = ctx.get<CFDataRef>(kSecAssessmentRuleKeyBookmark); break; case kAuthorityInstall: if (inTarget && CFGetTypeID(inTarget) == CFURLGetTypeID()) { // no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("virtual install")); } break; case kAuthorityOpenDoc: // handle document-open differently: use quarantine flags for whitelisting if (!target || CFGetTypeID(target) != CFURLGetTypeID()) // can only "add" file paths MacOSError::throwMe(errSecCSInvalidObjectRef); try { std::string spath = cfString(target.as<CFURLRef>()); FileQuarantine qtn(spath.c_str()); qtn.setFlag(QTN_FLAG_ASSESSMENT_OK); qtn.applyTo(spath.c_str()); } catch (const CommonError &error) { // could not set quarantine flag - report qualified success return cfmake<CFDictionaryRef>("{%O=%O,'assessment:error'=%d}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("error setting quarantine"), error.osStatus()); } catch (...) { return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("unable to set quarantine")); } return NULL; } // if we now have anything else, we're busted if (!target || CFGetTypeID(target) != SecRequirementGetTypeID()) MacOSError::throwMe(errSecCSInvalidObjectRef); double priority = 0; string label; bool allow = true; double expires = never; string remarks; if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority)) CFNumberGetValue(pri, kCFNumberDoubleType, &priority); if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel)) label = cfString(lab); if (CFDateRef time = ctx.get<CFDateRef>(kSecAssessmentUpdateKeyExpires)) // we're using Julian dates here; convert from CFDate expires = dateToJulian(time); if (CFBooleanRef allowing = ctx.get<CFBooleanRef>(kSecAssessmentUpdateKeyAllow)) allow = allowing == kCFBooleanTrue; if (CFStringRef rem = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyRemarks)) remarks = cfString(rem); CFRef<CFStringRef> requirementText; MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref())); SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule"); SQLite::Statement insert(*this, "INSERT INTO authority (type, allow, requirement, priority, label, expires, filter_unsigned, remarks)" " VALUES (:type, :allow, :requirement, :priority, :label, :expires, :filter_unsigned, :remarks);"); insert.bind(":type").integer(type); insert.bind(":allow").integer(allow); insert.bind(":requirement") = requirementText.get(); insert.bind(":priority") = priority; if (!label.empty()) insert.bind(":label") = label; insert.bind(":expires") = expires; insert.bind(":filter_unsigned") = filter_unsigned.empty() ? NULL : filter_unsigned.c_str(); if (!remarks.empty()) insert.bind(":remarks") = remarks; insert.execute(); SQLite::int64 newRow = this->lastInsert(); if (bookmark) { SQLite::Statement bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)"); bi.bind(":bookmark") = CFDataRef(bookmark); bi.bind(":authority").integer(newRow); bi.execute(); } this->purgeObjects(priority); xact.commit(); notify_post(kNotifySecAssessmentUpdate); return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyRow, newRow); }
// // Executable code. // Read from disk, evaluate properly, cache as indicated. The whole thing, so far. // void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsignedCode /* = true */) { FileQuarantine qtn(cfString(path).c_str()); if (qtn.flag(QTN_FLAG_HARD)) MacOSError::throwMe(errSecCSFileHardQuarantined); CFRef<SecStaticCodeRef> code; MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref())); OSStatus rc = noErr; // last validation error const SecCSFlags validationFlags = kSecCSEnforceRevocationChecks; WhitelistPrescreen whitelistScreen(code); // pre-screening filter for whitelist pre-screening (only) SQLite::Statement query(*this, "SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority" " WHERE type = :type" " ORDER BY priority DESC;"); query.bind(":type").integer(type); SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID std::string latentLabel; // ... and associated label, if any while (query.nextRow()) { bool allow = int(query[0]); const char *reqString = query[1]; SQLite3::int64 id = query[2]; const char *label = query[3]; double expires = query[4]; sqlite3_int64 ruleFlags = query[5]; SQLite3::int64 disabled = query[6]; const char *filter = query[7]; const char *remarks = query[8]; CFRef<SecRequirementRef> requirement; MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref())); rc = SecStaticCodeCheckValidity(code, validationFlags, requirement); // ad-hoc sign unsigned code, skip of Gatekeeper is off or the rule is disabled; but always do it for whitelist recording if (rc == errSecCSUnsigned && handleUnsignedCode && (!(disabled || overrideAssessment()) || SYSPOLICY_RECORDER_MODE_ENABLED())) { if (!SYSPOLICY_RECORDER_MODE_ENABLED()) { // apply whitelist pre-screening to speed things up for non-matches if (ruleFlags & kAuthorityFlagDefault) // can't ever match standard rules with unsigned code continue; if (whitelistScreen.reject(filter, remarks)) // apply whitelist pre-filter continue; } try { // ad-hoc sign the code and attach the signature CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0); CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity); CFRef<SecCodeSignerRef> signer; MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref())); MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags)); // if we're in GKE recording mode, save that signature and report its location if (SYSPOLICY_RECORDER_MODE_ENABLED()) { int status = recorder_code_unable; // ephemeral signature (not recorded) if (geteuid() == 0) { CFRef<CFUUIDRef> uuid = CFUUIDCreate(NULL); std::string sigfile = RECORDER_DIR + cfStringRelease(CFUUIDCreateString(NULL, uuid)) + ".tsig"; try { UnixPlusPlus::AutoFileDesc fd(sigfile, O_WRONLY | O_CREAT); fd.write(CFDataGetBytePtr(signature), CFDataGetLength(signature)); status = recorder_code_adhoc; // recorded signature SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path).c_str(), type, sigfile.c_str()); } catch (...) { } } // now report the D probe itself CFRef<CFDictionaryRef> info; MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); SYSPOLICY_RECORDER_MODE(cfString(path).c_str(), type, "", cdhash ? CFDataGetBytePtr(cdhash) : NULL, status); } // rerun the validation to update state rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, requirement); } catch (...) { } } switch (rc) { case noErr: // well signed and satisfies requirement... break; // ... continue below case errSecCSSignatureFailed: if (!codeInvalidityExceptions(code, result)) { if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED()) SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false); MacOSError::throwMe(rc); } if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED()) SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true); // treat as unsigned to fix problems in the field case errSecCSUnsigned: if (handleUnsignedCode) { cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); addAuthority(result, "no usable signature"); } return; case errSecCSReqFailed: // requirement missed, but otherwise okay continue; default: // broken in some way; all tests will fail like this so bail out MacOSError::throwMe(rc); } if (disabled) { if (latentID == 0) { latentID = id; if (label) latentLabel = label; } continue; // the loop } CFRef<CFDictionaryRef> info; // as needed if (flags & kSecAssessmentFlagRequestOrigin) { if (!info) MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates))) setOrigin(chain, result); } if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) { // cache inhibit if (!info) MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) { CFRef<CFDictionaryRef> xinfo; MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref())); if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) { this->recordOutcome(code, allow, type, min(expires, dateToJulian(limit)), id); } } } if (allow) { if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) { if (!info) MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, cdhash ? CFDataGetBytePtr(cdhash) : NULL); } } else { if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) { if (!info) MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); std::string cpath = cfString(path); const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL; SYSPOLICY_ASSESS_OUTCOME_DENY(cpath.c_str(), type, label, hashp); SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, label, hashp, recorder_code_untrusted); } } cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); addAuthority(result, label, id); return; } if (rc == errSecCSUnsigned) { // skipped all applicable rules due to pre-screening cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); addAuthority(result, "no usable signature"); return; } // no applicable authority (but signed, perhaps temporarily). Deny by default CFRef<CFDictionaryRef> info; MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); if (flags & kSecAssessmentFlagRequestOrigin) { if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates))) setOrigin(chain, result); } if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) { CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL; std::string cpath = cfString(path); SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath.c_str(), type, latentLabel.c_str(), hashp); SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, latentLabel.c_str(), hashp, 0); } if (!(flags & kSecAssessmentFlagNoCache)) this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID); cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false); addAuthority(result, latentLabel.c_str(), latentID); }