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