X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb..refs/heads/master:/securityd/src/codesigdb.cpp diff --git a/securityd/src/codesigdb.cpp b/securityd/src/codesigdb.cpp index c06a7582..0bacfd7c 100644 --- a/securityd/src/codesigdb.cpp +++ b/securityd/src/codesigdb.cpp @@ -1,15 +1,15 @@ /* - * Copyright (c) 2003-2009,2012 Apple Inc. All Rights Reserved. - * + * Copyright (c) 2003-2009,2012,2016 Apple Inc. All Rights Reserved. + * * @APPLE_LICENSE_HEADER_START@ - * + * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. - * + * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, @@ -17,7 +17,7 @@ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. - * + * * @APPLE_LICENSE_HEADER_END@ */ @@ -34,86 +34,23 @@ #include -// -// A self-constructing database key class. -// Key format is -// where -// single ASCII character type code ('H' for hash links) -// decimal userid of owning user, or 'S' for system entries. Followed by null byte. -// variable length key value (binary). -// -class DbKey : public CssmAutoData { -public: - DbKey(char type, const CssmData &key, bool perUser = false, uid_t user = 0); -}; - -DbKey::DbKey(char type, const CssmData &key, bool perUser, uid_t user) - : CssmAutoData(Allocator::standard()) -{ - using namespace LowLevelMemoryUtilities; - char header[20]; - size_t headerLength; - if (perUser) - headerLength = 1 + sprintf(header, "%c%d", type, user); - else - headerLength = 1 + sprintf(header, "%cS", type); - malloc(headerLength + key.length()); - memcpy(this->data(), header, headerLength); - memcpy(get().at(headerLength), key.data(), key.length()); -} - - -// -// A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information) -// -struct AclIdentity : public CodeSignatures::Identity { - AclIdentity(const CssmData hash, string path) : mHash(hash), mPath(path) { } - - string getPath() const { return mPath; } - const CssmData getHash() const { return mHash; } - -private: - const CssmData mHash; - const string mPath; -}; - - // // Construct a CodeSignatures objects // -CodeSignatures::CodeSignatures(const char *path) +CodeSignatures::CodeSignatures() { - try { - mDb.open(path, O_RDWR | O_CREAT, 0644); - } catch (const CommonError &err) { - try { - mDb.open(path, O_RDONLY, 0644); - Syslog::warning("database %s opened READONLY (R/W failed errno=%d)", path, err.unixError()); - secdebug("codesign", "database %s opened READONLY (R/W failed errno=%d)", path, err.unixError()); - } catch (...) { - Syslog::warning("cannot open %s; using no code equivalents", path); - secdebug("codesign", "unable to open %s; using no code equivalents", path); - } - } - if (mDb) - mDb.flush(); // in case we just created it - IFDUMPING("equiv", debugDump("open")); } CodeSignatures::~CodeSignatures() { } - // // (Re)open the equivalence database. // This is useful to switch to database in another volume. // void CodeSignatures::open(const char *path) { - mDb.open(path, O_RDWR | O_CREAT, 0644); - mDb.flush(); - IFDUMPING("equiv", debugDump("reopen")); } @@ -126,145 +63,66 @@ CodeSignatures::Identity::Identity() : mState(untried) CodeSignatures::Identity::~Identity() { } -string CodeSignatures::Identity::canonicalName(const string &path) -{ - string::size_type slash = path.rfind('/'); - if (slash == string::npos) // bloody unlikely, but whatever... - return path; - return path.substr(slash+1); -} - - -// -// Find and store database objects (primitive layer) -// -bool CodeSignatures::find(Identity &id, uid_t user) -{ - if (id.mState != Identity::untried) - return id.mState == Identity::valid; - try { - DbKey userKey('H', id.getHash(), true, user); - CssmData linkValue; - if (mDb.get(userKey, linkValue)) { - id.mName = string(linkValue.interpretedAs(), linkValue.length()); - IFDUMPING("equiv", id.debugDump("found/user")); - id.mState = Identity::valid; - return true; - } - DbKey sysKey('H', id.getHash()); - if (mDb.get(sysKey, linkValue)) { - id.mName = string(linkValue.interpretedAs(), linkValue.length()); - IFDUMPING("equiv", id.debugDump("found/system")); - id.mState = Identity::valid; - return true; - } - } catch (...) { - secdebug("codesign", "exception validating identity for %s - marking failed", id.path().c_str()); - id.mState = Identity::invalid; - } - return id.mState == Identity::valid; -} - -void CodeSignatures::makeLink(Identity &id, const string &ident, bool forUser, uid_t user) -{ - DbKey key('H', id.getHash(), forUser, user); - if (!mDb.put(key, StringData(ident))) - UnixError::throwMe(); -} - - -// -// Administrative manipulation calls -// -void CodeSignatures::addLink(const CssmData &oldHash, const CssmData &newHash, - const char *inName, bool forSystem) -{ - string name = Identity::canonicalName(inName); - uid_t user = Server::process().uid(); - if (forSystem && user) // only root user can establish forSystem links - UnixError::throwMe(EACCES); - if (!forSystem) // in fact, for now we don't allow per-user calls at all - UnixError::throwMe(EACCES); - AclIdentity oldCode(oldHash, name); - AclIdentity newCode(newHash, name); - secdebug("codesign", "addlink for name %s", name.c_str()); - StLock _(mDatabaseLock); - if (oldCode) { - if (oldCode.trustedName() != name) { - secdebug("codesign", "addlink does not match existing name %s", - oldCode.trustedName().c_str()); - MacOSError::throwMe(CSSMERR_CSP_VERIFY_FAILED); - } - } else { - makeLink(oldCode, name, !forSystem, user); - } - if (!newCode) - makeLink(newCode, name, !forSystem, user); - mDb.flush(); -} - -void CodeSignatures::removeLink(const CssmData &hash, const char *name, bool forSystem) -{ - AclIdentity code(hash, name); - uid_t user = Server::process().uid(); - if (forSystem && user) // only root user can remove forSystem links - UnixError::throwMe(EACCES); - DbKey key('H', hash, !forSystem, user); - StLock _(mDatabaseLock); - mDb.erase(key); - mDb.flush(); -} - - // // Verify signature matches. // This ends up getting called when a CodeSignatureAclSubject is validated. // The OSXVerifier describes what we require of the client code; the process represents // the requesting client; and the context gives us access to the ACL and its environment -// in case we want to, well, creatively rewrite it for some reason. +// in case we want to, well, creatively rewrite it for some reason. // bool CodeSignatures::verify(Process &process, const OSXVerifier &verifier, const AclValidationContext &context) { - secdebug("codesign", "start verify"); + secinfo("codesign", "start verify"); StLock _(process); - SecCodeRef code = process.currentGuest(); - if (!code) { - secdebug("codesign", "no code base: fail"); - return false; - } if (SecRequirementRef requirement = verifier.requirement()) { // If the ACL contains a code signature (requirement), we won't match against unsigned code at all. // The legacy hash is ignored (it's for use by pre-Leopard systems). - secdebug("codesign", "CS requirement present; ignoring legacy hashes"); + secinfo("codesign", "CS requirement present; ignoring legacy hashes"); Server::active().longTermActivity(); - switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, requirement)) { + switch (OSStatus rc = process.checkValidity(kSecCSDefaultFlags, requirement)) { case noErr: - secdebug("codesign", "CS verify passed"); + secinfo("codesign", "CS verify passed"); return true; case errSecCSUnsigned: - secdebug("codesign", "CS verify against unsigned binary failed"); + secinfo("codesign", "CS verify against unsigned binary failed"); return false; default: - secdebug("codesign", "CS verify failed OSStatus=%d", int32_t(rc)); + secinfo("codesign", "CS verify failed OSStatus=%d", int32_t(rc)); return false; } } - switch (matchSignedClientToLegacyACL(process, code, verifier, context)) { + switch (matchSignedClientToLegacyACL(process, verifier, context)) { case noErr: // handled, allow access return true; - case errSecCSUnsigned: // unsigned client, complete legacy case - secdebug("codesign", "no CS requirement - using legacy hash"); - return verifyLegacy(process, - CssmData::wrap(verifier.legacyHash(), SHA1::digestLength), - verifier.path()); + case errSecCSUnsigned: { // unsigned client, complete legacy case + secinfo("codesign", "no CS requirement - using legacy hash"); + + /* + * We should stop supporting this case for binaries + * built for modern OS/SDK, user should ad-hoc sign + * their binaries in that case. + * + * + */ + Identity &clientIdentity = process; + try { + if (clientIdentity.getHash() == CssmData::wrap(verifier.legacyHash(), SHA1::digestLength)) { + secinfo("codesign", "direct match: pass"); + return true; + } + } catch (...) { + secinfo("codesign", "exception getting client code hash: fail"); + return false; + } + return false; + } default: // client unsuitable, reject this match return false; } } - // // See if we can rewrite the ACL from legacy to Code Signing form without losing too much security. // Returns true if the present validation should succeed (we probably rewrote the ACL). @@ -286,14 +144,14 @@ static string trim(string s, char delimiter) static string trim(string s, char delimiter, string suffix) { s = trim(s, delimiter); - int preLength = s.length() - suffix.length(); + size_t preLength = s.length() - suffix.length(); if (preLength > 0 && s.substr(preLength) == suffix) s = s.substr(0, preLength); return s; } OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process, - SecCodeRef code, const OSXVerifier &verifier, const AclValidationContext &context) + const OSXVerifier &verifier, const AclValidationContext &context) { // // Check whether we seem to be matching a legacy .Mac ACL against a member of the .Mac group @@ -302,8 +160,8 @@ OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process, Server::active().longTermActivity(); CFRef dotmac; MacOSError::check(SecRequirementCreateGroup(CFSTR("dot-mac"), NULL, kSecCSDefaultFlags, &dotmac.aref())); - if (SecCodeCheckValidity(code, kSecCSDefaultFlags, dotmac) == noErr) { - secdebug("codesign", "client is a dot-mac application; update the ACL accordingly"); + if (process.checkValidity(kSecCSDefaultFlags, dotmac) == noErr) { + secinfo("codesign", "client is a dot-mac application; update the ACL accordingly"); // create a suitable AclSubject (this is the above-the-API-line way) CFRef reqdata; @@ -321,7 +179,7 @@ OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process, // Get best names for the ACL (legacy) subject and the (signed) client // CFRef info; - MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); + MacOSError::check(process.copySigningInfo(kSecCSSigningInformation, &info.aref())); CFStringRef signingIdentity = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoIdentifier)); if (!signingIdentity) // unsigned return errSecCSUnsigned; @@ -334,10 +192,10 @@ OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process, bundleName = trim(cfString(signingIdentity), '.'); string aclName = trim(verifier.path(), '/', ".app"); // ACL - - secdebug("codesign", "matching signed client \"%s\" against legacy ACL \"%s\"", + + secinfo("codesign", "matching signed client \"%s\" against legacy ACL \"%s\"", bundleName.c_str(), aclName.c_str()); - + // // Check whether we're matching a signed APPLE application against a legacy ACL by the same name // @@ -350,200 +208,20 @@ OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process, MacOSError::check(SecRequirementCreateWithData(CFTempData(reqData, sizeof(reqData)), kSecCSDefaultFlags, &apple.aref())); Server::active().longTermActivity(); - switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, apple)) { + switch (OSStatus rc = process.checkValidity(kSecCSDefaultFlags, apple)) { case noErr: { - secdebug("codesign", "withstands strict scrutiny; quietly adding new ACL"); - RefPointer wrap = new OSXCodeWrap(code); - RefPointer subject = new CodeSignatureAclSubject(OSXVerifier(wrap)); + secinfo("codesign", "withstands strict scrutiny; quietly adding new ACL"); + RefPointer subject = process.copyAclSubject(); SecurityServerAcl::addToStandardACL(context, subject); return noErr; } default: - secdebug("codesign", "validation fails with rc=%d, rejecting", int32_t(rc)); + secinfo("codesign", "validation fails with rc=%d, rejecting", int32_t(rc)); return rc; } - secdebug("codesign", "does not withstand strict scrutiny; ask the user"); - QueryCodeCheck query; - query.inferHints(process); - if (!query(verifier.path().c_str())) { - secdebug("codesign", "user declined equivalence: cancel the access"); - CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED); - } - RefPointer wrap = new OSXCodeWrap(code); - RefPointer subject = new CodeSignatureAclSubject(OSXVerifier(wrap)); - SecurityServerAcl::addToStandardACL(context, subject); - return noErr; } // not close enough to even ask - this can't match return errSecCSReqFailed; } - - -// -// Perform legacy hash verification. -// This is the pre-Leopard (Tiger, Panther) code path. Here we only have legacy hashes -// (called, confusingly, "signatures"), which we're matching against suitably computed -// "signatures" (hashes) on the requesting application. We consult the CodeEquivalenceDatabase -// in a doomed attempt to track changes made to applications through updates, and issue -// equivalence dialogs to users if we have a name match (but hash mismatch). That's all -// there was before Code Signing; and that's what you'll continue to get if the requesting -// application is unsigned. Until we throw the whole mess out altogether, hopefully by -// the Next Big Cat After Leopard. -// -bool CodeSignatures::verifyLegacy(Process &process, const CssmData &signature, string path) -{ - // First of all, if the signature directly matches the client's code, we're obviously fine - // we don't even need the database for that... - Identity &clientIdentity = process; - try { - if (clientIdentity.getHash() == signature) { - secdebug("codesign", "direct match: pass"); - return true; - } - } catch (...) { - secdebug("codesign", "exception getting client code hash: fail"); - return false; - } - -#if CONSULT_LEGACY_CODE_EQUIVALENCE_DATABASE - - // Ah well. Establish mediator objects for database signature links - AclIdentity aclIdentity(signature, path); - - uid_t user = process.uid(); - { - StLock _(mDatabaseLock); - find(aclIdentity, user); - find(clientIdentity, user); - } - - // if both links exist, we can decide this right now - if (aclIdentity && clientIdentity) { - if (aclIdentity.trustedName() == clientIdentity.trustedName()) { - secdebug("codesign", "app references match: pass"); - return true; - } else { - secdebug("codesign", "client/acl links exist but are unequal: fail"); - return false; - } - } - - // check for name equality - secdebug("codesign", "matching client %s against acl %s", - clientIdentity.name().c_str(), aclIdentity.name().c_str()); - if (aclIdentity.name() != clientIdentity.name()) { - secdebug("codesign", "name/path mismatch: fail"); - return false; - } - - // The names match - we have a possible update. - - // Take the UI lock now to serialize "update rushes". - LongtermStLock uiLocker(mUILock); - - // re-read the database in case some other thread beat us to the update - { - StLock _(mDatabaseLock); - find(aclIdentity, user); - find(clientIdentity, user); - } - if (aclIdentity && clientIdentity) { - if (aclIdentity.trustedName() == clientIdentity.trustedName()) { - secdebug("codesign", "app references match: pass (on the rematch)"); - return true; - } else { - secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)"); - return false; - } - } - - // ask the user - QueryCodeCheck query; - query.inferHints(process); - if (!query(aclIdentity.path().c_str())) - { - secdebug("codesign", "user declined equivalence: cancel the access"); - CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED); - } - - // take the database lock back for real - StLock _(mDatabaseLock); - - // user wants us to go ahead and establish trust (if possible) - if (aclIdentity) { - // acl is linked but new client: link the client to this application - makeLink(clientIdentity, aclIdentity.trustedName(), true, user); - mDb.flush(); - secdebug("codesign", "client %s linked to application %s: pass", - clientIdentity.path().c_str(), aclIdentity.trustedName().c_str()); - return true; - } - - if (clientIdentity) { // code link exists, acl link missing - // client is linked but ACL (hash) never seen: link the ACL to this app - makeLink(aclIdentity, clientIdentity.trustedName(), true, user); - mDb.flush(); - secdebug("codesign", "acl %s linked to client %s: pass", - aclIdentity.path().c_str(), clientIdentity.trustedName().c_str()); - return true; - } - - // the De Novo case: no links, must create everything - string ident = clientIdentity.name(); - makeLink(clientIdentity, ident, true, user); - makeLink(aclIdentity, ident, true, user); - mDb.flush(); - secdebug("codesign", "new linkages established: pass"); - return true; - -#else /* ignore Code Equivalence Database */ - - return false; - -#endif -} - - -// -// Debug dumping support -// -#if defined(DEBUGDUMP) - -void CodeSignatures::debugDump(const char *how) const -{ - using namespace Debug; - using namespace LowLevelMemoryUtilities; - if (!how) - how = "dump"; - CssmData key, value; - if (!mDb.first(key, value)) { - dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how); - } else { - dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how); - do { - const char *header = key.interpretedAs(); - size_t headerLength = strlen(header) + 1; - dump("%s:", header); - dumpData(key.at(headerLength), key.length() - headerLength); - dump(" => "); - dumpData(value); - dump("\n"); - } while (mDb.next(key, value)); - dump("END DUMP\n"); - } -} - -void CodeSignatures::Identity::debugDump(const char *how) const -{ - using namespace Debug; - if (!how) - how = "dump"; - dump("IDENTITY (%s) path=%s", how, getPath().c_str()); - dump(" name=%s hash=", mName.empty() ? "(unset)" : mName.c_str()); - dumpData(getHash()); - dump("\n"); -} - -#endif //DEBUGDUMP