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