]> git.saurik.com Git - apple/security.git/blob - securityd/src/csproxy.cpp
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / securityd / src / csproxy.cpp
1 /*
2 * Copyright (c) 2006-2010 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
24
25 //
26 // csproxy - Code Signing Hosting Proxy
27 //
28 #include <set>
29
30 #include "csproxy.h"
31 #include "server.h"
32 #include <Security/SecStaticCode.h>
33 #include <securityd_client/cshosting.h>
34 #include <security_utilities/cfmunge.h>
35 #include <security_utilities/casts.h>
36 #include <utilities/SecCFRelease.h>
37
38 //
39 // Construct a CodeSigningHost
40 //
41 CodeSigningHost::CodeSigningHost()
42 : mLock(Mutex::recursive), mHostingState(noHosting)
43 {
44 }
45
46
47 //
48 // Cleanup code.
49 //
50 CodeSigningHost::~CodeSigningHost()
51 {
52 reset();
53 }
54
55
56 //
57 // Reset Code Signing Hosting state.
58 // This turns hosting off and clears all children.
59 //
60 void CodeSigningHost::reset()
61 {
62 StLock<Mutex> _(mLock);
63 switch (mHostingState) {
64 case noHosting:
65 break; // nothing to do
66 case dynamicHosting:
67 mHostingPort.deallocate();
68 secnotice("SecServer", "%d host unregister", mHostingPort.port());
69 break;
70 case proxyHosting:
71 Server::active().remove(*this); // unhook service handler
72 mHostingPort.modRefs(MACH_PORT_RIGHT_RECEIVE, -1);
73 mHostingState = noHosting;
74 mHostingPort = MACH_PORT_NULL;
75 mGuests.erase(mGuests.begin(), mGuests.end());
76 secnotice("SecServer", "%d host unregister", mHostingPort.port());
77 break;
78 }
79 }
80
81
82 //
83 // Given a host reference (possibly NULL for the process itself), locate
84 // its most dedicated guest. This descends a contiguous chain of dedicated
85 // guests until it find a host that either has no guests, or whose guests
86 // are not dedicated.
87 //
88 CodeSigningHost::Guest *CodeSigningHost::findHost(SecGuestRef hostRef)
89 {
90 Guest *host = findGuest(hostRef, true);
91 for (;;) {
92 if (Guest *guest = findGuest(host))
93 if (guest->dedicated) {
94 host = guest;
95 continue;
96 }
97 return host;
98 }
99 }
100
101
102 //
103 // Look up guest by guestRef.
104 // Throws if we don't have a guest by that ref.
105 //
106 CodeSigningHost::Guest *CodeSigningHost::findGuest(SecGuestRef guestRef, bool hostOk /* = false */)
107 {
108 GuestMap::iterator it = mGuests.find(guestRef);
109 if (it == mGuests.end()) {
110 if (hostOk) {
111 return NULL;
112 } else {
113 MacOSError::throwMe(errSecCSNoSuchCode);
114 }
115 }
116 assert(it->first == it->second->guestRef());
117 return it->second;
118 }
119
120
121 //
122 // Look up guest by attribute set.
123 // Returns the host if the attributes can't be found (*loose* interpretation).
124 // Throws if multiple guests are found (ambiguity).
125 // Implicitly matches dedicated guests no matter what attributes are requested.
126 //
127 CodeSigningHost::Guest *CodeSigningHost::findGuest(Guest *host, const CssmData &attrData)
128 {
129 CFRef<CFDictionaryRef> attrDict = attrData
130 ? makeCFDictionaryFrom(attrData.data(), attrData.length())
131 : makeCFDictionary(0);
132 CFDictionary attrs(attrDict, errSecCSInvalidAttributeValues);
133
134 // if a guest handle was provided, start with that - it must be valid or we fail
135 if (CFNumberRef canonical = attrs.get<CFNumberRef>(kSecGuestAttributeCanonical)) {
136 // direct lookup by SecGuestRef (canonical guest handle)
137 SecGuestRef guestRef = cfNumber<SecGuestRef>(canonical);
138 if (Guest *guest = findGuest(guestRef, true)) // found guest handle
139 if (guest->isGuestOf(host, loose))
140 host = guest; // new starting point
141 else
142 MacOSError::throwMe(errSecCSNoSuchCode); // not a guest of given host
143 else
144 MacOSError::throwMe(errSecCSNoSuchCode); // not there at all
145 }
146
147 // now take the rest of the attrs
148 CFIndex count = CFDictionaryGetCount(attrs);
149
150 CFTypeRef *keys = (CFTypeRef*)malloc(count*sizeof(CFTypeRef));
151 CFTypeRef *values = (CFTypeRef*)malloc(count*sizeof(CFTypeRef));
152
153 if (keys == NULL || values == NULL) {
154 free(keys);
155 free(values);
156 MacOSError::throwMe(errSecMemoryError);
157 }
158
159 CFDictionaryGetKeysAndValues(attrs, keys, values);
160 for (;;) {
161 Guest *match = NULL; // previous match found
162 for (GuestMap::const_iterator it = mGuests.begin(); it != mGuests.end(); ++it) {
163 if (it->second->isGuestOf(host, strict)) {
164 if (it->second->matches(count, keys, values)) {
165 if (match) {
166 free(keys);
167 free(values);
168 MacOSError::throwMe(errSecCSMultipleGuests); // ambiguous
169 } else {
170 match = it->second;
171 }
172 }
173 }
174 }
175 if (!match) { // nothing found
176 free(keys);
177 free(values);
178 return host;
179 }
180 else {
181 host = match; // and repeat
182 }
183 }
184 }
185
186
187 //
188 // Find any guest of a given host.
189 // This will return a randomly chosen guest of this host if it has any,
190 // or NULL if it has none (i.e. it is not a host).
191 //
192 CodeSigningHost::Guest *CodeSigningHost::findGuest(Guest *host)
193 {
194 for (GuestMap::const_iterator it = mGuests.begin(); it != mGuests.end(); ++it)
195 if (it->second->isGuestOf(host, strict))
196 return it->second;
197 return NULL;
198 }
199
200
201 //
202 // Register a hosting API service port where the host will dynamically
203 // answer hosting queries from interested parties. This switches the process
204 // to dynamic hosting mode, and is incompatible with proxy hosting.
205 //
206 void CodeSigningHost::registerCodeSigning(mach_port_t hostingPort, SecCSFlags flags)
207 {
208 StLock<Mutex> _(mLock);
209 switch (mHostingState) {
210 case noHosting:
211 mHostingPort = hostingPort;
212 mHostingState = dynamicHosting;
213 secnotice("SecServer", "%d host register: %d", mHostingPort.port(), mHostingPort.port());
214 break;
215 default:
216 MacOSError::throwMe(errSecCSHostProtocolContradiction);
217 }
218 }
219
220
221 //
222 // Create a guest entry for the given host and prepare to answer for it
223 // when dynamic hosting queries are received for it.
224 // This engages proxy hosting mode, and is incompatible with dynamic hosting mode.
225 //
226 SecGuestRef CodeSigningHost::createGuest(SecGuestRef hostRef,
227 uint32_t status, const char *path,
228 const CssmData &cdhash, const CssmData &attributes, SecCSFlags flags)
229 {
230 StLock<Mutex> _(mLock);
231 if (path[0] != '/') // relative path (relative to what? :-)
232 MacOSError::throwMe(errSecCSHostProtocolRelativePath);
233 if (cdhash.length() > maxUcspHashLength)
234 MacOSError::throwMe(errSecCSHostProtocolInvalidHash);
235
236 // set up for hosting proxy services if nothing's there yet
237 switch (mHostingState) {
238 case noHosting: // first hosting call, this host
239 // set up proxy hosting
240 mHostingPort.allocate(); // allocate service port
241 MachServer::Handler::port(mHostingPort); // put into Handler
242 MachServer::active().add(*this); // start listening
243 mHostingState = proxyHosting; // now proxying for this host
244 secnotice("SecServer", "%d host proxy: %d", mHostingPort.port(), mHostingPort.port());
245 break;
246 case proxyHosting: // already proxying
247 break;
248 case dynamicHosting: // in dynamic mode, can't switch
249 MacOSError::throwMe(errSecCSHostProtocolContradiction);
250 }
251
252 RefPointer<Guest> host = findHost(hostRef);
253 if (RefPointer<Guest> knownGuest = findGuest(host)) { // got a guest already
254 if (flags & kSecCSDedicatedHost) {
255 MacOSError::throwMe(errSecCSHostProtocolDedicationError); // can't dedicate with other guests
256 } else if (knownGuest->dedicated) {
257 MacOSError::throwMe(errSecCSHostProtocolDedicationError); // other guest is already dedicated
258 }
259 }
260
261 // create the new guest
262 RefPointer<Guest> guest = new Guest;
263 if (host)
264 guest->guestPath = host->guestPath;
265 guest->guestPath.push_back(int_cast<CSSM_HANDLE,SecGuestRef>(guest->handle()));
266 guest->status = status;
267 guest->path = path;
268 guest->setAttributes(attributes);
269 guest->setHash(cdhash, flags & kSecCSGenerateGuestHash);
270 guest->dedicated = (flags & kSecCSDedicatedHost);
271 mGuests[guest->guestRef()] = guest;
272 secnotice("SecServer", "%d guest create %d %d status:%d %d %s", mHostingPort.port(), hostRef, guest->guestRef(), guest->status, flags, guest->path.c_str());
273 return guest->guestRef();
274 }
275
276
277 void CodeSigningHost::setGuestStatus(SecGuestRef guestRef, uint32_t status, const CssmData &attributes)
278 {
279 StLock<Mutex> _(mLock);
280 if (mHostingState != proxyHosting)
281 MacOSError::throwMe(errSecCSHostProtocolNotProxy);
282 Guest *guest = findGuest(guestRef);
283
284 // state modification machine
285 if ((status & ~guest->status) & kSecCodeStatusValid)
286 MacOSError::throwMe(errSecCSHostProtocolStateError); // can't set
287 if ((~status & guest->status) & (kSecCodeStatusHard | kSecCodeStatusKill))
288 MacOSError::throwMe(errSecCSHostProtocolStateError); // can't clear
289 guest->status = status;
290 secnotice("SecServer", "%d guest change %d %d", mHostingPort.port(), guestRef, status);
291
292 // replace attributes if requested
293 if (attributes)
294 guest->setAttributes(attributes);
295 }
296
297
298 //
299 // Remove a guest previously introduced via createGuest().
300 //
301 void CodeSigningHost::removeGuest(SecGuestRef hostRef, SecGuestRef guestRef)
302 {
303 StLock<Mutex> _(mLock);
304 if (mHostingState != proxyHosting)
305 MacOSError::throwMe(errSecCSHostProtocolNotProxy);
306 RefPointer<Guest> host = findHost(hostRef);
307 RefPointer<Guest> guest = findGuest(guestRef);
308 if (guest->dedicated) // can't remove a dedicated guest
309 MacOSError::throwMe(errSecCSHostProtocolDedicationError);
310 if (!guest->isGuestOf(host, strict))
311 MacOSError::throwMe(errSecCSHostProtocolUnrelated);
312
313 set<SecGuestRef> matchingGuests;
314
315 for (auto &it : mGuests) {
316 if (it.second->isGuestOf(guest, loose)) {
317 matchingGuests.insert(it.first);
318 }
319 }
320
321 for (auto &it : matchingGuests) {
322 secnotice("SecServer", "%d guest destroy %d", mHostingPort.port(), it);
323 mGuests.erase(it);
324 }
325 }
326
327
328 //
329 // The internal Guest object
330 //
331 CodeSigningHost::Guest::Guest() : mAttrData(NULL), mAttrDataLength(0)
332 {
333 }
334
335 CodeSigningHost::Guest::~Guest()
336 {
337 if (mAttrData != NULL) {
338 vm_size_t rounded_size = mach_vm_round_page(mAttrDataLength);
339 vm_deallocate(mach_task_self(), reinterpret_cast<vm_address_t>(mAttrData), rounded_size);
340 }
341 }
342
343 void CodeSigningHost::Guest::setAttributes(const CssmData &attrData)
344 {
345 CFRef<CFNumberRef> guest = makeCFNumber(guestRef());
346 if (attrData) {
347 CFDictionaryRef attrs = makeCFDictionaryFrom(attrData.data(), attrData.length());
348 attributes.take(cfmake<CFDictionaryRef>("{+%O,%O=%O}",
349 attrs, kSecGuestAttributeCanonical, guest.get()));
350 CFReleaseNull(attrs);
351 } else {
352 attributes.take(makeCFDictionary(1, kSecGuestAttributeCanonical, guest.get()));
353 }
354 }
355
356 void CodeSigningHost::Guest::createAttrData() const {
357 if (!mAttrData) {
358 CFRef<CFDataRef> data = makeCFData(this->attributes.get());
359
360 /* cshosting_server_identifyGuest() will point to the attrData in a MIG
361 * OOL buffer. To prevent leaking surrounding memory, the attr data gets its
362 * own (zeroed) pages. */
363 vm_address_t addr = 0;
364 vm_size_t rounded_size = mach_vm_round_page(CFDataGetLength(data.get()));
365 kern_return_t ret = vm_allocate(mach_task_self(), &addr, rounded_size, VM_FLAGS_ANYWHERE);
366
367 if (ret == KERN_SUCCESS) {
368 mAttrData = reinterpret_cast<uint8_t *>(addr);
369 mAttrDataLength = CFDataGetLength(data.get());
370 memcpy(mAttrData, CFDataGetBytePtr(data.get()), mAttrDataLength);
371 // pages returned by vm_allocate are zeroed, no need to fill out padding.
372 } else {
373 secerror("csproxy attrData vm_allocate failed: %d", ret);
374 }
375 }
376 }
377
378 void CodeSigningHost::Guest::setHash(const CssmData &given, bool generate)
379 {
380 if (given.length()) // explicitly given
381 this->cdhash.take(makeCFData(given));
382 else if (CFTypeRef hash = CFDictionaryGetValue(this->attributes, kSecGuestAttributeHash))
383 if (CFGetTypeID(hash) == CFDataGetTypeID())
384 this->cdhash = CFDataRef(hash);
385 else
386 MacOSError::throwMe(errSecCSHostProtocolInvalidHash);
387 else if (generate) { // generate from path (well, try)
388 CFRef<SecStaticCodeRef> code;
389 MacOSError::check(SecStaticCodeCreateWithPath(CFTempURL(this->path), kSecCSDefaultFlags, &code.aref()));
390 CFRef<CFDictionaryRef> info;
391 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
392 this->cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
393 }
394 }
395
396
397 bool CodeSigningHost::Guest::isGuestOf(Guest *host, GuestCheck check) const
398 {
399 vector<SecGuestRef> hostPath;
400 if (host)
401 hostPath = host->guestPath;
402 if (hostPath.size() <= guestPath.size()
403 && !memcmp(&hostPath[0], &guestPath[0], sizeof(SecGuestRef) * hostPath.size()))
404 // hostPath is a prefix of guestPath
405 switch (check) {
406 case loose:
407 return true;
408 case strict:
409 return guestPath.size() == hostPath.size() + 1; // immediate guest
410 }
411 return false;
412 }
413
414
415 //
416 // Check to see if a given guest matches the (possibly empty) attribute set provided
417 // (in broken-open form, for efficiency). A dedicated guest will match all attribute
418 // specifications, even empty ones. A non-dedicated guest matches if at least one
419 // attribute value requested matches exactly (in the sense of CFEqual) that given
420 // by the host for this guest.
421 //
422 bool CodeSigningHost::Guest::matches(CFIndex count, CFTypeRef keys[], CFTypeRef values[]) const
423 {
424 if (dedicated)
425 return true;
426 for (CFIndex n = 0; n < count; n++) {
427 CFStringRef key = CFStringRef(keys[n]);
428 if (CFEqual(key, kSecGuestAttributeCanonical)) // ignore canonical attribute (handled earlier)
429 continue;
430 if (CFTypeRef value = CFDictionaryGetValue(attributes, key))
431 if (CFEqual(value, values[n]))
432 return true;
433 }
434 return false;
435 }
436
437
438 //
439 // The MachServer dispatch handler for proxy hosting.
440 //
441
442 // give MIG handlers access to the object lock
443 class CodeSigningHost::Lock : private StLock<Mutex> {
444 public:
445 Lock(CodeSigningHost *host) : StLock<Mutex>(host->mLock) { }
446 };
447
448
449 boolean_t cshosting_server(mach_msg_header_t *, mach_msg_header_t *);
450
451 static ThreadNexus<CodeSigningHost *> context;
452
453 boolean_t CodeSigningHost::handle(mach_msg_header_t *in, mach_msg_header_t *out)
454 {
455 CodeSigningHost::Lock _(this);
456 context() = this;
457 return cshosting_server(in, out);
458 }
459
460
461 //
462 // Proxy implementation of Code Signing Hosting protocol
463 //
464 #define CSH_ARGS mach_port_t servicePort, mach_port_t replyPort, OSStatus *rcode
465
466 #define BEGIN_IPC try {
467 #define END_IPC *rcode = noErr; } \
468 catch (const CommonError &err) { *rcode = err.osStatus(); } \
469 catch (...) { *rcode = errSecCSInternalError; } \
470 return KERN_SUCCESS;
471
472 #define DATA_IN(base) void *base, mach_msg_type_number_t base##Length
473 #define DATA_OUT(base) void **base, mach_msg_type_number_t *base##Length
474 #define DATA(base) CssmData(base, base##Length)
475
476
477 //
478 // Find a guest by arbitrary attribute set.
479 //
480 // This returns an array of canonical guest references describing the path
481 // from the host given to the guest found. If the host itself is returned
482 // as a guest, this will be an empty array (zero length).
483 //
484 // The subhost return argument may in the future return the hosting port for
485 // a guest who dynamically manages its hosting (thus breaking out of proxy mode),
486 // but this is not yet implemented.
487 //
488 kern_return_t cshosting_server_findGuest(CSH_ARGS, SecGuestRef hostRef,
489 DATA_IN(attributes),
490 GuestChain *foundGuest, mach_msg_type_number_t *depth, mach_port_t *subhost)
491 {
492 BEGIN_IPC
493
494 *subhost = MACH_PORT_NULL; // preset no sub-hosting port returned
495
496 Process::Guest *host = context()->findGuest(hostRef, true);
497 if (Process::Guest *guest = context()->findGuest(host, DATA(attributes))) {
498 *foundGuest = &guest->guestPath[0];
499 *depth = int_cast<size_t, mach_msg_type_number_t>(guest->guestPath.size());
500 } else {
501 *foundGuest = NULL;
502 *depth = 0;
503 }
504 END_IPC
505 }
506
507
508 //
509 // Retrieve the path to a guest specified by canonical reference.
510 //
511 kern_return_t cshosting_server_identifyGuest(CSH_ARGS, SecGuestRef guestRef,
512 char *path, char *hash, uint32_t *hashLength, DATA_OUT(attributes))
513 {
514 BEGIN_IPC
515 CodeSigningHost::Guest *guest = context()->findGuest(guestRef);
516 strncpy(path, guest->path.c_str(), MAXPATHLEN);
517
518 // canonical cdhash
519 if (guest->cdhash) {
520 *hashLength = int_cast<size_t, uint32_t>(CFDataGetLength(guest->cdhash));
521 assert(*hashLength <= maxUcspHashLength);
522 memcpy(hash, CFDataGetBytePtr(guest->cdhash), *hashLength);
523 } else
524 *hashLength = 0; // unavailable
525
526 // visible attributes. This proxy returns all attributes set by the host
527 *attributes = (void*)guest->attrData(); // MIG botch (it doesn't need a writable pointer)
528 *attributesLength = int_cast<CFIndex, mach_msg_type_number_t>(guest->attrDataLength());
529
530 END_IPC
531 }
532
533
534 //
535 // Retrieve the status word for a guest specified by canonical reference.
536 //
537 kern_return_t cshosting_server_guestStatus(CSH_ARGS, SecGuestRef guestRef, uint32_t *status)
538 {
539 BEGIN_IPC
540 *status = context()->findGuest(guestRef)->status;
541 END_IPC
542 }
543
544
545 //
546 // Debug support
547 //
548 #if defined(DEBUGDUMP)
549
550 void CodeSigningHost::dump() const
551 {
552 StLock<Mutex> _(mLock);
553 switch (mHostingState) {
554 case noHosting:
555 break;
556 case dynamicHosting:
557 Debug::dump(" dynamic host port=%d", mHostingPort.port());
558 break;
559 case proxyHosting:
560 Debug::dump(" proxy-host port=%d", mHostingPort.port());
561 if (!mGuests.empty()) {
562 Debug::dump(" %d guests={", int(mGuests.size()));
563 for (GuestMap::const_iterator it = mGuests.begin(); it != mGuests.end(); ++it) {
564 if (it != mGuests.begin())
565 Debug::dump(", ");
566 it->second->dump();
567 }
568 Debug::dump("}");
569 }
570 break;
571 }
572 }
573
574 void CodeSigningHost::Guest::dump() const
575 {
576 Debug::dump("%s[", path.c_str());
577 for (vector<SecGuestRef>::const_iterator it = guestPath.begin(); it != guestPath.end(); ++it) {
578 if (it != guestPath.begin())
579 Debug::dump("/");
580 Debug::dump("0x%x", *it);
581 }
582 Debug::dump("; status=0x%x attrs=%s]",
583 status, cfStringRelease(CFCopyDescription(attributes)).c_str());
584 }
585
586 #endif //DEBUGDUMP