xpcengine.cpp
1 /* 2 * Copyright (c) 2011-2016 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 "xpcengine.h" 24 #include <xpc/connection.h> 25 #include <syslog.h> 26 #include <CoreFoundation/CoreFoundation.h> 27 #include <security_utilities/cfutilities.h> 28 #include <security_utilities/logging.h> 29 #include <security_utilities/cfmunge.h> 30 31 #include <map> 32 33 namespace Security { 34 namespace CodeSigning { 35 36 37 static void doProgress(xpc_object_t msg); 38 39 40 static const char serviceName[] = "com.apple.security.syspolicy"; 41 42 43 static dispatch_once_t dispatchInit; // one-time init marker 44 static xpc_connection_t service; // connection to spd 45 static dispatch_queue_t queue; // dispatch queue for service 46 47 static map<uint64_t, SecAssessmentFeedback> *feedbackBlocks; 48 49 static void init() 50 { 51 dispatch_once(&dispatchInit, ^void(void) { 52 feedbackBlocks = new map<uint64_t, SecAssessmentFeedback>; 53 const char *name = serviceName; 54 if (const char *env = getenv("SYSPOLICYNAME")) 55 name = env; 56 queue = dispatch_queue_create("spd-client", DISPATCH_QUEUE_SERIAL); 57 service = xpc_connection_create_mach_service(name, queue, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); 58 xpc_connection_set_event_handler(service, ^(xpc_object_t msg) { 59 if (xpc_get_type(msg) == XPC_TYPE_DICTIONARY) { 60 const char *function = xpc_dictionary_get_string(msg, "function"); 61 if (strcmp(function, "progress") == 0) { 62 try { 63 doProgress(msg); 64 } catch (...) { 65 Syslog::error("Discarding progress handler exception"); 66 } 67 } 68 } 69 }); 70 xpc_connection_resume(service); 71 }); 72 } 73 74 75 // 76 // Your standard XPC client-side machinery 77 // 78 class Message { 79 public: 80 xpc_object_t obj; 81 82 Message(const char *function) 83 { 84 init(); 85 obj = xpc_dictionary_create(NULL, NULL, 0); 86 xpc_dictionary_set_string(obj, "function", function); 87 } 88 ~Message() 89 { 90 if (obj) 91 xpc_release(obj); 92 } 93 operator xpc_object_t () { return obj; } 94 95 void send() 96 { 97 xpc_object_t reply = xpc_connection_send_message_with_reply_sync(service, obj); 98 xpc_release(obj); 99 obj = NULL; 100 xpc_type_t type = xpc_get_type(reply); 101 if (type == XPC_TYPE_DICTIONARY) { 102 obj = reply; 103 if (int64_t error = xpc_dictionary_get_int64(obj, "error")) 104 MacOSError::throwMe((int)error); 105 } else if (type == XPC_TYPE_ERROR) { 106 const char *s = xpc_copy_description(reply); 107 printf("Error returned: %s\n", s); 108 Syslog::notice("code signing internal problem: unexpected error from xpc: %s", s); 109 free((char*)s); 110 MacOSError::throwMe(errSecCSInternalError); 111 } else { 112 const char *s = xpc_copy_description(reply); 113 printf("Unexpected type of return object: %s\n", s); 114 free((char*)s); 115 } 116 } 117 }; 118 119 120 121 static void copyCFDictionary(const void *key, const void *value, void *ctx) 122 { 123 CFMutableDictionaryRef target = CFMutableDictionaryRef(ctx); 124 if (CFGetTypeID(value) == CFURLGetTypeID()) { 125 CFRef<CFStringRef> path = CFURLCopyFileSystemPath(CFURLRef(value), kCFURLPOSIXPathStyle); 126 CFDictionaryAddValue(target, key, path); 127 } else if (!CFEqual(key, kSecAssessmentContextKeyFeedback)) { 128 CFDictionaryAddValue(target, key, value); 129 } 130 } 131 132 133 static bool precheckAccess(CFURLRef path, CFDictionaryRef context) 134 { 135 CFTypeRef type = CFDictionaryGetValue(context, kSecAssessmentContextKeyOperation); 136 if (type == NULL || CFEqual(type, kSecAssessmentOperationTypeExecute)) { 137 CFRef<SecStaticCodeRef> code; 138 OSStatus rc = SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()); 139 if (rc == errSecCSBadBundleFormat) // work around <rdar://problem/26075034> 140 return false; 141 CFRef<CFURLRef> exec; 142 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &exec.aref())); 143 UnixError::check(::access(cfString(exec).c_str(), R_OK)); 144 } else { 145 UnixError::check(access(cfString(path).c_str(), R_OK)); 146 } 147 return true; 148 } 149 150 151 void xpcEngineAssess(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) 152 { 153 precheckAccess(path, context); 154 Message msg("assess"); 155 xpc_dictionary_set_string(msg, "path", cfString(path).c_str()); 156 xpc_dictionary_set_uint64(msg, "flags", flags); 157 CFRef<CFMutableDictionaryRef> ctx = makeCFMutableDictionary(); 158 if (context) { 159 CFDictionaryApplyFunction(context, copyCFDictionary, ctx); 160 } 161 162 SecAssessmentFeedback feedback = (SecAssessmentFeedback)CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback); 163 164 /* Map the feedback block to a random number for tracking, because we don't want 165 * to send over a pointer. */ 166 uint64_t __block feedbackId = 0; 167 if (feedback) { 168 dispatch_sync(queue, ^{ 169 bool added = false; 170 while (!added) { 171 /* Simple sequence number would probably be sufficient, 172 * but making the id unpredictable is also cheap enough here. */ 173 arc4random_buf(&feedbackId, sizeof(uint64_t)); 174 if ((*feedbackBlocks)[feedbackId] == NULL /* extremely certain */) { 175 (*feedbackBlocks)[feedbackId] = feedback; 176 added = true; 177 } 178 } 179 }); 180 CFDictionaryAddValue(ctx, kSecAssessmentContextKeyFeedback, CFTempNumber(feedbackId)); 181 } 182 183 CFRef<CFDataRef> contextData = makeCFData(CFDictionaryRef(ctx)); 184 xpc_dictionary_set_data(msg, "context", CFDataGetBytePtr(contextData), CFDataGetLength(contextData)); 185 186 msg.send(); 187 188 /* Done, feedback block won't be called anymore, 189 * so remove the feedback mapping from the global map. */ 190 if (feedback) { 191 dispatch_sync(queue, ^{ 192 feedbackBlocks->erase(feedbackId); 193 }); 194 } 195 196 if (int64_t error = xpc_dictionary_get_int64(msg, "error")) 197 MacOSError::throwMe((int)error); 198 199 size_t resultLength; 200 const void *resultData = xpc_dictionary_get_data(msg, "result", &resultLength); 201 CFRef<CFDictionaryRef> resultDict = makeCFDictionaryFrom(resultData, resultLength); 202 CFDictionaryApplyFunction(resultDict, copyCFDictionary, result); 203 CFDictionaryAddValue(result, CFSTR("assessment:remote"), kCFBooleanTrue); 204 } 205 206 static void doProgress(xpc_object_t msg) 207 { 208 uint64_t current = xpc_dictionary_get_uint64(msg, "current"); 209 uint64_t total = xpc_dictionary_get_uint64(msg, "total"); 210 uint64_t ref = xpc_dictionary_get_uint64(msg, "ref"); 211 const char *token = xpc_dictionary_get_string(msg, "token"); 212 213 SecAssessmentFeedback feedback = NULL; 214 215 // doProgress is called on the queue, so no dispatch_sync here. 216 try { 217 feedback = feedbackBlocks->at(ref); 218 } catch (std::out_of_range) { 219 // Indicates that syspolicyd gave us something it shouldn't have. 220 Syslog::error("no feedback block registered with ID %lld", ref); 221 MacOSError::throwMe(errSecCSInternalError); 222 } 223 224 CFTemp<CFDictionaryRef> info("{current=%d,total=%d}", current, total); 225 Boolean proceed = feedback(kSecAssessmentFeedbackProgress, info); 226 if (!proceed) { 227 xpc_connection_t connection = xpc_dictionary_get_remote_connection(msg); 228 xpc_object_t cancelRequest = xpc_dictionary_create(NULL, NULL, 0); 229 xpc_dictionary_set_string(cancelRequest, "function", "cancel"); 230 xpc_dictionary_set_string(cancelRequest, "token", token); 231 xpc_connection_send_message(connection, cancelRequest); 232 xpc_release(cancelRequest); 233 } 234 } 235 236 237 CFDictionaryRef xpcEngineUpdate(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context) 238 { 239 Message msg("update"); 240 // target can be NULL, a CFURLRef, a SecRequirementRef, or a CFNumberRef 241 if (target) { 242 if (CFGetTypeID(target) == CFNumberGetTypeID()) 243 xpc_dictionary_set_uint64(msg, "rule", cfNumber<int64_t>(CFNumberRef(target))); 244 else if (CFGetTypeID(target) == CFURLGetTypeID()) { 245 bool good = precheckAccess(CFURLRef(target), context); 246 if (!good) // work around <rdar://problem/26075034> 247 return makeCFDictionary(0); // pretend this worked 248 xpc_dictionary_set_string(msg, "url", cfString(CFURLRef(target)).c_str()); 249 } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) { 250 CFRef<CFDataRef> data; 251 MacOSError::check(SecRequirementCopyData(SecRequirementRef(target), kSecCSDefaultFlags, &data.aref())); 252 xpc_dictionary_set_data(msg, "requirement", CFDataGetBytePtr(data), CFDataGetLength(data)); 253 } else 254 MacOSError::throwMe(errSecCSInvalidObjectRef); 255 } 256 xpc_dictionary_set_uint64(msg, "flags", flags); 257 CFRef<CFMutableDictionaryRef> ctx = makeCFMutableDictionary(); 258 if (context) 259 CFDictionaryApplyFunction(context, copyCFDictionary, ctx); 260 AuthorizationRef localAuthorization = NULL; 261 if (CFDictionaryGetValue(ctx, kSecAssessmentUpdateKeyAuthorization) == NULL) { // no caller-provided authorization 262 MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &localAuthorization)); 263 AuthorizationExternalForm extForm; 264 MacOSError::check(AuthorizationMakeExternalForm(localAuthorization, &extForm)); 265 CFDictionaryAddValue(ctx, kSecAssessmentUpdateKeyAuthorization, CFTempData(&extForm, sizeof(extForm))); 266 } 267 CFRef<CFDataRef> contextData = makeCFData(CFDictionaryRef(ctx)); 268 xpc_dictionary_set_data(msg, "context", CFDataGetBytePtr(contextData), CFDataGetLength(contextData)); 269 270 msg.send(); 271 272 if (localAuthorization) { 273 AuthorizationFree(localAuthorization, kAuthorizationFlagDefaults); 274 } 275 276 if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { 277 MacOSError::throwMe((int)error); 278 } 279 280 size_t resultLength; 281 const void *resultData = xpc_dictionary_get_data(msg, "result", &resultLength); 282 return makeCFDictionaryFrom(resultData, resultLength); 283 } 284 285 286 bool xpcEngineControl(const char *control) 287 { 288 Message msg("control"); 289 xpc_dictionary_set_string(msg, "control", control); 290 msg.send(); 291 return true; 292 } 293 294 295 void xpcEngineRecord(CFDictionaryRef info) 296 { 297 Message msg("record"); 298 CFRef<CFDataRef> infoData = makeCFData(CFDictionaryRef(info)); 299 xpc_dictionary_set_data(msg, "info", CFDataGetBytePtr(infoData), CFDataGetLength(infoData)); 300 301 msg.send(); 302 } 303 304 void xpcEngineCheckDevID(CFBooleanRef* result) 305 { 306 Message msg("check-dev-id"); 307 308 msg.send(); 309 310 if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { 311 MacOSError::throwMe((int)error); 312 } 313 314 *result = xpc_dictionary_get_bool(msg,"result") ? kCFBooleanTrue : kCFBooleanFalse; 315 } 316 317 void xpcEngineCheckNotarized(CFBooleanRef* result) 318 { 319 Message msg("check-notarized"); 320 321 msg.send(); 322 323 if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { 324 MacOSError::throwMe((int)error); 325 } 326 327 *result = xpc_dictionary_get_bool(msg,"result") ? kCFBooleanTrue : kCFBooleanFalse; 328 } 329 330 void xpcEngineTicketRegister(CFDataRef ticketData) 331 { 332 Message msg("ticket-register"); 333 xpc_dictionary_set_data(msg, "ticketData", CFDataGetBytePtr(ticketData), CFDataGetLength(ticketData)); 334 335 msg.send(); 336 337 if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { 338 MacOSError::throwMe((int)error); 339 } 340 } 341 342 void xpcEngineTicketLookup(CFDataRef hashData, SecCSDigestAlgorithm hashType, SecAssessmentTicketFlags flags, double *date) 343 { 344 Message msg("ticket-lookup"); 345 xpc_dictionary_set_data(msg, "hashData", CFDataGetBytePtr(hashData), CFDataGetLength(hashData)); 346 xpc_dictionary_set_uint64(msg, "hashType", hashType); 347 xpc_dictionary_set_uint64(msg, "flags", flags); 348 349 msg.send(); 350 351 if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { 352 MacOSError::throwMe((int)error); 353 } 354 355 double local_date = xpc_dictionary_get_double(msg, "date"); 356 if (date && !isnan(local_date)) { 357 *date = local_date; 358 } 359 } 360 361 void xpcEngineLegacyCheck(CFDataRef hashData, SecCSDigestAlgorithm hashType, CFStringRef teamID) 362 { 363 Message msg("legacy-check"); 364 xpc_dictionary_set_data(msg, "hashData", CFDataGetBytePtr(hashData), CFDataGetLength(hashData)); 365 xpc_dictionary_set_uint64(msg, "hashType", hashType); 366 367 // There may not be a team id, so just leave it off if there isn't since xpc_dictionary_set_string 368 // will return a NULL if the value isn't provided. 369 if (teamID) { 370 xpc_dictionary_set_string(msg, "teamID", CFStringGetCStringPtr(teamID, kCFStringEncodingUTF8)); 371 } 372 373 msg.send(); 374 375 if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { 376 MacOSError::throwMe((int)error); 377 } 378 } 379 380 } // end namespace CodeSigning 381 } // end namespace Security