2 * Copyright (c) 2006-2010 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 // csproxy - Code Signing Hosting Proxy
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>
39 // Construct a CodeSigningHost
41 CodeSigningHost::CodeSigningHost()
42 : mLock(Mutex::recursive
), mHostingState(noHosting
)
50 CodeSigningHost::~CodeSigningHost()
57 // Reset Code Signing Hosting state.
58 // This turns hosting off and clears all children.
60 void CodeSigningHost::reset()
62 StLock
<Mutex
> _(mLock
);
63 switch (mHostingState
) {
65 break; // nothing to do
67 mHostingPort
.destroy();
68 mHostingPort
= MACH_PORT_NULL
;
69 secnotice("SecServer", "%d host unregister", mHostingPort
.port());
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());
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
89 CodeSigningHost::Guest
*CodeSigningHost::findHost(SecGuestRef hostRef
)
91 Guest
*host
= findGuest(hostRef
, true);
93 if (Guest
*guest
= findGuest(host
))
94 if (guest
->dedicated
) {
104 // Look up guest by guestRef.
105 // Throws if we don't have a guest by that ref.
107 CodeSigningHost::Guest
*CodeSigningHost::findGuest(SecGuestRef guestRef
, bool hostOk
/* = false */)
109 GuestMap::iterator it
= mGuests
.find(guestRef
);
110 if (it
== mGuests
.end()) {
114 MacOSError::throwMe(errSecCSNoSuchCode
);
117 assert(it
->first
== it
->second
->guestRef());
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.
128 CodeSigningHost::Guest
*CodeSigningHost::findGuest(Guest
*host
, const CssmData
&attrData
)
130 CFRef
<CFDictionaryRef
> attrDict
= attrData
131 ? makeCFDictionaryFrom(attrData
.data(), attrData
.length())
132 : makeCFDictionary(0);
133 CFDictionary
attrs(attrDict
, errSecCSInvalidAttributeValues
);
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
143 MacOSError::throwMe(errSecCSNoSuchCode
); // not a guest of given host
145 MacOSError::throwMe(errSecCSNoSuchCode
); // not there at all
148 // now take the rest of the attrs
149 CFIndex count
= CFDictionaryGetCount(attrs
);
151 CFTypeRef
*keys
= (CFTypeRef
*)malloc(count
*sizeof(CFTypeRef
));
152 CFTypeRef
*values
= (CFTypeRef
*)malloc(count
*sizeof(CFTypeRef
));
154 if (keys
== NULL
|| values
== NULL
) {
157 MacOSError::throwMe(errSecMemoryError
);
160 CFDictionaryGetKeysAndValues(attrs
, keys
, values
);
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
)) {
169 MacOSError::throwMe(errSecCSMultipleGuests
); // ambiguous
176 if (!match
) { // nothing found
182 host
= match
; // and repeat
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).
193 CodeSigningHost::Guest
*CodeSigningHost::findGuest(Guest
*host
)
195 for (GuestMap::const_iterator it
= mGuests
.begin(); it
!= mGuests
.end(); ++it
)
196 if (it
->second
->isGuestOf(host
, strict
))
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.
207 void CodeSigningHost::registerCodeSigning(mach_port_t hostingPort
, SecCSFlags flags
)
209 StLock
<Mutex
> _(mLock
);
210 switch (mHostingState
) {
212 mHostingPort
= hostingPort
;
213 mHostingState
= dynamicHosting
;
214 secnotice("SecServer", "%d host register: %d", mHostingPort
.port(), mHostingPort
.port());
217 MacOSError::throwMe(errSecCSHostProtocolContradiction
);
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.
227 SecGuestRef
CodeSigningHost::createGuest(SecGuestRef hostRef
,
228 uint32_t status
, const char *path
,
229 const CssmData
&cdhash
, const CssmData
&attributes
, SecCSFlags flags
)
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
);
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());
247 case proxyHosting
: // already proxying
249 case dynamicHosting
: // in dynamic mode, can't switch
250 MacOSError::throwMe(errSecCSHostProtocolContradiction
);
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
262 // create the new guest
263 RefPointer
<Guest
> guest
= new Guest
;
265 guest
->guestPath
= host
->guestPath
;
266 guest
->guestPath
.push_back(int_cast
<CSSM_HANDLE
,SecGuestRef
>(guest
->handle()));
267 guest
->status
= status
;
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();
278 void CodeSigningHost::setGuestStatus(SecGuestRef guestRef
, uint32_t status
, const CssmData
&attributes
)
280 StLock
<Mutex
> _(mLock
);
281 if (mHostingState
!= proxyHosting
)
282 MacOSError::throwMe(errSecCSHostProtocolNotProxy
);
283 Guest
*guest
= findGuest(guestRef
);
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
);
293 // replace attributes if requested
295 guest
->setAttributes(attributes
);
300 // Remove a guest previously introduced via createGuest().
302 void CodeSigningHost::removeGuest(SecGuestRef hostRef
, SecGuestRef guestRef
)
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
);
314 set
<SecGuestRef
> matchingGuests
;
316 for (auto &it
: mGuests
) {
317 if (it
.second
->isGuestOf(guest
, loose
)) {
318 matchingGuests
.insert(it
.first
);
322 for (auto &it
: matchingGuests
) {
323 secnotice("SecServer", "%d guest destroy %d", mHostingPort
.port(), it
);
330 // The internal Guest object
332 CodeSigningHost::Guest::Guest() : mAttrData(NULL
), mAttrDataLength(0)
336 CodeSigningHost::Guest::~Guest()
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
);
344 void CodeSigningHost::Guest::setAttributes(const CssmData
&attrData
)
346 CFRef
<CFNumberRef
> guest
= makeCFNumber(guestRef());
348 CFDictionaryRef attrs
= makeCFDictionaryFrom(attrData
.data(), attrData
.length());
349 attributes
.take(cfmake
<CFDictionaryRef
>("{+%O,%O=%O}",
350 attrs
, kSecGuestAttributeCanonical
, guest
.get()));
351 CFReleaseNull(attrs
);
353 attributes
.take(makeCFDictionary(1, kSecGuestAttributeCanonical
, guest
.get()));
357 void CodeSigningHost::Guest::createAttrData() const {
359 CFRef
<CFDataRef
> data
= makeCFData(this->attributes
.get());
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
);
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.
374 secerror("csproxy attrData vm_allocate failed: %d", ret
);
379 void CodeSigningHost::Guest::setHash(const CssmData
&given
, bool generate
)
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
);
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
));
398 bool CodeSigningHost::Guest::isGuestOf(Guest
*host
, GuestCheck check
) const
400 vector
<SecGuestRef
> hostPath
;
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
410 return guestPath
.size() == hostPath
.size() + 1; // immediate guest
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.
423 bool CodeSigningHost::Guest::matches(CFIndex count
, CFTypeRef keys
[], CFTypeRef values
[]) const
427 for (CFIndex n
= 0; n
< count
; n
++) {
428 CFStringRef key
= CFStringRef(keys
[n
]);
429 if (CFEqual(key
, kSecGuestAttributeCanonical
)) // ignore canonical attribute (handled earlier)
431 if (CFTypeRef value
= CFDictionaryGetValue(attributes
, key
))
432 if (CFEqual(value
, values
[n
]))
440 // The MachServer dispatch handler for proxy hosting.
443 // give MIG handlers access to the object lock
444 class CodeSigningHost::Lock
: private StLock
<Mutex
> {
446 Lock(CodeSigningHost
*host
) : StLock
<Mutex
>(host
->mLock
) { }
450 boolean_t
cshosting_server(mach_msg_header_t
*, mach_msg_header_t
*);
452 static ThreadNexus
<CodeSigningHost
*> context
;
454 boolean_t
CodeSigningHost::handle(mach_msg_header_t
*in
, mach_msg_header_t
*out
)
456 CodeSigningHost::Lock
_(this);
458 return cshosting_server(in
, out
);
463 // Proxy implementation of Code Signing Hosting protocol
465 #define CSH_ARGS mach_port_t servicePort, mach_port_t replyPort, OSStatus *rcode
467 #define BEGIN_IPC try {
468 #define END_IPC *rcode = noErr; } \
469 catch (const CommonError &err) { *rcode = err.osStatus(); } \
470 catch (...) { *rcode = errSecCSInternalError; } \
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)
479 // Find a guest by arbitrary attribute set.
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).
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.
489 kern_return_t
cshosting_server_findGuest(CSH_ARGS
, SecGuestRef hostRef
,
491 GuestChain
*foundGuest
, mach_msg_type_number_t
*depth
, mach_port_t
*subhost
)
495 *subhost
= MACH_PORT_NULL
; // preset no sub-hosting port returned
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());
510 // Retrieve the path to a guest specified by canonical reference.
512 kern_return_t
cshosting_server_identifyGuest(CSH_ARGS
, SecGuestRef guestRef
,
513 char *path
, char *hash
, uint32_t *hashLength
, DATA_OUT(attributes
))
516 CodeSigningHost::Guest
*guest
= context()->findGuest(guestRef
);
517 strncpy(path
, guest
->path
.c_str(), MAXPATHLEN
);
521 *hashLength
= int_cast
<size_t, uint32_t>(CFDataGetLength(guest
->cdhash
));
522 assert(*hashLength
<= maxUcspHashLength
);
523 memcpy(hash
, CFDataGetBytePtr(guest
->cdhash
), *hashLength
);
525 *hashLength
= 0; // unavailable
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());
536 // Retrieve the status word for a guest specified by canonical reference.
538 kern_return_t
cshosting_server_guestStatus(CSH_ARGS
, SecGuestRef guestRef
, uint32_t *status
)
541 *status
= context()->findGuest(guestRef
)->status
;
549 #if defined(DEBUGDUMP)
551 void CodeSigningHost::dump() const
553 StLock
<Mutex
> _(mLock
);
554 switch (mHostingState
) {
558 Debug::dump(" dynamic host port=%d", mHostingPort
.port());
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())
575 void CodeSigningHost::Guest::dump() const
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())
581 Debug::dump("0x%x", *it
);
583 Debug::dump("; status=0x%x attrs=%s]",
584 status
, cfStringRelease(CFCopyDescription(attributes
)).c_str());