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