2  * Copyright (c) 2011 Apple Inc. All Rights Reserved. 
   4  * @APPLE_LICENSE_HEADER_START@ 
   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 
  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. 
  21  * @APPLE_LICENSE_HEADER_END@ 
  24 #include "SecAssessment.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> 
  34 using namespace CodeSigning
; 
  40 struct _SecAssessment 
: private CFRuntimeBase 
{ 
  42         _SecAssessment(CFURLRef p
, CFDictionaryRef r
) : path(p
), result(r
) { } 
  44         CFCopyRef
<CFURLRef
> path
; 
  45         CFRef
<CFDictionaryRef
> result
; 
  48         static _SecAssessment 
&ref(SecAssessmentRef r
) 
  49                 { return *(_SecAssessment 
*)r
; } 
  52         void *operator new (size_t size
) 
  54                 return (void *)_CFRuntimeCreateInstance(NULL
, SecAssessmentGetTypeID(), 
  55                         sizeof(_SecAssessment
) - sizeof(CFRuntimeBase
), NULL
); 
  58         static void finalize(CFTypeRef obj
) 
  59         { ((_SecAssessment 
*)obj
)->~_SecAssessment(); } 
  62 typedef _SecAssessment SecAssessment
; 
  65 static const CFRuntimeClass assessmentClass 
= { 
  67         "SecAssessment",                                // name 
  70         SecAssessment::finalize
,                // finalize 
  78 static dispatch_once_t assessmentOnce
; 
  79 CFTypeID assessmentType 
= _kCFRuntimeNotATypeID
; 
  81 CFTypeID 
SecAssessmentGetTypeID() 
  84         dispatch_once(&assessmentOnce
, ^void() { 
  85                 if ((assessmentType 
= _CFRuntimeRegisterClass(&assessmentClass
)) == _kCFRuntimeNotATypeID
) 
  88         return assessmentType
; 
  93 // Common dictionary constants 
  95 const CFStringRef kSecAssessmentContextKeyOperation 
= CFSTR("operation"); 
  96 const CFStringRef kSecAssessmentOperationTypeExecute 
= CFSTR("operation:execute"); 
  97 const CFStringRef kSecAssessmentOperationTypeInstall 
= CFSTR("operation:install"); 
  98 const CFStringRef kSecAssessmentOperationTypeOpenDocument 
= CFSTR("operation:lsopen"); 
 102 // Read-only in-process access to the policy database 
 104 class ReadPolicy 
: public PolicyDatabase 
{ 
 106         ReadPolicy() : PolicyDatabase(defaultDatabase
) { } 
 108 ModuleNexus
<ReadPolicy
> gDatabase
; 
 112 // An on-demand instance of the policy engine 
 114 ModuleNexus
<PolicyEngine
> gEngine
; 
 118 // Help mapping API-ish CFString keys to more convenient internal enumerations 
 125 static uint 
mapEnum(CFDictionaryRef context
, CFStringRef attr
, const StringMap 
*map
, uint value 
= 0) 
 128                 if (CFTypeRef value 
= CFDictionaryGetValue(context
, attr
)) 
 129                         for (const StringMap 
*mp 
= map
; mp
->cstring
; ++mp
) 
 130                                 if (CFEqual(mp
->cstring
, value
)) 
 131                                         return mp
->enumeration
; 
 134         MacOSError::throwMe(errSecCSInvalidAttributeValues
); 
 137 static const StringMap mapType
[] = { 
 138         { kSecAssessmentOperationTypeExecute
, kAuthorityExecute 
}, 
 139         { kSecAssessmentOperationTypeInstall
, kAuthorityInstall 
}, 
 140         { kSecAssessmentOperationTypeOpenDocument
, kAuthorityOpenDoc 
}, 
 144 static AuthorityType 
typeFor(CFDictionaryRef context
, AuthorityType type 
= kAuthorityInvalid
) 
 145 { return mapEnum(context
, kSecAssessmentContextKeyOperation
, mapType
, type
); } 
 149 // Policy evaluation ("assessment") operations 
 151 const CFStringRef kSecAssessmentContextKeyCertificates 
= CFSTR("context:certificates"); 
 153 const CFStringRef kSecAssessmentAssessmentVerdict 
= CFSTR("assessment:verdict"); 
 154 const CFStringRef kSecAssessmentAssessmentOriginator 
= CFSTR("assessment:originator"); 
 155 const CFStringRef kSecAssessmentAssessmentAuthority 
= CFSTR("assessment:authority"); 
 156 const CFStringRef kSecAssessmentAssessmentSource 
= CFSTR("assessment:authority:source"); 
 157 const CFStringRef kSecAssessmentAssessmentAuthorityRow 
= CFSTR("assessment:authority:row"); 
 158 const CFStringRef kSecAssessmentAssessmentAuthorityOverride 
= CFSTR("assessment:authority:override"); 
 159 const CFStringRef kSecAssessmentAssessmentFromCache 
= CFSTR("assessment:authority:cached"); 
 161 SecAssessmentRef 
SecAssessmentCreate(CFURLRef path
, 
 162         SecAssessmentFlags flags
, 
 163         CFDictionaryRef context
, 
 167         SYSPOLICY_ASSESS_API(cfString(path
).c_str(), flags
); 
 169         if (flags 
& kSecAssessmentFlagAsynchronous
) 
 170                 MacOSError::throwMe(errSecCSUnimplemented
); 
 172         AuthorityType type 
= typeFor(context
, kAuthorityExecute
); 
 173         CFRef
<CFMutableDictionaryRef
> result 
= makeCFMutableDictionary(); 
 176                 // check the object cache first unless caller denied that or we need extended processing 
 177                 if (!(flags 
& (kSecAssessmentFlagRequestOrigin 
| kSecAssessmentFlagIgnoreCache
))) { 
 178                         if (gDatabase().checkCache(path
, type
, result
)) 
 179                                 return new SecAssessment(path
, result
.yield()); 
 182                 if (flags 
& kSecAssessmentFlagDirect
) { 
 183                         // ask the engine right here to do its thing 
 184                         SYSPOLICY_ASSESS_LOCAL(); 
 185                         gEngine().evaluate(path
, type
, flags
, context
, result
); 
 187                         // relay the question to our daemon for consideration 
 188                         SYSPOLICY_ASSESS_REMOTE(); 
 189                         xpcEngineAssess(path
, flags
, context
, result
); 
 191         } catch (CommonError 
&error
) { 
 192                 if (!overrideAssessment()) 
 193                         throw;          // let it go as an error 
 194                 cfadd(result
, "{%O=#F,'assessment:error'=%d}}", kSecAssessmentAssessmentVerdict
, error
.osStatus()); 
 196                 if (!overrideAssessment()) 
 197                         throw;          // let it go as an error 
 198                 cfadd(result
, "{%O=#F}", kSecAssessmentAssessmentVerdict
); 
 200         return new SecAssessment(path
, result
.yield()); 
 202         END_CSAPI_ERRORS1(NULL
) 
 206 static void traceResult(SecAssessment 
&assessment
, CFDictionaryRef result
) 
 208         if (CFDictionaryGetValue(result
, CFSTR("assessment:remote"))) 
 209                 return;         // just traced in syspolicyd 
 211         CFRef
<CFURLRef
> url 
= CFURLCopyAbsoluteURL(assessment
.path
); 
 212         string sanitized 
= cfString(url
); 
 213         string::size_type rslash 
= sanitized
.rfind('/'); 
 214         if (rslash 
!= string::npos
) 
 215                 sanitized 
= sanitized
.substr(rslash
+1); 
 216         string::size_type dot 
= sanitized
.rfind('.'); 
 217         if (dot 
!= string::npos
) 
 218                 sanitized 
= sanitized
.substr(dot
+1); 
 220                 sanitized 
= "(none)"; 
 222         string identifier 
= "UNBUNDLED"; 
 223         if (CFRef
<CFBundleRef
> bundle 
= CFBundleCreate(NULL
, assessment
.path
)) 
 224                 if (CFStringRef ident 
= CFBundleGetIdentifier(bundle
)) 
 225                         identifier 
= cfString(ident
); 
 227         string authority 
= "UNSPECIFIED"; 
 228         bool overridden 
= false; 
 229         if (CFDictionaryRef authdict 
= CFDictionaryRef(CFDictionaryGetValue(result
, kSecAssessmentAssessmentAuthority
))) { 
 230                 if (CFStringRef auth 
= CFStringRef(CFDictionaryGetValue(authdict
, kSecAssessmentAssessmentSource
))) 
 231                         authority 
= cfString(auth
); 
 233                         authority 
= "no authority"; 
 234                 if (CFDictionaryGetValue(authdict
, kSecAssessmentAssessmentAuthorityOverride
)) 
 238         MessageTrace 
trace("com.apple.security.assessment.outcome", NULL
); 
 239         trace
.add("signature2", "bundle:%s", identifier
.c_str()); 
 240         if (CFDictionaryGetValue(result
, kSecAssessmentAssessmentVerdict
) == kCFBooleanFalse
) { 
 241                 trace
.add("signature", "denied:%s", authority
.c_str()); 
 242                 trace
.add("signature3", sanitized
.c_str()); 
 243                 trace
.send("assessment denied for %s", sanitized
.c_str()); 
 244         } else if (overridden
) { 
 245                 trace
.add("signature", "override:%s", authority
.c_str()); 
 246                 trace
.add("signature3", sanitized
.c_str()); 
 247                 trace
.send("assessment denied for %s but overridden", sanitized
.c_str()); 
 249                 trace
.add("signature", "granted:%s", authority
.c_str()); 
 250                 trace
.add("signature3", sanitized
.c_str()); 
 251                 trace
.send("assessment granted for %s by %s", sanitized
.c_str(), authority
.c_str()); 
 257 // At present, CopyResult simply retrieves the result already formed by Create. 
 258 // In the future, this will be more lazy. 
 260 CFDictionaryRef 
SecAssessmentCopyResult(SecAssessmentRef assessmentRef
, 
 261         SecAssessmentFlags flags
, 
 266         SecAssessment 
&assessment 
= SecAssessment::ref(assessmentRef
); 
 267         CFCopyRef
<CFDictionaryRef
> result 
= assessment
.result
; 
 268         if (!(flags 
& kSecAssessmentFlagEnforce
) && overrideAssessment()) { 
 269                 // turn rejections into approvals, but note that we did that 
 270                 if (CFDictionaryGetValue(result
, kSecAssessmentAssessmentVerdict
) == kCFBooleanFalse
) { 
 271                         CFRef
<CFMutableDictionaryRef
> adulterated 
= makeCFMutableDictionary(result
.get()); 
 272                         CFDictionarySetValue(adulterated
, kSecAssessmentAssessmentVerdict
, kCFBooleanTrue
); 
 273                         if (CFDictionaryRef authority 
= CFDictionaryRef(CFDictionaryGetValue(adulterated
, kSecAssessmentAssessmentAuthority
))) { 
 274                                 CFRef
<CFMutableDictionaryRef
> authority2 
= makeCFMutableDictionary(authority
); 
 275                                 CFDictionarySetValue(authority2
, kSecAssessmentAssessmentAuthorityOverride
, CFSTR("security disabled")); 
 276                                 CFDictionarySetValue(adulterated
, kSecAssessmentAssessmentAuthority
, authority2
); 
 278                                 cfadd(adulterated
, "{%O={%O='security disabled'}}", 
 279                                         kSecAssessmentAssessmentAuthority
, kSecAssessmentAssessmentAuthorityOverride
); 
 281                         result 
= adulterated
.get(); 
 284         traceResult(assessment
, result
); 
 285         return result
.yield(); 
 287         END_CSAPI_ERRORS1(NULL
) 
 292 // Policy editing operations 
 294 const CFStringRef kSecAssessmentContextKeyUpdate 
= CFSTR("update"); 
 295 const CFStringRef kSecAssessmentUpdateOperationAddFile 
= CFSTR("update:addfile"); 
 296 const CFStringRef kSecAssessmentUpdateOperationRemoveFile 
= CFSTR("update:removefile"); 
 298 const CFStringRef kSecAssessmentUpdateKeyPriority 
= CFSTR("update:priority"); 
 299 const CFStringRef kSecAssessmentUpdateKeyLabel 
= CFSTR("update:label"); 
 301 Boolean 
SecAssessmentUpdate(CFURLRef path
, 
 302         SecAssessmentFlags flags
, 
 303         CFDictionaryRef context
, 
 308         CFDictionary 
ctx(context
, errSecCSInvalidAttributeValues
); 
 309         CFStringRef edit 
= ctx
.get
<CFStringRef
>(kSecAssessmentContextKeyUpdate
); 
 311         AuthorityType type 
= typeFor(context
); 
 312         if (edit 
== kSecAssessmentUpdateOperationAddFile
) 
 313                 return gEngine().add(path
, type
, flags
, context
); 
 314         else if (edit 
== kSecAssessmentUpdateOperationRemoveFile
) 
 315                 MacOSError::throwMe(errSecCSUnimplemented
); 
 317                 MacOSError::throwMe(errSecCSInvalidAttributeValues
); 
 319         END_CSAPI_ERRORS1(false) 
 324 // The fcntl of System Policies. 
 325 // For those very special requests. 
 327 Boolean 
SecAssessmentControl(CFStringRef control
, void *arguments
, CFErrorRef 
*errors
) 
 331         if (CFEqual(control
, CFSTR("ui-enable"))) { 
 332                 UnixPlusPlus::AutoFileDesc 
flagFile(visibleSecurityFlagFile
, O_CREAT 
| O_WRONLY
, 0644); 
 333                 MessageTrace 
trace("com.apple.security.assessment.state", "enable"); 
 334                 trace
.send("enable assessment outcomes"); 
 336         } else if (CFEqual(control
, CFSTR("ui-disable"))) { 
 337                 if (::remove(visibleSecurityFlagFile
) == 0 || errno 
== ENOENT
) { 
 338                         MessageTrace 
trace("com.apple.security.assessment.state", "disable"); 
 339                         trace
.send("disable assessment outcomes"); 
 342                 UnixError::throwMe(); 
 343         } else if (CFEqual(control
, CFSTR("ui-status"))) { 
 344                 CFBooleanRef 
&result 
= *(CFBooleanRef
*)(arguments
); 
 345                 if (overrideAssessment()) 
 346                         result 
= kCFBooleanFalse
; 
 348                         result 
= kCFBooleanTrue
; 
 351                 MacOSError::throwMe(errSecCSInvalidAttributeValues
); 
 353         END_CSAPI_ERRORS1(false)