/ OSX / libsecurity_codesigning / lib / xpcengine.cpp
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