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