1/* 2 * Copyright (c) 2011 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#include "cs.h" 24#include "SecAssessment.h" 25#include "policydb.h" 26#include "policyengine.h" 27#include "xpcengine.h" 28#include "csutilities.h" 29#include <CoreFoundation/CFRuntime.h> 30#include <security_utilities/globalizer.h> 31#include <security_utilities/unix++.h> 32#include <security_utilities/cfmunge.h> 33#include <notify.h> 34#include <esp.h> 35 36using namespace CodeSigning; 37 38 39static void esp_do_check(const char *op, CFDictionaryRef dict) 40{ 41 OSStatus result = __esp_check_ns(op, (void *)(CFDictionaryRef)dict); 42 if (result != noErr) 43 MacOSError::throwMe(result); 44} 45 46// 47// CF Objects 48// 49struct _SecAssessment : private CFRuntimeBase { 50public: 51 _SecAssessment(CFURLRef p, AuthorityType typ, CFDictionaryRef c, CFDictionaryRef r) : path(p), context(c), type(typ), result(r) { } 52 53 CFCopyRef<CFURLRef> path; 54 CFCopyRef<CFDictionaryRef> context; 55 AuthorityType type; 56 CFRef<CFDictionaryRef> result; 57 58public: 59 static _SecAssessment &ref(SecAssessmentRef r) 60 { return *(_SecAssessment *)r; } 61 62 // CF Boiler-plate 63 void *operator new (size_t size) 64 { 65 return (void *)_CFRuntimeCreateInstance(NULL, SecAssessmentGetTypeID(), 66 sizeof(_SecAssessment) - sizeof(CFRuntimeBase), NULL); 67 } 68 69 static void finalize(CFTypeRef obj) 70 { ((_SecAssessment *)obj)->~_SecAssessment(); } 71}; 72 73typedef _SecAssessment SecAssessment; 74 75 76static const CFRuntimeClass assessmentClass = { 77 0, // version 78 "SecAssessment", // name 79 NULL, // init 80 NULL, // copy 81 SecAssessment::finalize, // finalize 82 NULL, // equal 83 NULL, // hash 84 NULL, // formatting 85 NULL // debug string 86}; 87 88 89static dispatch_once_t assessmentOnce; 90CFTypeID assessmentType = _kCFRuntimeNotATypeID; 91 92CFTypeID SecAssessmentGetTypeID() 93{ 94 dispatch_once(&assessmentOnce, ^void() { 95 if ((assessmentType = _CFRuntimeRegisterClass(&assessmentClass)) == _kCFRuntimeNotATypeID) 96 abort(); 97 }); 98 return assessmentType; 99} 100 101 102// 103// Common dictionary constants 104// 105CFStringRef kSecAssessmentContextKeyOperation = CFSTR("operation"); 106CFStringRef kSecAssessmentOperationTypeExecute = CFSTR("operation:execute"); 107CFStringRef kSecAssessmentOperationTypeInstall = CFSTR("operation:install"); 108CFStringRef kSecAssessmentOperationTypeOpenDocument = CFSTR("operation:lsopen"); 109 110CFStringRef kSecAssessmentContextQuarantineFlags = CFSTR("context:qtnflags"); 111 112 113// 114// Read-only in-process access to the policy database 115// 116class ReadPolicy : public PolicyDatabase { 117public: 118 ReadPolicy() : PolicyDatabase(defaultDatabase) { } 119}; 120ModuleNexus<ReadPolicy> gDatabase; 121 122 123// 124// An on-demand instance of the policy engine 125// 126ModuleNexus<PolicyEngine> gEngine; 127 128 129// 130// Policy evaluation ("assessment") operations 131// 132CFStringRef kSecAssessmentAssessmentVerdict = CFSTR("assessment:verdict"); 133CFStringRef kSecAssessmentAssessmentOriginator = CFSTR("assessment:originator"); 134CFStringRef kSecAssessmentAssessmentAuthority = CFSTR("assessment:authority"); 135CFStringRef kSecAssessmentAssessmentSource = CFSTR("assessment:authority:source"); 136CFStringRef kSecAssessmentAssessmentAuthorityRow = CFSTR("assessment:authority:row"); 137CFStringRef kSecAssessmentAssessmentAuthorityOverride = CFSTR("assessment:authority:override"); 138CFStringRef kSecAssessmentAssessmentAuthorityOriginalVerdict = CFSTR("assessment:authority:verdict"); 139CFStringRef kSecAssessmentAssessmentFromCache = CFSTR("assessment:authority:cached"); 140CFStringRef kSecAssessmentAssessmentWeakSignature = CFSTR("assessment:authority:weak"); 141CFStringRef kSecAssessmentAssessmentCodeSigningError = CFSTR("assessment:cserror"); 142 143CFStringRef kDisabledOverride = CFSTR("security disabled"); 144 145SecAssessmentRef SecAssessmentCreate(CFURLRef path, 146 SecAssessmentFlags flags, 147 CFDictionaryRef context, 148 CFErrorRef *errors) 149{ 150 BEGIN_CSAPI 151 152 if (flags & kSecAssessmentFlagAsynchronous) 153 MacOSError::throwMe(errSecCSUnimplemented); 154 155 AuthorityType type = typeFor(context, kAuthorityExecute); 156 CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary(); 157 158 SYSPOLICY_ASSESS_API(cfString(path).c_str(), int(type), flags); 159 160 try { 161 if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) { 162 CFTemp<CFDictionaryRef> dict("{path=%O, flags=%d, context=%O, override=%d}", path, flags, context, overrideAssessment()); 163 esp_do_check("cs-assessment-evaluate", dict); 164 } 165 166 // check the object cache first unless caller denied that or we need extended processing 167 if (!(flags & (kSecAssessmentFlagRequestOrigin | kSecAssessmentFlagIgnoreCache))) { 168 if (gDatabase().checkCache(path, type, flags, result)) 169 return new SecAssessment(path, type, context, result.yield()); 170 } 171 172 if (flags & kSecAssessmentFlagDirect) { 173 // ask the engine right here to do its thing 174 SYSPOLICY_ASSESS_LOCAL(); 175 gEngine().evaluate(path, type, flags, context, result); 176 } else { 177 // relay the question to our daemon for consideration 178 SYSPOLICY_ASSESS_REMOTE(); 179 xpcEngineAssess(path, flags, context, result); 180 } 181 } catch (CommonError &error) { 182 switch (error.osStatus()) { 183 case CSSMERR_TP_CERT_REVOKED: 184 throw; 185 default: 186 if (!overrideAssessment(flags)) 187 throw; // let it go as an error 188 break; 189 } 190 // record the error we would have returned 191 cfadd(result, "{%O=#F,'assessment:error'=%d}}", kSecAssessmentAssessmentVerdict, error.osStatus()); 192 } catch (...) { 193 // catch stray errors not conforming to the CommonError scheme 194 if (!overrideAssessment(flags)) 195 throw; // let it go as an error 196 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); 197 } 198 199 if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) { 200 CFTemp<CFDictionaryRef> dict("{path=%O, flags=%d, context=%O, override=%d, result=%O}", path, flags, context, overrideAssessment(), (CFDictionaryRef)result); 201 __esp_notify_ns("cs-assessment-evaluate", (void *)(CFDictionaryRef)dict); 202 } 203 204 return new SecAssessment(path, type, context, result.yield()); 205 206 END_CSAPI_ERRORS1(NULL) 207} 208 209 210static void traceResult(CFURLRef target, MessageTrace &trace, std::string &sanitized) 211{ 212 static const char *interestingBundles[] = { 213 "UNBUNDLED", 214 "com.apple.", 215 "com.install4j.", 216 "com.MindVision.", 217 "com.yourcompany.", 218 219 "com.adobe.flashplayer.installmanager", 220 "com.adobe.Installers.Setup", 221 "com.adobe.PDApp.setup", 222 "com.bittorrent.uTorrent", 223 "com.divx.divx6formacinstaller", 224 "com.getdropbox.dropbox", 225 "com.google.Chrome", 226 "com.Google.GoogleEarthPlugin.plugin", 227 "com.Google.GoogleEarthPlus", 228 "com.hp.Installer", 229 "com.macpaw.CleanMyMac", 230 "com.microsoft.SilverlightInstaller", 231 "com.paragon-software.filesystems.NTFS.pkg", 232 "com.RealNetworks.RealPlayer", 233 "com.skype.skype", 234 "it.alfanet.squared5.MPEGStreamclip", 235 "org.mozilla.firefox", 236 "org.videolan.vlc", 237 238 NULL // sentinel 239 }; 240 241 string identifier = "UNBUNDLED"; 242 string version = "UNKNOWN"; 243 if (CFRef<CFBundleRef> bundle = CFBundleCreate(NULL, target)) { 244 if (CFStringRef ident = CFBundleGetIdentifier(bundle)) 245 identifier = cfString(ident); 246 if (CFStringRef vers = CFStringRef(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString")))) 247 version = cfString(vers); 248 } 249 250 CFRef<CFURLRef> url = CFURLCopyAbsoluteURL(target); 251 sanitized = cfString(url); 252 string::size_type rslash = sanitized.rfind('/'); 253 if (rslash != string::npos) 254 sanitized = sanitized.substr(rslash+1); 255 bool keepFilename = false; 256 for (const char **pfx = interestingBundles; *pfx; pfx++) { 257 size_t pfxlen = strlen(*pfx); 258 if (identifier.compare(0, pfxlen, *pfx, pfxlen) == 0) 259 if (pfxlen == identifier.size() || (*pfx)[pfxlen-1] == '.') { 260 keepFilename = true; 261 break; 262 } 263 } 264 if (!keepFilename) { 265 string::size_type dot = sanitized.rfind('.'); 266 if (dot != string::npos) 267 sanitized = sanitized.substr(dot); 268 else 269 sanitized = "(none)"; 270 } 271 272 trace.add("signature2", "bundle:%s", identifier.c_str()); 273 trace.add("signature3", "%s", sanitized.c_str()); 274 trace.add("signature5", "%s", version.c_str()); 275} 276 277static void traceAssessment(SecAssessment &assessment, AuthorityType type, CFDictionaryRef result) 278{ 279 if (CFDictionaryGetValue(result, CFSTR("assessment:remote"))) 280 return; // just traced in syspolicyd 281 282 string authority = "UNSPECIFIED"; 283 bool overridden = false; 284 bool old_overridden = false; 285 if (CFDictionaryRef authdict = CFDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority))) { 286 if (CFStringRef auth = CFStringRef(CFDictionaryGetValue(authdict, kSecAssessmentAssessmentSource))) 287 authority = cfString(auth); 288 else 289 authority = "no authority"; 290 if (CFTypeRef override = CFDictionaryGetValue(authdict, kSecAssessmentAssessmentAuthorityOverride)) 291 if (CFEqual(override, kDisabledOverride)) { 292 old_overridden = true; 293 if (CFDictionaryGetValue(authdict, kSecAssessmentAssessmentAuthorityOriginalVerdict) == kCFBooleanFalse) 294 overridden = true; 295 } 296 } 297 298 MessageTrace trace("com.apple.security.assessment.outcome2", NULL); 299 std::string sanitized; 300 traceResult(assessment.path, trace, sanitized); 301 trace.add("signature4", "%d", type); 302 303 if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse) { 304 trace.add("signature", "denied:%s", authority.c_str()); 305 trace.send("assessment denied for %s", sanitized.c_str()); 306 } else if (overridden) { // would have failed except for override 307 trace.add("signature", "defeated:%s", authority.c_str()); 308 trace.send("assessment denied for %s but overridden", sanitized.c_str()); 309 } else if (old_overridden) { // would have succeeded even without override 310 trace.add("signature", "override:%s", authority.c_str()); 311 trace.send("assessment granted for %s and overridden", sanitized.c_str()); 312 } else { 313 trace.add("signature", "granted:%s", authority.c_str()); 314 trace.send("assessment granted for %s by %s", sanitized.c_str(), authority.c_str()); 315 } 316} 317 318static void traceUpdate(CFTypeRef target, CFDictionaryRef context, CFDictionaryRef result) 319{ 320 // only trace add operations on URL targets 321 if (target == NULL || CFGetTypeID(target) != CFURLGetTypeID()) 322 return; 323 CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate)); 324 if (!CFEqual(edit, kSecAssessmentUpdateOperationAdd)) 325 return; 326 MessageTrace trace("com.apple.security.assessment.update", NULL); 327 std::string sanitized; 328 traceResult(CFURLRef(target), trace, sanitized); 329 trace.send("added rule for %s", sanitized.c_str()); 330} 331 332 333// 334// At present, CopyResult simply retrieves the result already formed by Create. 335// In the future, this will be more lazy. 336// 337CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessmentRef, 338 SecAssessmentFlags flags, 339 CFErrorRef *errors) 340{ 341 BEGIN_CSAPI 342 343 SecAssessment &assessment = SecAssessment::ref(assessmentRef); 344 CFCopyRef<CFDictionaryRef> result = assessment.result; 345 if (overrideAssessment(flags)) { 346 // turn rejections into approvals, but note that we did that 347 CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict); 348 if (verdict == kCFBooleanFalse) { 349 CFRef<CFMutableDictionaryRef> adulterated = makeCFMutableDictionary(result.get()); 350 CFDictionarySetValue(adulterated, kSecAssessmentAssessmentVerdict, kCFBooleanTrue); 351 if (CFDictionaryRef authority = CFDictionaryRef(CFDictionaryGetValue(adulterated, kSecAssessmentAssessmentAuthority))) { 352 CFRef<CFMutableDictionaryRef> authority2 = makeCFMutableDictionary(authority); 353 CFDictionarySetValue(authority2, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride); 354 CFDictionarySetValue(authority2, kSecAssessmentAssessmentAuthorityOriginalVerdict, verdict); 355 CFDictionarySetValue(adulterated, kSecAssessmentAssessmentAuthority, authority2); 356 } else { 357 cfadd(adulterated, "{%O={%O=%O}}", 358 kSecAssessmentAssessmentAuthority, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride); 359 } 360 result = adulterated.get(); 361 } 362 } 363 bool trace = CFDictionaryContainsKey(assessment.context, kSecAssessmentContextQuarantineFlags); 364 if (trace) 365 traceAssessment(assessment, assessment.type, result); 366 return result.yield(); 367 368 END_CSAPI_ERRORS1(NULL) 369} 370 371 372// 373// Policy editing operations. 374// These all make permanent changes to the system-wide authority records. 375// 376CFStringRef kSecAssessmentContextKeyUpdate = CFSTR("update"); 377CFStringRef kSecAssessmentUpdateOperationAdd = CFSTR("update:add"); 378CFStringRef kSecAssessmentUpdateOperationRemove = CFSTR("update:remove"); 379CFStringRef kSecAssessmentUpdateOperationEnable = CFSTR("update:enable"); 380CFStringRef kSecAssessmentUpdateOperationDisable = CFSTR("update:disable"); 381CFStringRef kSecAssessmentUpdateOperationFind = CFSTR("update:find"); 382 383CFStringRef kSecAssessmentUpdateKeyAuthorization = CFSTR("update:authorization"); 384CFStringRef kSecAssessmentUpdateKeyPriority = CFSTR("update:priority"); 385CFStringRef kSecAssessmentUpdateKeyLabel = CFSTR("update:label"); 386CFStringRef kSecAssessmentUpdateKeyExpires = CFSTR("update:expires"); 387CFStringRef kSecAssessmentUpdateKeyAllow = CFSTR("update:allow"); 388CFStringRef kSecAssessmentUpdateKeyRemarks = CFSTR("update:remarks"); 389 390CFStringRef kSecAssessmentUpdateKeyRow = CFSTR("update:row"); 391CFStringRef kSecAssessmentUpdateKeyCount = CFSTR("update:count"); 392CFStringRef kSecAssessmentUpdateKeyFound = CFSTR("update:found"); 393 394CFStringRef kSecAssessmentRuleKeyID = CFSTR("rule:id"); 395CFStringRef kSecAssessmentRuleKeyPriority = CFSTR("rule:priority"); 396CFStringRef kSecAssessmentRuleKeyAllow = CFSTR("rule:allow"); 397CFStringRef kSecAssessmentRuleKeyLabel = CFSTR("rule:label"); 398CFStringRef kSecAssessmentRuleKeyRemarks = CFSTR("rule:remarks"); 399CFStringRef kSecAssessmentRuleKeyRequirement = CFSTR("rule:requirement"); 400CFStringRef kSecAssessmentRuleKeyType = CFSTR("rule:type"); 401CFStringRef kSecAssessmentRuleKeyExpires = CFSTR("rule:expires"); 402CFStringRef kSecAssessmentRuleKeyDisabled = CFSTR("rule:disabled"); 403CFStringRef kSecAssessmentRuleKeyBookmark = CFSTR("rule:bookmark"); 404 405 406Boolean SecAssessmentUpdate(CFTypeRef target, 407 SecAssessmentFlags flags, 408 CFDictionaryRef context, 409 CFErrorRef *errors) 410{ 411 if (CFDictionaryRef outcome = SecAssessmentCopyUpdate(target, flags, context, errors)) { 412 CFRelease(outcome); 413 return true; 414 } else { 415 return false; 416 } 417} 418 419CFDictionaryRef SecAssessmentCopyUpdate(CFTypeRef target, 420 SecAssessmentFlags flags, 421 CFDictionaryRef context, 422 CFErrorRef *errors) 423{ 424 BEGIN_CSAPI 425 426 CFDictionary ctx(context, errSecCSInvalidAttributeValues); 427 CFRef<CFDictionaryRef> result; 428 429 if (flags & kSecAssessmentFlagDirect) { 430 if (__esp_enabled()) { 431 CFTemp<CFDictionaryRef> dict("{target=%O, flags=%d, context=%O}", target, flags, context); 432 OSStatus esp_result = __esp_check_ns("cs-assessment-update", (void *)(CFDictionaryRef)dict); 433 if (esp_result != noErr) 434 return NULL; 435 } 436 437 // ask the engine right here to do its thing 438 result = gEngine().update(target, flags, ctx); 439 } else { 440 // relay the question to our daemon for consideration 441 result = xpcEngineUpdate(target, flags, ctx); 442 } 443 444 if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) { 445 CFTemp<CFDictionaryRef> dict("{target=%O, flags=%d, context=%O, outcome=%O}", target, flags, context, (CFDictionaryRef)result); 446 __esp_notify_ns("cs-assessment-update", (void *)(CFDictionaryRef)dict); 447 } 448 449 traceUpdate(target, context, result); 450 return result.yield(); 451 452 END_CSAPI_ERRORS1(false) 453} 454 455 456// 457// The fcntl of System Policies. 458// For those very special requests. 459// 460Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *errors) 461{ 462 BEGIN_CSAPI 463 464 CFTemp<CFDictionaryRef> dict("{control=%O}", control); 465 esp_do_check("cs-assessment-control", dict); 466 467 if (CFEqual(control, CFSTR("ui-enable"))) { 468 setAssessment(true); 469 MessageTrace trace("com.apple.security.assessment.state", "enable"); 470 trace.send("enable assessment outcomes"); 471 return true; 472 } else if (CFEqual(control, CFSTR("ui-disable"))) { 473 setAssessment(false); 474 MessageTrace trace("com.apple.security.assessment.state", "disable"); 475 trace.send("disable assessment outcomes"); 476 return true; 477 } else if (CFEqual(control, CFSTR("ui-status"))) { 478 CFBooleanRef &result = *(CFBooleanRef*)(arguments); 479 if (overrideAssessment()) 480 result = kCFBooleanFalse; 481 else 482 result = kCFBooleanTrue; 483 return true; 484 } else if (CFEqual(control, CFSTR("ui-enable-devid"))) { 485 CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID"); 486 if (CFDictionaryRef result = gEngine().enable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx)) 487 CFRelease(result); 488 MessageTrace trace("com.apple.security.assessment.state", "enable-devid"); 489 trace.send("enable Developer ID approval"); 490 return true; 491 } else if (CFEqual(control, CFSTR("ui-disable-devid"))) { 492 CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID"); 493 if (CFDictionaryRef result = gEngine().disable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx)) 494 CFRelease(result); 495 MessageTrace trace("com.apple.security.assessment.state", "disable-devid"); 496 trace.send("disable Developer ID approval"); 497 return true; 498 } else if (CFEqual(control, CFSTR("ui-get-devid"))) { 499 CFBooleanRef &result = *(CFBooleanRef*)(arguments); 500 if (gEngine().value<int>("SELECT disabled FROM authority WHERE label = 'Developer ID';", true)) 501 result = kCFBooleanFalse; 502 else 503 result = kCFBooleanTrue; 504 return true; 505 } else if (CFEqual(control, CFSTR("ui-record-reject"))) { 506 // send this through syspolicyd for update validation 507 xpcEngineRecord(CFDictionaryRef(arguments)); 508 return true; 509 } else if (CFEqual(control, CFSTR("ui-record-reject-local"))) { 510 // perform the local operation (requires root) 511 gEngine().recordFailure(CFDictionaryRef(arguments)); 512 return true; 513 } else if (CFEqual(control, CFSTR("ui-recall-reject"))) { 514 // no special privileges required for this, so read directly 515 CFDictionaryRef &result = *(CFDictionaryRef*)(arguments); 516 CFRef<CFDataRef> infoData = cfLoadFile(lastRejectFile); 517 if (infoData) 518 result = makeCFDictionaryFrom(infoData); 519 else 520 result = NULL; 521 return true; 522 } else 523 MacOSError::throwMe(errSecCSInvalidAttributeValues); 524 525 END_CSAPI_ERRORS1(false) 526} 527