]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
e3d460c9 | 2 | * Copyright (c) 2011-2016 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
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> | |
fa7225c8 | 28 | #include <security_utilities/logging.h> |
d8f41ccd | 29 | #include <security_utilities/cfmunge.h> |
b1ab9ed8 | 30 | |
b54c578e | 31 | #include <map> |
b1ab9ed8 A |
32 | |
33 | namespace Security { | |
34 | namespace CodeSigning { | |
d8f41ccd A |
35 | |
36 | ||
37 | static void doProgress(xpc_object_t msg); | |
38 | ||
b1ab9ed8 A |
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 | |
b54c578e A |
46 | |
47 | static map<uint64_t, SecAssessmentFeedback> *feedbackBlocks; | |
48 | ||
b1ab9ed8 A |
49 | static void init() |
50 | { | |
51 | dispatch_once(&dispatchInit, ^void(void) { | |
b54c578e | 52 | feedbackBlocks = new map<uint64_t, SecAssessmentFeedback>; |
b1ab9ed8 A |
53 | const char *name = serviceName; |
54 | if (const char *env = getenv("SYSPOLICYNAME")) | |
55 | name = env; | |
b54c578e | 56 | queue = dispatch_queue_create("spd-client", DISPATCH_QUEUE_SERIAL); |
b1ab9ed8 | 57 | service = xpc_connection_create_mach_service(name, queue, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); |
d8f41ccd A |
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"); | |
b54c578e A |
61 | if (strcmp(function, "progress") == 0) { |
62 | try { | |
63 | doProgress(msg); | |
64 | } catch (...) { | |
65 | Syslog::error("Discarding progress handler exception"); | |
66 | } | |
d8f41ccd A |
67 | } |
68 | } | |
b1ab9ed8 A |
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")) | |
427c49bc | 104 | MacOSError::throwMe((int)error); |
b1ab9ed8 A |
105 | } else if (type == XPC_TYPE_ERROR) { |
106 | const char *s = xpc_copy_description(reply); | |
107 | printf("Error returned: %s\n", s); | |
fa7225c8 | 108 | Syslog::notice("code signing internal problem: unexpected error from xpc: %s", s); |
b1ab9ed8 | 109 | free((char*)s); |
fa7225c8 | 110 | MacOSError::throwMe(errSecCSInternalError); |
b1ab9ed8 A |
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); | |
b1ab9ed8 A |
124 | if (CFGetTypeID(value) == CFURLGetTypeID()) { |
125 | CFRef<CFStringRef> path = CFURLCopyFileSystemPath(CFURLRef(value), kCFURLPOSIXPathStyle); | |
126 | CFDictionaryAddValue(target, key, path); | |
b54c578e | 127 | } else if (!CFEqual(key, kSecAssessmentContextKeyFeedback)) { |
b1ab9ed8 A |
128 | CFDictionaryAddValue(target, key, value); |
129 | } | |
130 | } | |
e3d460c9 A |
131 | |
132 | ||
fa7225c8 | 133 | static bool precheckAccess(CFURLRef path, CFDictionaryRef context) |
e3d460c9 A |
134 | { |
135 | CFTypeRef type = CFDictionaryGetValue(context, kSecAssessmentContextKeyOperation); | |
136 | if (type == NULL || CFEqual(type, kSecAssessmentOperationTypeExecute)) { | |
137 | CFRef<SecStaticCodeRef> code; | |
fa7225c8 A |
138 | OSStatus rc = SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()); |
139 | if (rc == errSecCSBadBundleFormat) // work around <rdar://problem/26075034> | |
140 | return false; | |
e3d460c9 A |
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 | } | |
fa7225c8 | 147 | return true; |
e3d460c9 A |
148 | } |
149 | ||
b1ab9ed8 | 150 | |
427c49bc | 151 | void xpcEngineAssess(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) |
b1ab9ed8 | 152 | { |
e3d460c9 | 153 | precheckAccess(path, context); |
b1ab9ed8 A |
154 | Message msg("assess"); |
155 | xpc_dictionary_set_string(msg, "path", cfString(path).c_str()); | |
79b9da22 | 156 | xpc_dictionary_set_uint64(msg, "flags", flags); |
b1ab9ed8 | 157 | CFRef<CFMutableDictionaryRef> ctx = makeCFMutableDictionary(); |
b54c578e | 158 | if (context) { |
b1ab9ed8 | 159 | CFDictionaryApplyFunction(context, copyCFDictionary, ctx); |
b54c578e A |
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 | ||
b1ab9ed8 A |
183 | CFRef<CFDataRef> contextData = makeCFData(CFDictionaryRef(ctx)); |
184 | xpc_dictionary_set_data(msg, "context", CFDataGetBytePtr(contextData), CFDataGetLength(contextData)); | |
185 | ||
186 | msg.send(); | |
187 | ||
b54c578e A |
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 | ||
b1ab9ed8 | 196 | if (int64_t error = xpc_dictionary_get_int64(msg, "error")) |
427c49bc | 197 | MacOSError::throwMe((int)error); |
b1ab9ed8 A |
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 | } | |
d8f41ccd A |
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"); | |
b54c578e A |
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 | ||
d8f41ccd A |
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 | } | |
b1ab9ed8 A |
235 | |
236 | ||
427c49bc | 237 | CFDictionaryRef xpcEngineUpdate(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context) |
b1ab9ed8 A |
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))); | |
e3d460c9 | 244 | else if (CFGetTypeID(target) == CFURLGetTypeID()) { |
fa7225c8 A |
245 | bool good = precheckAccess(CFURLRef(target), context); |
246 | if (!good) // work around <rdar://problem/26075034> | |
247 | return makeCFDictionary(0); // pretend this worked | |
b1ab9ed8 | 248 | xpc_dictionary_set_string(msg, "url", cfString(CFURLRef(target)).c_str()); |
e3d460c9 | 249 | } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) { |
b1ab9ed8 A |
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 | } | |
79b9da22 | 256 | xpc_dictionary_set_uint64(msg, "flags", flags); |
b1ab9ed8 A |
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 | ||
fa7225c8 A |
275 | if (int64_t error = xpc_dictionary_get_int64(msg, "error")) |
276 | MacOSError::throwMe((int)error); | |
b1ab9ed8 A |
277 | |
278 | size_t resultLength; | |
279 | const void *resultData = xpc_dictionary_get_data(msg, "result", &resultLength); | |
280 | return makeCFDictionaryFrom(resultData, resultLength); | |
281 | } | |
282 | ||
283 | ||
284 | bool xpcEngineControl(const char *control) | |
285 | { | |
286 | Message msg("control"); | |
287 | xpc_dictionary_set_string(msg, "control", control); | |
288 | msg.send(); | |
289 | return true; | |
290 | } | |
291 | ||
292 | ||
427c49bc A |
293 | void xpcEngineRecord(CFDictionaryRef info) |
294 | { | |
295 | Message msg("record"); | |
296 | CFRef<CFDataRef> infoData = makeCFData(CFDictionaryRef(info)); | |
297 | xpc_dictionary_set_data(msg, "info", CFDataGetBytePtr(infoData), CFDataGetLength(infoData)); | |
298 | ||
299 | msg.send(); | |
300 | } | |
301 | ||
fa7225c8 A |
302 | void xpcEngineCheckDevID(CFBooleanRef* result) |
303 | { | |
304 | Message msg("check-dev-id"); | |
305 | ||
306 | msg.send(); | |
307 | ||
308 | if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { | |
309 | MacOSError::throwMe((int)error); | |
310 | } | |
311 | ||
312 | *result = xpc_dictionary_get_bool(msg,"result") ? kCFBooleanTrue : kCFBooleanFalse; | |
313 | } | |
314 | ||
79b9da22 A |
315 | void xpcEngineCheckNotarized(CFBooleanRef* result) |
316 | { | |
317 | Message msg("check-notarized"); | |
318 | ||
319 | msg.send(); | |
320 | ||
321 | if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { | |
322 | MacOSError::throwMe((int)error); | |
323 | } | |
324 | ||
325 | *result = xpc_dictionary_get_bool(msg,"result") ? kCFBooleanTrue : kCFBooleanFalse; | |
326 | } | |
327 | ||
328 | void xpcEngineTicketRegister(CFDataRef ticketData) | |
329 | { | |
330 | Message msg("ticket-register"); | |
331 | xpc_dictionary_set_data(msg, "ticketData", CFDataGetBytePtr(ticketData), CFDataGetLength(ticketData)); | |
332 | ||
333 | msg.send(); | |
334 | ||
335 | if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { | |
336 | MacOSError::throwMe((int)error); | |
337 | } | |
338 | } | |
339 | ||
340 | void xpcEngineTicketLookup(CFDataRef hashData, SecCSDigestAlgorithm hashType, SecAssessmentTicketFlags flags, double *date) | |
341 | { | |
342 | Message msg("ticket-lookup"); | |
343 | xpc_dictionary_set_data(msg, "hashData", CFDataGetBytePtr(hashData), CFDataGetLength(hashData)); | |
344 | xpc_dictionary_set_uint64(msg, "hashType", hashType); | |
345 | xpc_dictionary_set_uint64(msg, "flags", flags); | |
346 | ||
347 | msg.send(); | |
348 | ||
349 | if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { | |
350 | MacOSError::throwMe((int)error); | |
351 | } | |
352 | ||
353 | double local_date = xpc_dictionary_get_double(msg, "date"); | |
354 | if (date && !isnan(local_date)) { | |
355 | *date = local_date; | |
356 | } | |
357 | } | |
358 | ||
b54c578e A |
359 | void xpcEngineLegacyCheck(CFDataRef hashData, SecCSDigestAlgorithm hashType, CFStringRef teamID) |
360 | { | |
361 | Message msg("legacy-check"); | |
362 | xpc_dictionary_set_data(msg, "hashData", CFDataGetBytePtr(hashData), CFDataGetLength(hashData)); | |
363 | xpc_dictionary_set_uint64(msg, "hashType", hashType); | |
364 | ||
365 | // There may not be a team id, so just leave it off if there isn't since xpc_dictionary_set_string | |
366 | // will return a NULL if the value isn't provided. | |
367 | if (teamID) { | |
368 | xpc_dictionary_set_string(msg, "teamID", CFStringGetCStringPtr(teamID, kCFStringEncodingUTF8)); | |
369 | } | |
370 | ||
371 | msg.send(); | |
372 | ||
373 | if (int64_t error = xpc_dictionary_get_int64(msg, "error")) { | |
374 | MacOSError::throwMe((int)error); | |
375 | } | |
376 | } | |
427c49bc | 377 | |
b1ab9ed8 A |
378 | } // end namespace CodeSigning |
379 | } // end namespace Security |