// // Construct and prepare an SQL query on the authority table, operating on some set of existing authority records. // In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given. // void PolicyEngine::selectRules(SQLite::Statement &action, std::string phrase, std::string table, CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, std::string suffix /* = "" */) { CFDictionary ctx(context, errSecCSInvalidAttributeValues); CFCopyRef<CFTypeRef> target = inTarget; std::string filter_unsigned; // ignored; used just to trigger ad-hoc signing normalizeTarget(target, ctx, &filter_unsigned); string label; if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel)) label = cfString(CFStringRef(lab)); if (!target) { if (label.empty()) { if (type == kAuthorityInvalid) { action.query(phrase + suffix); } else { action.query(phrase + " WHERE " + table + ".type = :type" + suffix); action.bind(":type").integer(type); } } else { // have label if (type == kAuthorityInvalid) { action.query(phrase + " WHERE " + table + ".label = :label" + suffix); } else { action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".label = :label" + suffix); action.bind(":type").integer(type); } action.bind(":label") = label; } } else if (CFGetTypeID(target) == CFNumberGetTypeID()) { action.query(phrase + " WHERE " + table + ".id = :id" + suffix); action.bind(":id").integer(cfNumber<uint64_t>(target.as<CFNumberRef>())); } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) { if (type == kAuthorityInvalid) type = kAuthorityExecute; CFRef<CFStringRef> requirementText; MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref())); action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".requirement = :requirement" + suffix); action.bind(":type").integer(type); action.bind(":requirement") = requirementText.get(); } else MacOSError::throwMe(errSecCSInvalidObjectRef); }
// // 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); }
bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, CFURLRef path, SecAssessmentFlags matchFlags) { if (matchFlags == 0) { // playback; consult authority table for matches DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep(); std::string screen; if (CFRef<CFDataRef> info = rep->component(cdInfoSlot)) { SHA1 hash; hash.update(CFDataGetBytePtr(info), CFDataGetLength(info)); screen = createWhitelistScreen('I', hash); } else if (rep->mainExecutableImage()) { screen = "N"; } else { SHA1 hash; hashFileData(rep->mainExecutablePath().c_str(), &hash); screen = createWhitelistScreen('M', hash); } SQLite::Statement query(*this, "SELECT flags FROM authority " "WHERE type = :type" " AND NOT flags & :flag" " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END"); query.bind(":type").integer(type); query.bind(":flag").integer(kAuthorityFlagDefault); query.bind(":screen") = screen; query.bind(":remarks") = cfString(path); if (!query.nextRow()) // guaranteed no matching rule return false; matchFlags = SQLite3::int64(query[0]); } 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, (matchFlags & kAuthorityFlagWhitelistV2) ? kSecCSSignOpaque : kSecCSSignV1, &signer.aref())); MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags)); SecRequirementRef dr = NULL; SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr); CFStringRef drs = NULL; SecRequirementCopyString(dr, kSecCSDefaultFlags, &drs); // 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); } return true; // it worked; we're now (well) signed } catch (...) { } return false; }