From 90dc47c27df1983f6ebc252b0c4b94c8718fe52d Mon Sep 17 00:00:00 2001 From: Apple Date: Thu, 30 Aug 2018 17:29:35 +0000 Subject: [PATCH] Security-58286.70.7.tar.gz --- OSX/authd/engine.c | 23 +- OSX/libsecurity_codesigning/lib/CSCommon.h | 6 + .../lib/CSCommonPriv.h | 4 +- .../lib/CodeSigner.cpp | 60 ++++- OSX/libsecurity_codesigning/lib/CodeSigner.h | 1 + OSX/libsecurity_codesigning/lib/SecCode.cpp | 12 +- OSX/libsecurity_codesigning/lib/SecCode.h | 1 + OSX/libsecurity_codesigning/lib/SecCodePriv.h | 6 +- .../lib/SecCodeSigner.cpp | 5 +- .../lib/SecCodeSigner.h | 13 +- .../lib/SecStaticCode.cpp | 1 + .../lib/SecStaticCode.h | 1 + .../lib/StaticCode.cpp | 133 ++++++++-- OSX/libsecurity_codesigning/lib/StaticCode.h | 8 +- .../lib/bundlediskrep.cpp | 1 + OSX/libsecurity_codesigning/lib/cdbuilder.cpp | 42 +++- OSX/libsecurity_codesigning/lib/cdbuilder.h | 17 ++ .../lib/codedirectory.cpp | 37 ++- .../lib/codedirectory.h | 43 +++- OSX/libsecurity_codesigning/lib/machorep.cpp | 100 ++++---- .../lib/piddiskrep.cpp | 2 +- OSX/libsecurity_codesigning/lib/sigblob.cpp | 15 ++ OSX/libsecurity_codesigning/lib/sigblob.h | 15 ++ OSX/libsecurity_codesigning/lib/signer.cpp | 229 +++++++++++++++--- OSX/libsecurity_codesigning/lib/signer.h | 32 ++- .../lib/signerutils.cpp | 59 ++++- OSX/libsecurity_codesigning/lib/signerutils.h | 13 +- OSX/libsecurity_utilities/lib/macho++.cpp | 55 +++++ OSX/libsecurity_utilities/lib/macho++.h | 9 +- .../secitem/si-28-sectrustsettings.m | 40 ++- OSX/sec/Security/SecExports.exp-in | 1 + OSX/sec/securityd/SecPolicyServer.c | 7 + Security.exp-in | 14 +- securityd/src/csproxy.cpp | 42 +++- securityd/src/csproxy.h | 9 +- 35 files changed, 856 insertions(+), 200 deletions(-) diff --git a/OSX/authd/engine.c b/OSX/authd/engine.c index 2a3de5c0..848a0b8f 100644 --- a/OSX/authd/engine.c +++ b/OSX/authd/engine.c @@ -496,7 +496,7 @@ _evaluate_mechanisms(engine_t engine, CFArrayRef mechanisms) // otherwise we need to check la_result if (auth_items_exist(engine->context, AGENT_CONTEXT_AP_PAM_SERVICE_NAME) || auth_items_exist(engine->context, kAuthorizationEnvironmentPassword)) { // do not try to get credentials as it has been already passed by sheet - os_log(AUTHD_LOG, "engine: ingoring builtin sheet authenticate"); + os_log(AUTHD_LOG, "engine: ignoring builtin sheet authenticate"); } else { // sheet itself did the authenticate the user os_log(AUTHD_LOG, "engine: running builtin sheet authenticate"); @@ -1376,6 +1376,14 @@ OSStatus engine_authorize(engine_t engine, auth_rights_t rights, auth_items_t en auth_items_copy(engine->hints, environment); } + // First restore all context values from the AuthorizationRef + auth_items_t decrypted_items = auth_items_create(); + require_action(decrypted_items != NULL, done, os_log_error(AUTHD_LOG, "engine: enable to create items")); + auth_items_content_copy(decrypted_items, auth_token_get_context(engine->auth)); + auth_items_decrypt(decrypted_items, auth_token_get_encryption_key(engine->auth)); + auth_items_copy(engine->context, decrypted_items); + CFReleaseSafe(decrypted_items); + if (engine->flags & kAuthorizationFlagSheet) { CFTypeRef extract_password_entitlement = auth_token_copy_entitlement_value(engine->auth, "com.apple.authorization.extract-password"); if (extract_password_entitlement && (CFGetTypeID(extract_password_entitlement) == CFBooleanGetTypeID()) && extract_password_entitlement == kCFBooleanTrue) { @@ -1390,7 +1398,11 @@ OSStatus engine_authorize(engine_t engine, auth_rights_t rights, auth_items_t en if (!enforced_entitlement()) { save_password = true; } - const char *user = auth_items_get_string(environment, kAuthorizationEnvironmentUsername); + + // Try to use/update fresh context values from the environment + require_action(environment, done, os_log_debug(AUTHD_LOG, "engine: Missing environment for sheet authorization"); status = errAuthorizationDenied); + + const char *user = auth_items_get_string(environment, kAuthorizationEnvironmentUsername); require_action(user, done, os_log_debug(AUTHD_LOG, "engine: Missing username"); status = errAuthorizationDenied); auth_items_set_string(engine->context, kAuthorizationEnvironmentUsername, user); @@ -1425,13 +1437,6 @@ OSStatus engine_authorize(engine_t engine, auth_rights_t rights, auth_items_t en _extract_password_from_la(engine); engine->preauthorizing = true; } - - auth_items_t decrypted_items = auth_items_create(); - require_action(decrypted_items != NULL, done, os_log_error(AUTHD_LOG, "engine: enable to create items")); - auth_items_content_copy(decrypted_items, auth_token_get_context(engine->auth)); - auth_items_decrypt(decrypted_items, auth_token_get_encryption_key(engine->auth)); - auth_items_copy(engine->context, decrypted_items); - CFReleaseSafe(decrypted_items); engine->dismissed = false; auth_rights_clear(engine->grantedRights); diff --git a/OSX/libsecurity_codesigning/lib/CSCommon.h b/OSX/libsecurity_codesigning/lib/CSCommon.h index 3ede88d9..22129bcb 100644 --- a/OSX/libsecurity_codesigning/lib/CSCommon.h +++ b/OSX/libsecurity_codesigning/lib/CSCommon.h @@ -123,6 +123,8 @@ CF_ENUM(OSStatus) { errSecCSBadTeamIdentifier = -66997, /* a Team Identifier is wrong or inappropriate */ errSecCSSignatureUntrusted = -66996, /* signature is valid but signer is not trusted */ errSecMultipleExecSegments = -66995, /* the image contains multiple executable segments */ + errSecCSInvalidEntitlements = -66994, /* invalid entitlement plist */ + errSecCSInvalidRuntimeVersion = -66993, /* an invalid runtime version was explicitly set */ }; /* @@ -245,6 +247,9 @@ typedef CF_OPTIONS(uint32_t, SecCSFlags) { immediately if it becomes invalid. @constant kSecCodeSignatureForceExpiration Forces the kSecCSConsiderExpiration flag on all validations of the code. + @constant kSecCodeSignatureRuntime + Instructs the kernel to apply runtime hardening policies as required by the + hardened runtime version */ typedef CF_OPTIONS(uint32_t, SecCodeSignatureFlags) { kSecCodeSignatureHost = 0x0001, /* may host guest code */ @@ -255,6 +260,7 @@ typedef CF_OPTIONS(uint32_t, SecCodeSignatureFlags) { kSecCodeSignatureRestrict = 0x0800, /* restrict dyld loading */ kSecCodeSignatureEnforcement = 0x1000, /* enforce code signing */ kSecCodeSignatureLibraryValidation = 0x2000, /* library validation required */ + kSecCodeSignatureRuntime = 0x10000, /* apply runtime hardening policies */ }; /*! diff --git a/OSX/libsecurity_codesigning/lib/CSCommonPriv.h b/OSX/libsecurity_codesigning/lib/CSCommonPriv.h index d527dd37..0bb82021 100644 --- a/OSX/libsecurity_codesigning/lib/CSCommonPriv.h +++ b/OSX/libsecurity_codesigning/lib/CSCommonPriv.h @@ -71,6 +71,7 @@ extern const SecCodeDirectoryFlagTable kSecCodeDirectoryFlagTable[]; data that is usually written to separate files. This is the format of detached signatures if the program is capable of having multiple architectures. @constant kSecCodeMagicEntitlement Magic number for a standard entitlement blob. + @constant kSecCodeMagicEntitlementDER Magic number for a DER entitlement blob. @constant kSecCodeMagicByte The first byte (in NBO) shared by all these magic numbers. This is not a valid ASCII character; test for this to distinguish between text and binary data if you expect a code signing-related binary blob. @@ -83,7 +84,8 @@ enum { kSecCodeMagicEmbeddedSignature = 0xfade0cc0, /* single-architecture embedded signature */ kSecCodeMagicDetachedSignature = 0xfade0cc1, /* detached multi-architecture signature */ kSecCodeMagicEntitlement = 0xfade7171, /* entitlement blob */ - + kSecCodeMagicEntitlementDER = 0xfade7172, /* entitlement DER blob */ + kSecCodeMagicByte = 0xfa /* shared first byte */ }; diff --git a/OSX/libsecurity_codesigning/lib/CodeSigner.cpp b/OSX/libsecurity_codesigning/lib/CodeSigner.cpp index ee8045f9..b6b274e2 100644 --- a/OSX/libsecurity_codesigning/lib/CodeSigner.cpp +++ b/OSX/libsecurity_codesigning/lib/CodeSigner.cpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace Security { @@ -58,6 +59,53 @@ public: else return false; } + + uint32_t parseRuntimeVersion(std::string& runtime) + { + uint32_t version = 0; + char* cursor = const_cast(runtime.c_str()); + char* end = cursor + runtime.length(); + char* nxt = NULL; + long component = 0; + int component_shift = 16; + + // x should convert to 0x00XX0000 + // x.y should convert to 0x00XXYY00 + // x.y.z should covert to 0x00XXYYZZ + // 0, 0.0, and 0.0.0 are rejected + // anything else should be rejected + while (cursor < end) { + nxt = NULL; + errno = 0; + component = strtol(cursor, &nxt, 10); + if (cursor == nxt || + (errno != 0) || + (component < 0 || component > UINT8_MAX)) { + secdebug("signer", "Runtime version: %s is invalid", runtime.c_str()); + MacOSError::throwMe(errSecCSInvalidRuntimeVersion); + } + version |= (component & 0xff) << component_shift; + component_shift -= 8; + + if (*nxt == '\0') { + break; + } + + if (*nxt != '.' || component_shift < 0 || (nxt + 1) == end) { + // Catch a trailing "." + secdebug("signer", "Runtime version: %s is invalid", runtime.c_str()); + MacOSError::throwMe(errSecCSInvalidRuntimeVersion); + } + cursor = nxt + 1; + } + + if (version == 0) { + secdebug("signer","Runtime version: %s is a version of zero", runtime.c_str()); + MacOSError::throwMe(errSecCSInvalidRuntimeVersion); + } + + return version; + } }; @@ -65,7 +113,7 @@ public: // Construct a SecCodeSigner // SecCodeSigner::SecCodeSigner(SecCSFlags flags) - : mOpFlags(flags), mLimitedAsync(NULL) + : mOpFlags(flags), mLimitedAsync(NULL), mRuntimeVersionOverride(0) { } @@ -204,7 +252,7 @@ SecCodeSigner::Parser::Parser(SecCodeSigner &state, CFDictionaryRef parameters) if (CFNumberRef cmsSize = get(CFSTR("cmssize"))) state.mCMSSize = cfNumber(cmsSize); else - state.mCMSSize = 9000; // likely big enough + state.mCMSSize = 18000; // big enough for now, not forever. // metadata preservation options if (CFNumberRef preserve = get(kSecCodeSignerPreserveMetadata)) { @@ -280,6 +328,14 @@ SecCodeSigner::Parser::Parser(SecCodeSigner &state, CFDictionaryRef parameters) state.mTimestampAuthentication = get(kSecCodeSignerTimestampAuthentication); state.mTimestampService = get(kSecCodeSignerTimestampServer); state.mNoTimeStampCerts = getBool(kSecCodeSignerTimestampOmitCertificates); + + if (CFStringRef runtimeVersionOverride = get(kSecCodeSignerRuntimeVersion)) { + std::string runtime = cfString(runtimeVersionOverride); + if (runtime.empty()) { + MacOSError::throwMe(errSecCSInvalidRuntimeVersion); + } + state.mRuntimeVersionOverride = parseRuntimeVersion(runtime); + } } diff --git a/OSX/libsecurity_codesigning/lib/CodeSigner.h b/OSX/libsecurity_codesigning/lib/CodeSigner.h index e9e41d28..7cb3b6d3 100644 --- a/OSX/libsecurity_codesigning/lib/CodeSigner.h +++ b/OSX/libsecurity_codesigning/lib/CodeSigner.h @@ -93,6 +93,7 @@ public: bool mWantTimeStamp; // use a Timestamp server bool mNoTimeStampCerts; // don't request certificates with timestamping request LimitedAsync *mLimitedAsync; // limited async workers for verification + uint32_t mRuntimeVersionOverride; // runtime Version Override }; diff --git a/OSX/libsecurity_codesigning/lib/SecCode.cpp b/OSX/libsecurity_codesigning/lib/SecCode.cpp index 3d918e4b..47d1cc16 100644 --- a/OSX/libsecurity_codesigning/lib/SecCode.cpp +++ b/OSX/libsecurity_codesigning/lib/SecCode.cpp @@ -214,7 +214,8 @@ OSStatus SecCodeCheckValidityWithErrors(SecCodeRef codeRef, SecCSFlags flags, kSecCSConsiderExpiration | kSecCSStrictValidate | kSecCSRestrictSidebandData - | kSecCSEnforceRevocationChecks); + | kSecCSEnforceRevocationChecks + ); SecPointer code = SecCode::required(codeRef); code->checkValidity(flags); if (const SecRequirement *req = SecRequirement::optional(requirementRef)) @@ -257,6 +258,7 @@ const CFStringRef kSecCodeInfoTimestamp = CFSTR("signing-timestamp"); const CFStringRef kSecCodeInfoTrust = CFSTR("trust"); const CFStringRef kSecCodeInfoUnique = CFSTR("unique"); const CFStringRef kSecCodeInfoCdHashes = CFSTR("cdhashes"); +const CFStringRef kSecCodeInfoRuntimeVersion = CFSTR("runtime-version"); const CFStringRef kSecCodeInfoCodeDirectory = CFSTR("CodeDirectory"); @@ -265,10 +267,10 @@ const CFStringRef kSecCodeInfoDiskRepInfo = CFSTR("DiskRepInfo"); const CFStringRef kSecCodeInfoResourceDirectory = CFSTR("ResourceDirectory"); /* DiskInfoRepInfo types */ -const CFStringRef kSecCodeInfoDiskRepOSPlatform = CFSTR("OSPlatform"); -const CFStringRef kSecCodeInfoDiskRepOSVersionMin = CFSTR("OSVersionMin"); -const CFStringRef kSecCodeInfoDiskRepOSSDKVersion = CFSTR("SDKVersion"); -const CFStringRef kSecCodeInfoDiskRepNoLibraryValidation = CFSTR("NoLibraryValidation"); +const CFStringRef kSecCodeInfoDiskRepVersionPlatform = CFSTR("VersionPlatform"); +const CFStringRef kSecCodeInfoDiskRepVersionMin = CFSTR("VersionMin"); +const CFStringRef kSecCodeInfoDiskRepVersionSDK = CFSTR("VersionSDK"); +const CFStringRef kSecCodeInfoDiskRepNoLibraryValidation = CFSTR("NoLibraryValidation"); OSStatus SecCodeCopySigningInformation(SecStaticCodeRef codeRef, SecCSFlags flags, diff --git a/OSX/libsecurity_codesigning/lib/SecCode.h b/OSX/libsecurity_codesigning/lib/SecCode.h index 20ba29f4..b1a6afbc 100644 --- a/OSX/libsecurity_codesigning/lib/SecCode.h +++ b/OSX/libsecurity_codesigning/lib/SecCode.h @@ -455,6 +455,7 @@ extern const CFStringRef kSecCodeInfoTimestamp; /* Signing */ extern const CFStringRef kSecCodeInfoTrust; /* Signing */ extern const CFStringRef kSecCodeInfoUnique; /* generic */ extern const CFStringRef kSecCodeInfoCdHashes; /* generic */ +extern const CFStringRef kSecCodeInfoRuntimeVersion; /*generic */ OSStatus SecCodeCopySigningInformation(SecStaticCodeRef code, SecCSFlags flags, CFDictionaryRef * __nonnull CF_RETURNS_RETAINED information); diff --git a/OSX/libsecurity_codesigning/lib/SecCodePriv.h b/OSX/libsecurity_codesigning/lib/SecCodePriv.h index 7faa3634..b610af86 100644 --- a/OSX/libsecurity_codesigning/lib/SecCodePriv.h +++ b/OSX/libsecurity_codesigning/lib/SecCodePriv.h @@ -45,9 +45,9 @@ extern const CFStringRef kSecCodeInfoCodeOffset; /* Internal */ extern const CFStringRef kSecCodeInfoDiskRepInfo; /* Internal */ extern const CFStringRef kSecCodeInfoResourceDirectory; /* Internal */ -extern const CFStringRef kSecCodeInfoDiskRepOSPlatform; /* Number */ -extern const CFStringRef kSecCodeInfoDiskRepOSVersionMin; /* Number */ -extern const CFStringRef kSecCodeInfoDiskRepOSSDKVersion; /* Number */ +extern const CFStringRef kSecCodeInfoDiskRepVersionPlatform; /* Number */ +extern const CFStringRef kSecCodeInfoDiskRepVersionMin; /* Number */ +extern const CFStringRef kSecCodeInfoDiskRepVersionSDK; /* Number */ extern const CFStringRef kSecCodeInfoDiskRepNoLibraryValidation; /* String */ /*! diff --git a/OSX/libsecurity_codesigning/lib/SecCodeSigner.cpp b/OSX/libsecurity_codesigning/lib/SecCodeSigner.cpp index 7d83ce7a..f043523f 100644 --- a/OSX/libsecurity_codesigning/lib/SecCodeSigner.cpp +++ b/OSX/libsecurity_codesigning/lib/SecCodeSigner.cpp @@ -59,6 +59,7 @@ const CFStringRef kSecCodeSignerTimestampOmitCertificates = CFSTR("timestamp-omi const CFStringRef kSecCodeSignerPreserveMetadata = CFSTR("preserve-metadata"); const CFStringRef kSecCodeSignerTeamIdentifier = CFSTR("teamidentifier"); const CFStringRef kSecCodeSignerPlatformIdentifier = CFSTR("platform-identifier"); +const CFStringRef kSecCodeSignerRuntimeVersion = CFSTR("runtime-version"); @@ -89,7 +90,9 @@ OSStatus SecCodeSignerCreate(CFDictionaryRef parameters, SecCSFlags flags, | kSecCSSignV1 | kSecCSSignNoV1 | kSecCSSignBundleRoot - | kSecCSSignStrictPreflight); + | kSecCSSignStrictPreflight + | kSecCSSignGeneratePEH + | kSecCSSignGenerateEntitlementDER); SecPointer signer = new SecCodeSigner(flags); signer->parameters(parameters); CodeSigning::Required(signerRef) = signer->handle(); diff --git a/OSX/libsecurity_codesigning/lib/SecCodeSigner.h b/OSX/libsecurity_codesigning/lib/SecCodeSigner.h index 7b3704b2..88067c14 100644 --- a/OSX/libsecurity_codesigning/lib/SecCodeSigner.h +++ b/OSX/libsecurity_codesigning/lib/SecCodeSigner.h @@ -135,6 +135,12 @@ CFTypeID SecCodeSignerGetTypeID(void); on the verifying system. The default is to embed enough certificates to ensure proper verification of Apple-generated timestamp signatures. + @constant kSecCodeSignerRuntimeVersion A CFString indicating the version of runtime hardening policies + that the process should be opted into. The string should be of the form "x", "x.x", or "x.x.x" where + x is a number between 0 and 255. This parameter is optional. If the signer specifies + kSecCodeSignatureRuntime but does not provide this parameter, the runtime version will be the SDK + version built into the Mach-O. + */ extern const CFStringRef kSecCodeSignerApplicationData; extern const CFStringRef kSecCodeSignerDetached; @@ -157,6 +163,7 @@ extern const CFStringRef kSecCodeSignerTimestampOmitCertificates; extern const CFStringRef kSecCodeSignerPreserveMetadata; extern const CFStringRef kSecCodeSignerTeamIdentifier; extern const CFStringRef kSecCodeSignerPlatformIdentifier; +extern const CFStringRef kSecCodeSignerRuntimeVersion; enum { kSecCodeSignerPreserveIdentifier = 1 << 0, // preserve signing identifier @@ -165,7 +172,9 @@ enum { kSecCodeSignerPreserveResourceRules = 1 << 3, // preserve resource rules (and thus resources) kSecCodeSignerPreserveFlags = 1 << 4, // preserve signing flags kSecCodeSignerPreserveTeamIdentifier = 1 << 5, // preserve team identifier flags - kSecCodeSignerPreserveDigestAlgorithm = 1 << 6, // preserve digest algorithms used + kSecCodeSignerPreserveDigestAlgorithm = 1 << 6, // preserve digest algorithms used + kSecCodeSignerPreservePEH = 1 << 7, // preserve pre-encryption hashes + kSecCodeSignerPreserveRuntime = 1 << 8, // preserve the runtime version }; @@ -194,6 +203,8 @@ enum { kSecCSSignNoV1 = 1 << 5, // do not include V1 form kSecCSSignBundleRoot = 1 << 6, // include files in bundle root kSecCSSignStrictPreflight = 1 << 7, // fail signing operation if signature would fail strict validation + kSecCSSignGeneratePEH = 1 << 8, // generate pre-encryption hashes + kSecCSSignGenerateEntitlementDER = 1 << 9, // generate entitlement DER }; diff --git a/OSX/libsecurity_codesigning/lib/SecStaticCode.cpp b/OSX/libsecurity_codesigning/lib/SecStaticCode.cpp index 3d96abd0..7a93ee24 100644 --- a/OSX/libsecurity_codesigning/lib/SecStaticCode.cpp +++ b/OSX/libsecurity_codesigning/lib/SecStaticCode.cpp @@ -122,6 +122,7 @@ OSStatus SecStaticCodeCheckValidityWithErrors(SecStaticCodeRef staticCodeRef, Se | kSecCSRestrictSymlinks | kSecCSRestrictToAppLike | kSecCSUseSoftwareSigningCert + | kSecCSValidatePEH ); if (errors) diff --git a/OSX/libsecurity_codesigning/lib/SecStaticCode.h b/OSX/libsecurity_codesigning/lib/SecStaticCode.h index 5fdb98b7..1183adca 100644 --- a/OSX/libsecurity_codesigning/lib/SecStaticCode.h +++ b/OSX/libsecurity_codesigning/lib/SecStaticCode.h @@ -179,6 +179,7 @@ CF_ENUM(uint32_t) { kSecCSRestrictToAppLike = 1 << 8, kSecCSRestrictSidebandData = 1 << 9, kSecCSUseSoftwareSigningCert = 1 << 10, + kSecCSValidatePEH = 1 << 11, }; OSStatus SecStaticCodeCheckValidity(SecStaticCodeRef staticCode, SecCSFlags flags, diff --git a/OSX/libsecurity_codesigning/lib/StaticCode.cpp b/OSX/libsecurity_codesigning/lib/StaticCode.cpp index ff621d53..059a8c00 100644 --- a/OSX/libsecurity_codesigning/lib/StaticCode.cpp +++ b/OSX/libsecurity_codesigning/lib/StaticCode.cpp @@ -35,6 +35,7 @@ #include "sigblob.h" #include "resources.h" #include "detachedrep.h" +#include "signerutils.h" #if TARGET_OS_OSX #include "csdatabase.h" #endif @@ -343,6 +344,7 @@ void SecStaticCode::resetValidity() mResourcesValidContext = NULL; } mDir = NULL; + mCodeDirectories.clear(); mSignature = NULL; for (unsigned n = 0; n < cdSlotCount; n++) mCache[n] = NULL; @@ -384,7 +386,7 @@ CFDataRef SecStaticCode::component(CodeDirectory::SpecialSlot slot, OSStatus fai return NULL; if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), // ... and it's no good - CFDataGetLength(data), -slot)) + CFDataGetLength(data), -slot, false)) MacOSError::throwMe(errorForSlot(slot)); // ... then bail } cache = data; // it's okay, cache it @@ -399,6 +401,41 @@ CFDataRef SecStaticCode::component(CodeDirectory::SpecialSlot slot, OSStatus fai } +// +// Get the CodeDirectories. +// Throws (if check==true) or returns NULL (check==false) if there are none. +// Always throws if the CodeDirectories exist but are invalid. +// NEVER validates against the signature. +// +const SecStaticCode::CodeDirectoryMap * +SecStaticCode::codeDirectories(bool check /* = true */) const +{ + if (mCodeDirectories.empty()) { + try { + loadCodeDirectories(mCodeDirectories); + } catch (...) { + if (check) + throw; + // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectories. + // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory. + if (!mCodeDirectories.empty()) { + assert(false); + Syslog::warning("code signing internal problem: mCodeDirectories set despite exception exit"); + MacOSError::throwMe(errSecCSInternalError); + } + } + } else { + return &mCodeDirectories; + } + if (!mCodeDirectories.empty()) { + return &mCodeDirectories; + } + if (check) { + MacOSError::throwMe(errSecCSUnsigned); + } + return NULL; +} + // // Get the CodeDirectory. // Throws (if check==true) or returns NULL (check==false) if there is none. @@ -410,11 +447,10 @@ const CodeDirectory *SecStaticCode::codeDirectory(bool check /* = true */) const if (!mDir) { // pick our favorite CodeDirectory from the choices we've got try { - CodeDirectoryMap candidates; - if (loadCodeDirectories(candidates)) { + CodeDirectoryMap const *candidates = codeDirectories(check); + if (candidates != NULL) { CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms); - mDir = candidates[type]; // and the winner is... - candidates.swap(mCodeDirectories); + mDir = candidates->at(type); // and the winner is... } } catch (...) { if (check) @@ -689,14 +725,17 @@ bool SecStaticCode::verifySignature() MacOSError::throwMe(errSecCSSignatureFailed); } - // retrieve auxiliary data bag and verify against current state - CFRef hashBag; - switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgility(cms, 0, &hashBag.aref())) { + // retrieve auxiliary v1 data bag and verify against current state + CFRef hashAgilityV1; + switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgility(cms, 0, &hashAgilityV1.aref())) { case noErr: - if (hashBag) { - CFRef hashDict = makeCFDictionaryFrom(hashBag); + if (hashAgilityV1) { + CFRef hashDict = makeCFDictionaryFrom(hashAgilityV1); CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes"))); CFArrayRef myCdList = this->cdHashes(); + + /* Note that this is not very "agile": There's no way to calculate the exact + * list for comparison if it contains hash algorithms we don't know yet... */ if (cdList == NULL || !CFEqual(cdList, myCdList)) MacOSError::throwMe(errSecCSSignatureFailed); } @@ -707,6 +746,62 @@ bool SecStaticCode::verifySignature() MacOSError::throwMe(rc); } + // retrieve auxiliary v2 data bag and verify against current state + CFRef hashAgilityV2; + switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgilityV2(cms, 0, &hashAgilityV2.aref())) { + case noErr: + if (hashAgilityV2) { + /* Require number of code directoris and entries in the hash agility + * dict to be the same size (no stripping out code directories). + */ + if (CFDictionaryGetCount(hashAgilityV2) != mCodeDirectories.size()) { + MacOSError::throwMe(errSecCSSignatureFailed); + } + + /* Require every cdhash of every code directory whose hash + * algorithm we know to be in the agility dictionary. + * + * We check untruncated cdhashes here because we can. + */ + bool foundOurs = false; + for (auto& entry : mCodeDirectories) { + SECOidTag tag = CodeDirectorySet::SECOidTagForAlgorithm(entry.first); + + if (tag == SEC_OID_UNKNOWN) { + // Unknown hash algorithm, ignore. + continue; + } + + CFRef key = makeCFNumber(int(tag)); + CFRef entryCdhash; + entryCdhash = (CFDataRef)CFDictionaryGetValue(hashAgilityV2, (void*)key.get()); + + CodeDirectory const *cd = (CodeDirectory const*)CFDataGetBytePtr(entry.second); + CFRef ourCdhash = cd->cdhash(false); // Untruncated cdhash! + if (!CFEqual(entryCdhash, ourCdhash)) { + MacOSError::throwMe(errSecCSSignatureFailed); + } + + if (entry.first == this->hashAlgorithm()) { + foundOurs = true; + } + } + + /* Require the cdhash of our chosen code directory to be in the dictionary. + * In theory, the dictionary could be full of unsupported cdhashes, but we + * really want ours, which is bound to be supported, to be covered. + */ + if (!foundOurs) { + MacOSError::throwMe(errSecCSSignatureFailed); + } + } + break; + case -1: /* CMS used to return this for "no attribute found", so tolerate it. Now returning noErr/NULL */ + break; + default: + MacOSError::throwMe(rc); + } + // internal signing time (as specified by the signer; optional) mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-) switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) { @@ -974,7 +1069,7 @@ void SecStaticCode::validateComponent(CodeDirectory::SpecialSlot slot, OSStatus if (codeDirectory()->slotIsPresent(-slot)) // was supposed to be there... MacOSError::throwMe(fail); // ... and is missing } else { - if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot)) + if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot, false)) MacOSError::throwMe(fail); } } @@ -1009,7 +1104,8 @@ void SecStaticCode::validateExecutable() __block bool good = true; CodeDirectory::multipleHashFileData(fd, thisPage, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) { const CodeDirectory* cd = (const CodeDirectory*)CFDataGetBytePtr(mCodeDirectories[type]); - if (!hasher->verify((*cd)[slot])) + if (!hasher->verify(cd->getSlot(slot, + mValidationFlags & kSecCSValidatePEH))) good = false; }); if (!good) { @@ -1292,7 +1388,7 @@ CFDataRef SecStaticCode::copyComponent(CodeDirectory::SpecialSlot slot, CFDataRe const CodeDirectory* cd = this->codeDirectory(); if (CFCopyRef component = this->component(slot)) { if (hash) { - const void *slotHash = (*cd)[slot]; + const void *slotHash = cd->getSlot(slot, false); if (cd->hashSize != CFDataGetLength(hash) || 0 != memcmp(slotHash, CFDataGetBytePtr(hash), cd->hashSize)) { Syslog::notice("copyComponent hash mismatch slot %d length %d", slot, int(CFDataGetLength(hash))); return NULL; // mismatch @@ -1358,12 +1454,12 @@ bool SecStaticCode::checkfix30814861(string path, bool addition) { CFRef inf = diskRepInformation(); try { CFDictionary info(diskRepInformation(), errSecCSNotSupported); - uint32_t platformCmd = - cfNumber(info.get(kSecCodeInfoDiskRepOSPlatform, errSecCSNotSupported), 0); + uint32_t platform = + cfNumber(info.get(kSecCodeInfoDiskRepVersionPlatform, errSecCSNotSupported), 0); uint32_t sdkVersion = - cfNumber(info.get(kSecCodeInfoDiskRepOSSDKVersion, errSecCSNotSupported), 0); + cfNumber(info.get(kSecCodeInfoDiskRepVersionSDK, errSecCSNotSupported), 0); - if (platformCmd != LC_VERSION_MIN_IPHONEOS || sdkVersion >= 0x00090000) { + if (platform != PLATFORM_IOS || sdkVersion >= 0x00090000) { return false; } } catch (const MacOSError &error) { @@ -1834,6 +1930,9 @@ CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags) CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithms, digests); if (cd->platform) CFDictionaryAddValue(dict, kSecCodeInfoPlatformIdentifier, CFTempNumber(cd->platform)); + if (cd->runtimeVersion()) { + CFDictionaryAddValue(dict, kSecCodeInfoRuntimeVersion, CFTempNumber(cd->runtimeVersion())); + } // // Deliver any Info.plist only if it looks intact diff --git a/OSX/libsecurity_codesigning/lib/StaticCode.h b/OSX/libsecurity_codesigning/lib/StaticCode.h index 46dfbd2e..71ead90f 100644 --- a/OSX/libsecurity_codesigning/lib/StaticCode.h +++ b/OSX/libsecurity_codesigning/lib/StaticCode.h @@ -117,7 +117,10 @@ public: void detachedSignature(CFDataRef sig); // attach an explicitly given detached signature void checkForSystemSignature(); // check for and attach system-supplied detached signature + typedef std::map > CodeDirectoryMap; + const CodeDirectory *codeDirectory(bool check = true) const; + const CodeDirectoryMap *codeDirectories(bool check = true) const; CodeDirectory::HashAlgorithm hashAlgorithm() const { return codeDirectory()->hashType; } CodeDirectory::HashAlgorithms hashAlgorithms() const { return mHashAlgorithms; } CFDataRef cdHash(); @@ -200,12 +203,13 @@ public: bool trustedSigningCertChain() { return mTrustedSigningCertChain; } #endif + void handleOtherArchitectures(void (^handle)(SecStaticCode* other)); + public: void staticValidate(SecCSFlags flags, const SecRequirement *req); void staticValidateCore(SecCSFlags flags, const SecRequirement *req); protected: - typedef std::map > CodeDirectoryMap; bool loadCodeDirectories(CodeDirectoryMap& cdMap) const; protected: @@ -220,8 +224,6 @@ protected: static void checkOptionalResource(CFTypeRef key, CFTypeRef value, void *context); bool hasWeakResourceRules(CFDictionaryRef rulesDict, uint32_t version, CFArrayRef allowedOmissions); - void handleOtherArchitectures(void (^handle)(SecStaticCode* other)); - private: void validateOtherVersions(CFURLRef path, SecCSFlags flags, SecRequirementRef req, SecStaticCode *code); bool checkfix30814861(string path, bool addition); diff --git a/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp b/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp index 8ec8852c..8b6a67b9 100644 --- a/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp +++ b/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp @@ -471,6 +471,7 @@ CFArrayRef BundleDiskRep::modifiedFiles() checkModifiedFile(files, cdResourceDirSlot); checkModifiedFile(files, cdTopDirectorySlot); checkModifiedFile(files, cdEntitlementSlot); + checkModifiedFile(files, cdEntitlementDERSlot); checkModifiedFile(files, cdRepSpecificSlot); for (CodeDirectory::Slot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; ++slot) checkModifiedFile(files, slot); diff --git a/OSX/libsecurity_codesigning/lib/cdbuilder.cpp b/OSX/libsecurity_codesigning/lib/cdbuilder.cpp index babfe29c..e3c4240d 100644 --- a/OSX/libsecurity_codesigning/lib/cdbuilder.cpp +++ b/OSX/libsecurity_codesigning/lib/cdbuilder.cpp @@ -50,6 +50,8 @@ CodeDirectory::Builder::Builder(HashAlgorithm digestAlgorithm) mExecSegOffset(0), mExecSegLimit(0), mExecSegFlags(0), + mGeneratePreEncryptHashes(false), + mRuntimeVersion(0), mDir(NULL) { mDigestLength = (uint32_t)MakeHash(this)->digestLength(); @@ -121,6 +123,8 @@ CodeDirectory::Scatter *CodeDirectory::Builder::scatter(unsigned count) size_t CodeDirectory::Builder::fixedSize(const uint32_t version) { size_t cdSize = sizeof(CodeDirectory); + if (version < supportsPreEncrypt) + cdSize -= sizeof(mDir->runtime) + sizeof(mDir->preEncryptOffset); if (version < supportsExecSegment) cdSize -= sizeof(mDir->execSegBase) + sizeof(mDir->execSegLimit) + sizeof(mDir->execSegFlags); if (version < supportsCodeLimit64) @@ -157,6 +161,11 @@ size_t CodeDirectory::Builder::size(const uint32_t version) if (mTeamID.size()) offset += mTeamID.size() + 1; // size of teamID (with null byte) offset += (mCodeSlots + mSpecialSlots) * mDigestLength; // hash vector + + if (mGeneratePreEncryptHashes || !mPreservedPreEncryptHashMap.empty()) { + offset += mCodeSlots * mDigestLength; + } + if (offset <= offset0) UnixError::throwMe(ENOEXEC); @@ -187,8 +196,10 @@ CodeDirectory *CodeDirectory::Builder::build() size_t teamIDLength = mTeamID.size() + 1; // Determine the version - if (mExecSegLimit > 0) { + if (mGeneratePreEncryptHashes || !mPreservedPreEncryptHashMap.empty() || mRuntimeVersion) { version = currentVersion; + } else if (mExecSegLimit > 0) { + version = supportsExecSegment; } else if (mExecLength > UINT32_MAX) { version = supportsCodeLimit64; } else if (mTeamID.size()) { @@ -231,6 +242,7 @@ CodeDirectory *CodeDirectory::Builder::build() mDir->execSegBase = mExecSegOffset; mDir->execSegLimit = mExecSegLimit; mDir->execSegFlags = mExecSegFlags; + mDir->runtime = mRuntimeVersion; // locate and fill flex fields size_t offset = fixedSize(mDir->version); @@ -250,18 +262,27 @@ CodeDirectory *CodeDirectory::Builder::build() memcpy(mDir->teamID(), mTeamID.c_str(), teamIDLength); offset += teamIDLength; } + // (add new flexibly-allocated fields here) + /* Pre-encrypt hashes come before normal hashes, so that the kernel can free + * the normal, potentially post-encrypt hashes away easily. */ + if (mGeneratePreEncryptHashes || !mPreservedPreEncryptHashMap.empty()) { + mDir->preEncryptOffset = (uint32_t)offset; + offset += mCodeSlots * mDigestLength; + } + mDir->hashOffset = (uint32_t)(offset + mSpecialSlots * mDigestLength); offset += (mSpecialSlots + mCodeSlots) * mDigestLength; + assert(offset == total); // matches allocated size (void)offset; // fill special slots - memset((*mDir)[(int)-mSpecialSlots], 0, mDigestLength * mSpecialSlots); + memset(mDir->getSlotMutable((int)-mSpecialSlots, false), 0, mDigestLength * mSpecialSlots); for (size_t slot = 1; slot <= mSpecialSlots; ++slot) - memcpy((*mDir)[(int)-slot], specialSlot((SpecialSlot)slot), mDigestLength); + memcpy(mDir->getSlotMutable((int)-slot, false), specialSlot((SpecialSlot)slot), mDigestLength); // fill code slots mExec.seek(mExecOffset); @@ -271,10 +292,23 @@ CodeDirectory *CodeDirectory::Builder::build() if (mPageSize) thisPage = min(thisPage, mPageSize); MakeHash hasher(this); - generateHash(hasher, mExec, (*mDir)[slot], thisPage); + generateHash(hasher, mExec, mDir->getSlotMutable(slot, false), thisPage); + if (mGeneratePreEncryptHashes && mPreservedPreEncryptHashMap.empty()) { + memcpy(mDir->getSlotMutable(slot, true), mDir->getSlot(slot, false), + mDir->hashSize); + } remaining -= thisPage; } assert(remaining == 0); + + PreEncryptHashMap::iterator preEncrypt = + mPreservedPreEncryptHashMap.find(mHashType); + if (preEncrypt != mPreservedPreEncryptHashMap.end()) { + memcpy(mDir->getSlotMutable(0, true), + CFDataGetBytePtr(preEncrypt->second), + mCodeSlots * mDigestLength); + mPreservedPreEncryptHashMap.erase(preEncrypt->first); // Releases the CFData memory. + } // all done. Pass ownership to caller return mDir; diff --git a/OSX/libsecurity_codesigning/lib/cdbuilder.h b/OSX/libsecurity_codesigning/lib/cdbuilder.h index 761dcf63..7872fd58 100644 --- a/OSX/libsecurity_codesigning/lib/cdbuilder.h +++ b/OSX/libsecurity_codesigning/lib/cdbuilder.h @@ -64,6 +64,18 @@ public: mExecSegOffset = base; mExecSegLimit = limit; mExecSegFlags = flags; } void addExecSegFlags(uint64_t flags) { mExecSegFlags |= flags; } + typedef std::map > + PreEncryptHashMap; + + void generatePreEncryptHashes(bool pre) { mGeneratePreEncryptHashes = pre; } + void preservePreEncryptHashMap(PreEncryptHashMap preEncryptHashMap) { + mPreservedPreEncryptHashMap = preEncryptHashMap; + } + + void runTimeVersion(uint32_t runtime) { + mRuntimeVersion = runtime; + } + size_t size(const uint32_t version); // calculate size CodeDirectory *build(); // build CodeDirectory and return it size_t fixedSize(const uint32_t version); // calculate fixed size of the CodeDirectory @@ -102,6 +114,11 @@ private: uint64_t mExecSegLimit; // limit of executable segment uint64_t mExecSegFlags; // executable segment flags + bool mGeneratePreEncryptHashes; // whether to also generate new pre-encrypt hashes + PreEncryptHashMap mPreservedPreEncryptHashMap; // existing pre-encrypt hashes to be set + + uint32_t mRuntimeVersion; // Hardened Runtime Version + CodeDirectory *mDir; // what we're building }; diff --git a/OSX/libsecurity_codesigning/lib/codedirectory.cpp b/OSX/libsecurity_codesigning/lib/codedirectory.cpp index 1fc4fddd..33ae72ca 100644 --- a/OSX/libsecurity_codesigning/lib/codedirectory.cpp +++ b/OSX/libsecurity_codesigning/lib/codedirectory.cpp @@ -79,6 +79,8 @@ const char *CodeDirectory::canonicalSlotName(SpecialSlot slot) return kSecCS_TOPDIRECTORYFILE; case cdEntitlementSlot: return kSecCS_ENTITLEMENTFILE; + case cdEntitlementDERSlot: + return kSecCS_ENTITLEMENTDERFILE; case cdRepSpecificSlot: return kSecCS_REPSPECIFICFILE; default: @@ -105,6 +107,7 @@ unsigned CodeDirectory::slotAttributes(SpecialSlot slot) case cdSignatureSlot: return cdComponentPerArchitecture; // raw case cdEntitlementSlot: + case cdEntitlementDERSlot: return cdComponentIsBlob; // global case cdIdentificationSlot: return cdComponentPerArchitecture; // raw @@ -157,7 +160,9 @@ void CodeDirectory::checkIntegrity() const if (version > currentVersion) secinfo("codedir", "%p version 0x%x newer than current 0x%x", this, uint32_t(version), currentVersion); - + + bool hasPreEncryptHashes = version >= supportsPreEncrypt && preEncryptOffset != 0; + // now check interior offsets for validity if (!stringAt(identOffset)) MacOSError::throwMe(errSecCSSignatureFailed); // identifier out of blob range @@ -165,6 +170,8 @@ void CodeDirectory::checkIntegrity() const MacOSError::throwMe(errSecCSSignatureFailed); // identifier out of blob range if (!contains(hashOffset - int64_t(hashSize) * nSpecialSlots, hashSize * (int64_t(nSpecialSlots) + nCodeSlots))) MacOSError::throwMe(errSecCSSignatureFailed); // hash array out of blob range + if (hasPreEncryptHashes && !contains(preEncryptOffset, hashSize * (int64_t(nCodeSlots)))) + MacOSError::throwMe(errSecCSSignatureFailed); // pre-encrypt array out of blob range if (const Scatter *scatter = this->scatterVector()) { // the optional scatter vector is terminated with an element having (count == 0) unsigned int pagesConsumed = 0; @@ -175,7 +182,8 @@ void CodeDirectory::checkIntegrity() const break; pagesConsumed += scatter->count; } - if (!contains((*this)[pagesConsumed-1], hashSize)) // referenced too many main hash slots + if (!contains(getSlot(pagesConsumed-1, false), hashSize) || + (hasPreEncryptHashes && !contains(getSlot(pagesConsumed-1, true), hashSize))) // referenced too many main hash slots MacOSError::throwMe(errSecCSSignatureFailed); } @@ -197,13 +205,13 @@ void CodeDirectory::checkIntegrity() const // // Validate a slot against data in memory. // -bool CodeDirectory::validateSlot(const void *data, size_t length, Slot slot) const +bool CodeDirectory::validateSlot(const void *data, size_t length, Slot slot, bool preEncrypt) const { secinfo("codedir", "%p validating slot %d", this, int(slot)); MakeHash hasher(this); Hashing::Byte digest[hasher->digestLength()]; generateHash(hasher, data, length, digest); - return memcmp(digest, (*this)[slot], hasher->digestLength()) == 0; + return memcmp(digest, getSlot(slot, preEncrypt), hasher->digestLength()) == 0; } @@ -211,12 +219,12 @@ bool CodeDirectory::validateSlot(const void *data, size_t length, Slot slot) con // Validate a slot against the contents of an open file. At most 'length' bytes // will be read from the file. // -bool CodeDirectory::validateSlot(FileDesc fd, size_t length, Slot slot) const +bool CodeDirectory::validateSlot(FileDesc fd, size_t length, Slot slot, bool preEncrypt) const { MakeHash hasher(this); Hashing::Byte digest[hasher->digestLength()]; generateHash(hasher, fd, digest, length); - return memcmp(digest, (*this)[slot], hasher->digestLength()) == 0; + return memcmp(digest, getSlot(slot, preEncrypt), hasher->digestLength()) == 0; } @@ -228,7 +236,7 @@ bool CodeDirectory::validateSlot(FileDesc fd, size_t length, Slot slot) const bool CodeDirectory::slotIsPresent(Slot slot) const { if (slot >= -Slot(nSpecialSlots) && slot < Slot(nCodeSlots)) { - const Hashing::Byte *digest = (*this)[slot]; + const Hashing::Byte *digest = getSlot(slot, false); for (unsigned n = 0; n < hashSize; n++) if (digest[n]) return true; // non-zero digest => present @@ -322,15 +330,17 @@ bool CodeDirectory::verifyMemoryContent(CFDataRef data, const Byte* digest) cons // // Generate the canonical cdhash - the internal hash of the CodeDirectory itself. -// We currently truncate to 20 bytes because that's what the kernel can deal with. +// With 'truncate' truncates to 20 bytes, because that's what's commonly used. // -CFDataRef CodeDirectory::cdhash() const +CFDataRef CodeDirectory::cdhash(bool truncate) const { MakeHash hash(this); Hashing::Byte digest[hash->digestLength()]; hash->update(this, this->length()); hash->finish(digest); - return makeCFData(digest, min(hash->digestLength(), size_t(kSecCodeCDHashLength))); + return makeCFData(digest, + truncate ? min(hash->digestLength(), size_t(kSecCodeCDHashLength)) : + hash->digestLength()); } @@ -380,11 +390,11 @@ std::string CodeDirectory::hexHash(const unsigned char *hash) const std::string CodeDirectory::screeningCode() const { if (slotIsPresent(-cdInfoSlot)) // has Info.plist - return "I" + hexHash((*this)[-cdInfoSlot]); // use Info.plist hash + return "I" + hexHash(getSlot(-cdInfoSlot, false)); // use Info.plist hash if (slotIsPresent(-cdRepSpecificSlot)) // has Info.plist - return "R" + hexHash((*this)[-cdRepSpecificSlot]); // use Info.plist hash + return "R" + hexHash(getSlot(-cdRepSpecificSlot, false)); // use Info.plist hash if (pageSize == 0) // good-enough proxy for "not a Mach-O file" - return "M" + hexHash((*this)[0]); // use hash of main executable + return "M" + hexHash(getSlot(0, false)); // use hash of main executable return "N"; // no suitable screening code } @@ -406,5 +416,6 @@ const SecCodeDirectoryFlagTable kSecCodeDirectoryFlagTable[] = { { "restrict", kSecCodeSignatureRestrict, true }, { "enforcement", kSecCodeSignatureEnforcement, true }, { "library-validation", kSecCodeSignatureLibraryValidation, true }, + { "runtime", kSecCodeSignatureRuntime, true }, { NULL } }; diff --git a/OSX/libsecurity_codesigning/lib/codedirectory.h b/OSX/libsecurity_codesigning/lib/codedirectory.h index 5fdaa355..3da3be15 100644 --- a/OSX/libsecurity_codesigning/lib/codedirectory.h +++ b/OSX/libsecurity_codesigning/lib/codedirectory.h @@ -65,7 +65,7 @@ namespace CodeSigning { #define kSecCS_ENTITLEMENTFILE "CodeEntitlements" // entitlement configuration #define kSecCS_REPSPECIFICFILE "CodeRepSpecific" // DiskRep-specific use slot #define kSecCS_TOPDIRECTORYFILE "CodeTopDirectory" // Top-level directory list - +#define kSecCS_ENTITLEMENTDERFILE "CodeEntitlementDER" // DER entitlement representation // // Special hash slot values. In a CodeDirectory, these show up at negative slot @@ -93,6 +93,7 @@ enum { cdTopDirectorySlot = 4, // Application specific slot cdEntitlementSlot = 5, // embedded entitlement configuration cdRepSpecificSlot = 6, // for use by disk rep + cdEntitlementDERSlot = 7, // DER representation of entitlements // (add further primary slot numbers here) cdSlotCount, // total number of special slots (+1 for slot 0) @@ -202,8 +203,11 @@ public: Endian execSegLimit; // limit of executable segment Endian execSegFlags; // exec segment flags + Endian runtime; // Runtime version encoded as an unsigned int + Endian preEncryptOffset; // offset of pre-encrypt hash slots + // works with the version field; see comments above - static const uint32_t currentVersion = 0x20400; // "version 2.4" + static const uint32_t currentVersion = 0x20500; // "version 2.5" static const uint32_t compatibilityLimit = 0x2F000; // "version 3 with wiggle room" static const uint32_t earliestVersion = 0x20001; // earliest supported version @@ -211,6 +215,7 @@ public: static const uint32_t supportsTeamID = 0x20200; // first version to support team ID option static const uint32_t supportsCodeLimit64 = 0x20300; // first version to support codeLimit64 static const uint32_t supportsExecSegment = 0x20400; // first version to support exec base and limit + static const uint32_t supportsPreEncrypt = 0x20500; // first version to support pre-encrypt hashes and runtime version void checkIntegrity() const; // throws if inconsistent or unsupported version @@ -228,18 +233,28 @@ public: // main hash array access SpecialSlot maxSpecialSlot() const; - unsigned char *operator [] (Slot slot) + unsigned char *getSlotMutable (Slot slot, bool preEncrypt) { assert(slot >= int(-nSpecialSlots) && slot < int(nCodeSlots)); - return at(hashOffset) + hashSize * slot; + + if (preEncrypt) { + if (version >= supportsPreEncrypt && preEncryptOffset != 0) { + assert(slot >= 0); + return at(preEncryptOffset) + hashSize * slot; + } else { + return NULL; + } + } else { + return at(hashOffset) + hashSize * slot; + } } - - const unsigned char *operator [] (Slot slot) const + + const unsigned char *getSlot (Slot slot, bool preEncrypt) const { - assert(slot >= int(-nSpecialSlots) && slot < int(nCodeSlots)); - return at(hashOffset) + hashSize * slot; + CodeDirectory *cd = const_cast(this); + return const_cast(cd->getSlotMutable(slot, preEncrypt)); } - + // // The main page hash array can be "scattered" across the code file // by specifying an array of Scatter elements, terminated with an @@ -266,9 +281,13 @@ public: uint64_t execSegmentLimit() const { return (version >= supportsExecSegment) ? execSegLimit.get() : 0; } uint64_t execSegmentFlags() const { return (version >= supportsExecSegment) ? execSegFlags.get() : 0; } + const unsigned char *preEncryptHashes() const { return getSlot(0, true); } + + uint32_t runtimeVersion() const {return (version >= supportsPreEncrypt) ? runtime.get() : 0; } + public: - bool validateSlot(const void *data, size_t size, Slot slot) const; // validate memory buffer against page slot - bool validateSlot(UnixPlusPlus::FileDesc fd, size_t size, Slot slot) const; // read and validate file + bool validateSlot(const void *data, size_t size, Slot slot, bool preEncrypted) const; // validate memory buffer against page slot + bool validateSlot(UnixPlusPlus::FileDesc fd, size_t size, Slot slot, bool preEncrypted) const; // read and validate file bool slotIsPresent(Slot slot) const; class Builder; @@ -276,7 +295,7 @@ public: public: static DynamicHash *hashFor(HashAlgorithm hashType); // create a DynamicHash subclass for (hashType) digests DynamicHash *getHash() const { return hashFor(this->hashType); } // make one for me - CFDataRef cdhash() const; + CFDataRef cdhash(bool truncate = true) const; static void multipleHashFileData(UnixPlusPlus::FileDesc fd, size_t limit, HashAlgorithms types, void (^action)(HashAlgorithm type, DynamicHash* hasher)); bool verifyMemoryContent(CFDataRef data, const Byte* digest) const; diff --git a/OSX/libsecurity_codesigning/lib/machorep.cpp b/OSX/libsecurity_codesigning/lib/machorep.cpp index 0959b559..6986c725 100644 --- a/OSX/libsecurity_codesigning/lib/machorep.cpp +++ b/OSX/libsecurity_codesigning/lib/machorep.cpp @@ -110,38 +110,31 @@ void MachORep::prepareForSigning(SigningContext &context) { if (context.digestAlgorithms().empty()) { auto_ptr macho(mainExecutableImage()->architecture()); - - if (const version_min_command *version = macho->findMinVersion()) { - uint32_t limit = 0; - switch (macho->flip(version->cmd)) { - case LC_VERSION_MIN_MACOSX: + + uint32_t limit = 0; + switch (macho->platform()) { + case 0: + // If we don't know the platform, we stay agile. + return; + case PLATFORM_MACOS: + // 10.11.4 had first proper sha256 support. limit = (10 << 16 | 11 << 8 | 4 << 0); break; -#if 0 /* need updated libMIS before we can do this switch */ - case LC_VERSION_MIN_IPHONEOS: - limit = (9 << 16 | 3 << 8); - break; - case LC_VERSION_MIN_WATCHOS: - limit = (2 << 16 | 2 << 8); - break; - case LC_VERSION_MIN_TVOS: - limit = (9 << 16 | 2 << 8); + case PLATFORM_TVOS: + case PLATFORM_IOS: + // iOS 11 and tvOS 11 had first proper sha256 support. + limit = (11 << 16 | 0 << 8 | 0 << 0); break; + case PLATFORM_WATCHOS: + // We stay agile on the watch right now. + return; default: + // All other platforms are assumed to be new and support SHA256. break; -#else - case LC_VERSION_MIN_IPHONEOS: - case LC_VERSION_MIN_WATCHOS: - case LC_VERSION_MIN_TVOS: - return; - default: - break; -#endif - } - if (macho->flip(version->version) >= limit) { - // young enough not to need SHA-1 legacy support - context.setDigestAlgorithm(kSecCodeSignatureHashSHA256); - } + } + if (macho->minVersion() >= limit) { + // young enough not to need SHA-1 legacy support + context.setDigestAlgorithm(kSecCodeSignatureHashSHA256); } } } @@ -162,29 +155,9 @@ size_t MachORep::signingLimit() } bool MachORep::needsExecSeg(const MachO& macho) { - if (const version_min_command *version = macho.findMinVersion()) { - uint32_t min = UINT32_MAX; - - switch (macho.flip(version->cmd)) { - case LC_VERSION_MIN_IPHONEOS: - case LC_VERSION_MIN_TVOS: - min = (11 << 16 | 0 << 8); - break; - case LC_VERSION_MIN_WATCHOS: - min = (4 << 16 | 0 << 8); - break; - - default: - /* macOS currently does not get this. */ - return false; - } - - if (macho.flip(version->version) >= min) { - return true; - } - } - - return false; + uint32_t platform = macho.platform(); + // Everything embedded gets an exec segment. + return platform != 0 && platform != PLATFORM_MACOS; } size_t MachORep::execSegBase(const Architecture *arch) @@ -398,15 +371,28 @@ CFDictionaryRef MachORep::diskRepInformation() auto_ptr macho (mainExecutableImage()->architecture()); CFRef info; - if (const version_min_command *version = macho->findMinVersion()) { - + uint32_t platform = 0; + uint32_t minVersion = 0; + uint32_t sdkVersion = 0; + + if (macho->version(&platform, &minVersion, &sdkVersion)) { + + /* These keys replace the old kSecCodeInfoDiskRepOSPlatform, kSecCodeInfoDiskRepOSVersionMin + * and kSecCodeInfoDiskRepOSSDKVersion. The keys were renamed because we changed what value + * "platform" represents: For the old key, the actual load command (e.g. LC_VERSION_MIN_MACOSX) + * was returned; for the new key, we return one of the PLATFORM_* values used by LC_BUILD_VERSION. + * + * The keys are private and undocumented, and maintaining a translation table between the old and + * new domain would provide little value at high cost, but we do remove the old keys to make + * the change obvious. + */ + info.take(cfmake("{%O = %d,%O = %d,%O = %d}", - kSecCodeInfoDiskRepOSPlatform, macho->flip(version->cmd), - kSecCodeInfoDiskRepOSVersionMin, macho->flip(version->version), - kSecCodeInfoDiskRepOSSDKVersion, macho->flip(version->sdk))); + kSecCodeInfoDiskRepVersionPlatform, platform, + kSecCodeInfoDiskRepVersionMin, minVersion, + kSecCodeInfoDiskRepVersionSDK, sdkVersion)); - if (macho->flip(version->cmd) == LC_VERSION_MIN_MACOSX && - macho->flip(version->sdk) < (10 << 16 | 9 << 8)) + if (platform == PLATFORM_MACOS && sdkVersion < (10 << 16 | 9 << 8)) { info.take(cfmake("{+%O, %O = 'OS X SDK version before 10.9 does not support Library Validation'}", info.get(), diff --git a/OSX/libsecurity_codesigning/lib/piddiskrep.cpp b/OSX/libsecurity_codesigning/lib/piddiskrep.cpp index 176fcfb5..e56d1f46 100644 --- a/OSX/libsecurity_codesigning/lib/piddiskrep.cpp +++ b/OSX/libsecurity_codesigning/lib/piddiskrep.cpp @@ -39,7 +39,7 @@ PidDiskRep::setCredentials(const Security::CodeSigning::CodeDirectory *cd) { // save the Info.plist slot if (cd->slotIsPresent(cdInfoSlot)) { - mInfoPlistHash.take(makeCFData((*cd)[cdInfoSlot], cd->hashSize)); + mInfoPlistHash.take(makeCFData(cd->getSlot(cdInfoSlot, false), cd->hashSize)); } } diff --git a/OSX/libsecurity_codesigning/lib/sigblob.cpp b/OSX/libsecurity_codesigning/lib/sigblob.cpp index 619986a1..6e7cf028 100644 --- a/OSX/libsecurity_codesigning/lib/sigblob.cpp +++ b/OSX/libsecurity_codesigning/lib/sigblob.cpp @@ -62,6 +62,21 @@ CFDictionaryRef EntitlementBlob::entitlements() const this->length() - sizeof(EntitlementBlob)); } +EntitlementDERBlob *EntitlementDERBlob::alloc(size_t length) { + size_t blobLength = length + sizeof(BlobCore); + if (blobLength < length) { + // overflow + return NULL; + } + + EntitlementDERBlob *b = (EntitlementDERBlob *)malloc(blobLength); + + if (b != NULL) { + b->BlobCore::initialize(kSecCodeMagicEntitlementDER, blobLength); + } + + return b; +} } // end namespace CodeSigning } // end namespace Security diff --git a/OSX/libsecurity_codesigning/lib/sigblob.h b/OSX/libsecurity_codesigning/lib/sigblob.h index 50d8d21d..eed6b5c5 100644 --- a/OSX/libsecurity_codesigning/lib/sigblob.h +++ b/OSX/libsecurity_codesigning/lib/sigblob.h @@ -74,6 +74,21 @@ public: CFDictionaryRef entitlements() const; }; +// +// Similar, but in DER representation. +// +class EntitlementDERBlob : public Blob { +public: + static EntitlementDERBlob *alloc(size_t length); + + uint8_t *der() { return data; } + const uint8_t *der() const { return data; } + size_t derLength() const { return BlobCore::length() - sizeof(BlobCore); } + +private: + uint8_t data[0]; +}; + } // end namespace CodeSigning } // end namespace Security diff --git a/OSX/libsecurity_codesigning/lib/signer.cpp b/OSX/libsecurity_codesigning/lib/signer.cpp index fb9e7567..2a9aea29 100644 --- a/OSX/libsecurity_codesigning/lib/signer.cpp +++ b/OSX/libsecurity_codesigning/lib/signer.cpp @@ -24,6 +24,7 @@ // // signer - Signing operation supervisor and controller // +#include "der_plist.h" #include "signer.h" #include "resources.h" #include "signerutils.h" @@ -168,6 +169,8 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) entitlements = state.mEntitlementData; if (!entitlements && (inherit & kSecCodeSignerPreserveEntitlements)) entitlements = code->component(cdEntitlementSlot); + + generateEntitlementDER = signingFlags() & kSecCSSignGenerateEntitlementDER; // work out the CodeDirectory flags word bool haveCdFlags = false; @@ -291,8 +294,88 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) if (!rpath.empty()) { buildResources(rrpath, rpath, resourceRules); } + + + + if (inherit & kSecCodeSignerPreservePEH) { + /* We need at least one architecture in all cases because we index our + * PreEncryptionMaps by architecture. However, only machOs have any + * architecture at all, for generic targets there will just be one + * PreEncryptionHashMap. + * So if the main executable is not a machO, we just choose the local + * (signer's) main architecture as dummy value for the first element in our pair. */ + preEncryptMainArch = (code->diskRep()->mainExecutableIsMachO() ? + code->diskRep()->mainExecutableImage()->bestNativeArch() : + Architecture::local()); + + addPreEncryptHashes(preEncryptHashMaps[preEncryptMainArch], code); + + code->handleOtherArchitectures(^(Security::CodeSigning::SecStaticCode *subcode) { + Universal *fat = subcode->diskRep()->mainExecutableImage(); + assert(fat && fat->narrowed()); // handleOtherArchitectures gave us a focused architecture slice. + Architecture arch = fat->bestNativeArch(); // actually, only architecture for this slice. + addPreEncryptHashes(preEncryptHashMaps[arch], subcode); + }); + } + + if (inherit & kSecCodeSignerPreserveRuntime) { + /* We need at least one architecture in all cases because we index our + * RuntimeVersionMaps by architecture. However, only machOs have any + * architecture at all, for generic targets there will just be one + * RuntimeVersionMap. + * So if the main executable is not a machO, we just choose the local + * (signer's) main architecture as dummy value for the first element in our pair. */ + runtimeVersionMainArch = (code->diskRep()->mainExecutableIsMachO() ? + code->diskRep()->mainExecutableImage()->bestNativeArch() : + Architecture::local()); + + addRuntimeVersions(runtimeVersionMap[runtimeVersionMainArch], code); + + code->handleOtherArchitectures(^(Security::CodeSigning::SecStaticCode *subcode) { + Universal *fat = subcode->diskRep()->mainExecutableImage(); + assert(fat && fat->narrowed()); // handleOtherArchitectures gave us a focused architecture slice. + Architecture arch = fat->bestNativeArch(); // actually, only architecture for this slice. + addRuntimeVersions(runtimeVersionMap[arch], subcode); + }); + } } +void SecCodeSigner::Signer::addPreEncryptHashes(PreEncryptHashMap &map, SecStaticCode const *code) { + SecStaticCode::CodeDirectoryMap const *cds = code->codeDirectories(); + + if (cds != NULL) { + for(auto const& pair : *cds) { + CodeDirectory::HashAlgorithm const alg = pair.first; + CFDataRef const cddata = pair.second; + + CodeDirectory const * cd = + reinterpret_cast(CFDataGetBytePtr(cddata)); + if (cd->preEncryptHashes() != NULL) { + CFRef preEncrypt = makeCFData(cd->preEncryptHashes(), + cd->nCodeSlots * cd->hashSize); + map[alg] = preEncrypt; + } + } + } +} + +void SecCodeSigner::Signer::addRuntimeVersions(RuntimeVersionMap &map, const SecStaticCode *code) +{ + SecStaticCode::CodeDirectoryMap const *cds = code->codeDirectories(); + + if (cds != NULL) { + for(auto const& pair : *cds) { + CodeDirectory::HashAlgorithm const alg = pair.first; + CFDataRef const cddata = pair.second; + + CodeDirectory const * cd = + reinterpret_cast(CFDataGetBytePtr(cddata)); + if (cd->runtimeVersion()) { + map[alg] = cd->runtimeVersion(); + } + } + } +} // // Collect the resource seal for a program. @@ -462,15 +545,27 @@ void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context bool mainBinary = arch.source.get()->type() == MH_EXECUTE; + uint32_t runtimeVersion = 0; + if (cdFlags & kSecCodeSignatureRuntime) { + runtimeVersion = state.mRuntimeVersionOverride ? state.mRuntimeVersionOverride : arch.source.get()->sdkVersion(); + } + arch.ireqs(requirements, rep->defaultRequirements(&arch.architecture, *this), context); if (editor->attribute(writerNoGlobal)) // can't store globally, add per-arch populate(arch); for (auto type = digestAlgorithms().begin(); type != digestAlgorithms().end(); ++type) { + uint32_t runtimeVersionToUse = runtimeVersion; + if ((cdFlags & kSecCodeSignatureRuntime) && runtimeVersionMap.count(arch.architecture)) { + if (runtimeVersionMap[arch.architecture].count(*type)) { + runtimeVersionToUse = runtimeVersionMap[arch.architecture][*type]; + } + } arch.eachDigest(^(CodeDirectory::Builder& builder) { populate(builder, arch, arch.ireqs, arch.source->offset(), arch.source->signingExtent(), mainBinary, rep->execSegBase(&(arch.architecture)), rep->execSegLimit(&(arch.architecture)), - unsigned(digestAlgorithms().size()-1)); + unsigned(digestAlgorithms().size()-1), + preEncryptHashMaps[arch.architecture], runtimeVersionToUse); }); } @@ -502,10 +597,10 @@ void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context CodeDirectory *cd = builder.build(); cdSet.add(cd); }); - CFRef hashes = cdSet.hashBag(); - CFTemp hashDict("{cdhashes=%O}", hashes.get()); - CFRef hashBag = makeCFData(hashDict.get()); - CFRef signature = signCodeDirectory(cdSet.primary(), hashBag); + + CFRef hashDict = cdSet.hashDict(); + CFRef hashList = cdSet.hashList(); + CFRef signature = signCodeDirectory(cdSet.primary(), hashDict, hashList); // complete the SuperBlob cdSet.populate(&arch); @@ -544,7 +639,9 @@ void SecCodeSigner::Signer::signArchitectureAgnostic(const Requirement::Context populate(builder, *writer, ireqs, rep->signingBase(), rep->signingLimit(), false, // only machOs can currently be main binaries rep->execSegBase(NULL), rep->execSegLimit(NULL), - unsigned(digestAlgorithms().size()-1)); + unsigned(digestAlgorithms().size()-1), + preEncryptHashMaps[preEncryptMainArch], // Only one map, the default. + (cdFlags & kSecCodeSignatureRuntime) ? state.mRuntimeVersionOverride : 0); CodeDirectory *cd = builder.build(); if (!state.mDryRun) @@ -561,10 +658,9 @@ void SecCodeSigner::Signer::signArchitectureAgnostic(const Requirement::Context if (!state.mDryRun) cdSet.populate(writer); - CFRef hashes = cdSet.hashBag(); - CFTemp hashDict("{cdhashes=%O}", hashes.get()); - CFRef hashBag = makeCFData(hashDict.get()); - CFRef signature = signCodeDirectory(cdSet.primary(), hashBag); + CFRef hashDict = cdSet.hashDict(); + CFRef hashList = cdSet.hashList(); + CFRef signature = signCodeDirectory(cdSet.primary(), hashDict, hashList); writer->signature(signature); // commit to storage @@ -591,7 +687,9 @@ void SecCodeSigner::Signer::populate(DiskRep::Writer &writer) void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::Writer &writer, InternalRequirements &ireqs, size_t offset, size_t length, bool mainBinary, size_t execSegBase, size_t execSegLimit, - unsigned alternateDigestCount) + unsigned alternateDigestCount, + PreEncryptHashMap const &preEncryptHashMap, + uint32_t runtimeVersion) { // fill the CodeDirectory builder.executable(rep->mainExecutablePath(), pagesize, offset, length); @@ -600,6 +698,9 @@ void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::W builder.teamID(teamID); builder.platform(state.mPlatform); builder.execSeg(execSegBase, execSegLimit, mainBinary ? kSecCodeExecSegMainBinary : 0); + builder.generatePreEncryptHashes(signingFlags() & kSecCSSignGeneratePEH); + builder.preservePreEncryptHashMap(preEncryptHashMap); + builder.runTimeVersion(runtimeVersion); if (CFRef data = rep->component(cdInfoSlot)) builder.specialSlot(cdInfoSlot, data); @@ -615,7 +716,17 @@ void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::W builder.specialSlot(cdEntitlementSlot, entitlements); if (mainBinary) { - builder.addExecSegFlags(entitlementsToExecSegFlags(entitlements)); + CFRef entitlementDER; + uint64_t execSegFlags = 0; + cookEntitlements(entitlements, generateEntitlementDER, + &execSegFlags, &entitlementDER.aref()); + + if (generateEntitlementDER) { + writer.component(cdEntitlementDERSlot, entitlementDER); + builder.specialSlot(cdEntitlementDERSlot, entitlementDER); + } + + builder.addExecSegFlags(execSegFlags); } } if (CFRef repSpecific = rep->component(cdRepSpecificSlot)) @@ -648,7 +759,9 @@ void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::W // // Generate the CMS signature for a (finished) CodeDirectory. // -CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd, CFDataRef hashBag) +CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd, + CFDictionaryRef hashDict, + CFArrayRef hashList) { assert(state.mSigner); CFRef defaultTSContext = NULL; @@ -671,9 +784,21 @@ CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd, CFDa MacOSError::check(CMSEncoderSetSigningTime(cms, time)); } - if (hashBag) { + if (hashDict != NULL) { + assert(hashList != NULL); + + // V2 Hash Agility + + MacOSError::check(CMSEncoderAddSignedAttributes(cms, kCMSAttrAppleCodesigningHashAgilityV2)); + MacOSError::check(CMSEncoderSetAppleCodesigningHashAgilityV2(cms, hashDict)); + + // V1 Hash Agility + + CFTemp hashDict("{cdhashes=%O}", hashList); + CFRef hashAgilityV1Attribute = makeCFData(hashDict.get()); + MacOSError::check(CMSEncoderAddSignedAttributes(cms, kCMSAttrAppleCodesigningHashAgility)); - MacOSError::check(CMSEncoderSetAppleCodesigningHashAgility(cms, hashBag)); + MacOSError::check(CMSEncoderSetAppleCodesigningHashAgility(cms, hashAgilityV1Attribute)); } MacOSError::check(CMSEncoderUpdateContent(cms, cd, cd->length())); @@ -787,37 +912,75 @@ bool SecCodeSigner::Signer::booleanEntitlement(CFDictionaryRef entDict, CFString return CFBooleanGetValue(entValue); } -uint64_t SecCodeSigner::Signer::entitlementsToExecSegFlags(CFDataRef entitlements) +void SecCodeSigner::Signer::cookEntitlements(CFDataRef entitlements, bool generateDER, + uint64_t *execSegFlags, CFDataRef *entitlementDER) { if (!entitlements) { - return 0; + return; // nothing to do. } - const EntitlementBlob *blob = reinterpret_cast(CFDataGetBytePtr(entitlements)); - - if (blob == NULL || !blob->validateBlob(CFDataGetLength(entitlements))) { - return 0; - } + EntitlementDERBlob *derBlob = NULL; try { + const EntitlementBlob *blob = reinterpret_cast(CFDataGetBytePtr(entitlements)); + + if (blob == NULL || !blob->validateBlob(CFDataGetLength(entitlements))) { + MacOSError::throwMe(errSecCSInvalidEntitlements); + } + CFRef entDict = blob->entitlements(); - uint64_t flags = 0; + if (generateDER) { + CFRef error = NULL; + size_t const der_size = der_sizeof_plist(entDict, &error.aref()); - flags |= booleanEntitlement(entDict, CFSTR("get-task-allow")) ? kSecCodeExecSegAllowUnsigned : 0; - flags |= booleanEntitlement(entDict, CFSTR("run-unsigned-code")) ? kSecCodeExecSegAllowUnsigned : 0; - flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.cs.debugger")) ? kSecCodeExecSegDebugger : 0; - flags |= booleanEntitlement(entDict, CFSTR("dynamic-codesigning")) ? kSecCodeExecSegJit : 0; - flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.skip-library-validation")) ? kSecCodeExecSegSkipLibraryVal : 0; - flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.amfi.can-load-cdhash")) ? kSecCodeExecSegCanLoadCdHash : 0; - flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.amfi.can-execute-cdhash")) ? kSecCodeExecSegCanExecCdHash : 0; + if (der_size == 0) { + secerror("Getting DER size for entitlement plist failed: %@", error.get()); + MacOSError::throwMe(errSecCSInvalidEntitlements); + } + + derBlob = EntitlementDERBlob::alloc(der_size); + + if (derBlob == NULL) { + secerror("Cannot allocate buffer for DER entitlements of size %zu", der_size); + MacOSError::throwMe(errSecCSInvalidEntitlements); + } + uint8_t * const der_end = derBlob->der() + der_size; + uint8_t * const der_start = der_encode_plist(entDict, &error.aref(), derBlob->der(), der_end); - return flags; + if (der_start != derBlob->der()) { + secerror("Entitlement DER start mismatch (%zu)", (size_t)(der_start - derBlob->der())); + free(derBlob); + MacOSError::throwMe(errSecCSInvalidEntitlements); + } + + *entitlementDER = makeCFData(derBlob, derBlob->length()); + free(derBlob); + derBlob = NULL; + } + + if (execSegFlags != NULL) { + uint64_t flags = 0; + + flags |= booleanEntitlement(entDict, CFSTR("get-task-allow")) ? kSecCodeExecSegAllowUnsigned : 0; + flags |= booleanEntitlement(entDict, CFSTR("run-unsigned-code")) ? kSecCodeExecSegAllowUnsigned : 0; + flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.cs.debugger")) ? kSecCodeExecSegDebugger : 0; + flags |= booleanEntitlement(entDict, CFSTR("dynamic-codesigning")) ? kSecCodeExecSegJit : 0; + flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.skip-library-validation")) ? kSecCodeExecSegSkipLibraryVal : 0; + flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.amfi.can-load-cdhash")) ? kSecCodeExecSegCanLoadCdHash : 0; + flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.amfi.can-execute-cdhash")) ? kSecCodeExecSegCanExecCdHash : 0; + + *execSegFlags = flags; + } } catch (const CommonError &err) { - // Not fatal. + free(derBlob); + // Not fatal if we're not asked to generate DER entitlements. + secwarning("failed to parse entitlements: %s", err.what()); - return 0; + if (generateDER) { + throw; + } } } diff --git a/OSX/libsecurity_codesigning/lib/signer.h b/OSX/libsecurity_codesigning/lib/signer.h index 65ea50a5..f43494f0 100644 --- a/OSX/libsecurity_codesigning/lib/signer.h +++ b/OSX/libsecurity_codesigning/lib/signer.h @@ -67,28 +67,49 @@ protected: void signMachO(Universal *fat, const Requirement::Context &context); // sign a Mach-O binary void signArchitectureAgnostic(const Requirement::Context &context); // sign anything else + // HashAlgorithm -> PreEncrypt hashes + typedef std::map > + PreEncryptHashMap; + // Architecture -> PreEncryptHashMap + typedef std::map + PreEncryptHashMaps; + // HashAlgorithm -> Hardened Runtime Version + typedef std::map + RuntimeVersionMap; + // Architecture -> RuntimeVersionMap + typedef std::map + RuntimeVersionMaps; + void populate(DiskRep::Writer &writer); // global void populate(CodeDirectory::Builder &builder, DiskRep::Writer &writer, InternalRequirements &ireqs, size_t offset, size_t length, bool mainBinary, size_t execSegBase, size_t execSegLimit, - unsigned alternateDigestCount); // per-architecture - CFDataRef signCodeDirectory(const CodeDirectory *cd, CFDataRef hashBag); + unsigned alternateDigestCount, + const PreEncryptHashMap& preEncryptHashMap, + uint32_t runtimeVersion); // per-architecture + CFDataRef signCodeDirectory(const CodeDirectory *cd, + CFDictionaryRef hashDict, CFArrayRef hashList); uint32_t cdTextFlags(std::string text); // convert text CodeDirectory flags std::string uniqueName() const; // derive unique string from rep protected: + std::string sdkPath(const std::string &path) const; bool isAdhoc() const; SecCSFlags signingFlags() const; private: + void addPreEncryptHashes(PreEncryptHashMap &map, SecStaticCode const *code); + void addRuntimeVersions(RuntimeVersionMap &map, SecStaticCode const *code); + void considerTeamID(const PreSigningContext& context); std::vector > topSlots(CodeDirectory::Builder &builder) const; static bool booleanEntitlement(CFDictionaryRef entDict, CFStringRef key); - static uint64_t entitlementsToExecSegFlags(CFDataRef entitlements); + static void cookEntitlements(CFDataRef entitlements, bool generateDER, + uint64_t *execSegFlags, CFDataRef *entitlementsDER); protected: void buildResources(std::string root, std::string relBase, CFDictionaryRef rules); @@ -104,12 +125,17 @@ private: std::string identifier; // signing identifier std::string teamID; // team identifier CFRef entitlements; // entitlements + Architecture preEncryptMainArch; // pre-encrypt main architecture + PreEncryptHashMaps preEncryptHashMaps; // pre-encrypt hashes to keep + Architecture runtimeVersionMainArch; // runtime version main architecture + RuntimeVersionMaps runtimeVersionMap; // runtime versions to keep uint32_t cdFlags; // CodeDirectory flags const Requirements *requirements; // internal requirements ready-to-use size_t pagesize; // size of main executable pages CFAbsoluteTime signingTime; // signing time for CMS signature (0 => now) bool emitSigningTime; // emit signing time as a signed CMS attribute bool strict; // strict validation + bool generateEntitlementDER; // generate entitlement DER private: Mutex resourceLock; diff --git a/OSX/libsecurity_codesigning/lib/signerutils.cpp b/OSX/libsecurity_codesigning/lib/signerutils.cpp index 855d1949..d543433a 100644 --- a/OSX/libsecurity_codesigning/lib/signerutils.cpp +++ b/OSX/libsecurity_codesigning/lib/signerutils.cpp @@ -24,17 +24,22 @@ // // signerutils - utilities for signature generation // +#include "csutilities.h" +#include "drmaker.h" +#include "resources.h" #include "signerutils.h" #include "signer.h" -#include "SecCodeSigner.h" + +#include #include #include -#include "resources.h" -#include "csutilities.h" -#include "drmaker.h" + +#include "SecCodeSigner.h" + #include #include #include + #include // for helper validation @@ -416,17 +421,57 @@ const CodeDirectory* CodeDirectorySet::primary() const return mPrimary; } - -CFArrayRef CodeDirectorySet::hashBag() const +CFArrayRef CodeDirectorySet::hashList() const { CFRef hashList = makeCFMutableArray(0); for (auto it = begin(); it != end(); ++it) { - CFRef cdhash = it->second->cdhash(); + CFRef cdhash = it->second->cdhash(true); CFArrayAppendValue(hashList, cdhash); } return hashList.yield(); } +CFDictionaryRef CodeDirectorySet::hashDict() const +{ + CFRef hashDict = makeCFMutableDictionary(); + + for (auto it = begin(); it != end(); ++it) { + SECOidTag tag = CodeDirectorySet::SECOidTagForAlgorithm(it->first); + + if (tag == SEC_OID_UNKNOWN) { + MacOSError::throwMe(errSecCSUnsupportedDigestAlgorithm); + } + + CFRef hashType = makeCFNumber(int(tag)); + CFRef fullCdhash = it->second->cdhash(false); // Full-length cdhash! + CFDictionarySetValue(hashDict, hashType, fullCdhash); + } + + return hashDict.yield(); +} + +SECOidTag CodeDirectorySet::SECOidTagForAlgorithm(CodeDirectory::HashAlgorithm algorithm) { + SECOidTag tag; + + switch (algorithm) { + case kSecCodeSignatureHashSHA1: + tag = SEC_OID_SHA1; + break; + case kSecCodeSignatureHashSHA256: + case kSecCodeSignatureHashSHA256Truncated: // truncated *page* hashes, not cdhash + tag = SEC_OID_SHA256; + break; + case kSecCodeSignatureHashSHA384: + tag = SEC_OID_SHA384; + break; + default: + tag = SEC_OID_UNKNOWN; + } + + return tag; +} + + } // end namespace CodeSigning } // end namespace Security diff --git a/OSX/libsecurity_codesigning/lib/signerutils.h b/OSX/libsecurity_codesigning/lib/signerutils.h index 88cb2055..c4cb1238 100644 --- a/OSX/libsecurity_codesigning/lib/signerutils.h +++ b/OSX/libsecurity_codesigning/lib/signerutils.h @@ -30,6 +30,9 @@ #include "CodeSigner.h" #include "sigblob.h" #include "cdbuilder.h" + +#include + #include #include #include @@ -214,8 +217,14 @@ public: void populate(DiskRep::Writer* writer) const; const CodeDirectory* primary() const; - CFArrayRef hashBag() const; - + + // Note that the order of the hashList is relevant. + // (Which is also why there are separate methods, CFDictionary is not ordered.) + CFArrayRef hashList() const; + CFDictionaryRef hashDict() const; + + static SECOidTag SECOidTagForAlgorithm(CodeDirectory::HashAlgorithm algorithm); + private: mutable const CodeDirectory* mPrimary; }; diff --git a/OSX/libsecurity_utilities/lib/macho++.cpp b/OSX/libsecurity_utilities/lib/macho++.cpp index c5599e5c..32836ab0 100644 --- a/OSX/libsecurity_utilities/lib/macho++.cpp +++ b/OSX/libsecurity_utilities/lib/macho++.cpp @@ -452,6 +452,61 @@ const version_min_command *MachOBase::findMinVersion() const return NULL; } +const build_version_command *MachOBase::findBuildVersion() const +{ + for (const load_command *command = loadCommands(); command; command = nextCommand(command)) { + if (flip(command->cmd) == LC_BUILD_VERSION) { + if(flip(command->cmdsize) < sizeof(build_version_command)) { + UnixError::throwMe(ENOEXEC); + } + + return reinterpret_cast(command); + } + } + return NULL; +} + +bool MachOBase::version(uint32_t *platform, uint32_t *minVersion, uint32_t *sdkVersion) const +{ + const build_version_command *bc = findBuildVersion(); + + if (bc != NULL) { + if (platform != NULL) { *platform = flip(bc->platform); } + if (minVersion != NULL) { *minVersion = flip(bc->minos); } + if (sdkVersion != NULL) { *sdkVersion = flip(bc->sdk); } + return true; + } + + const version_min_command *vc = findMinVersion(); + + if (vc != NULL) { + uint32_t pf; + switch (flip(vc->cmd)) { + case LC_VERSION_MIN_MACOSX: + pf = PLATFORM_MACOS; + break; + case LC_VERSION_MIN_IPHONEOS: + pf = PLATFORM_IOS; + break; + case LC_VERSION_MIN_WATCHOS: + pf = PLATFORM_WATCHOS; + break; + case LC_VERSION_MIN_TVOS: + pf = PLATFORM_TVOS; + break; + default: + // Old style load command, but we don't know what platform to map to. + pf = 0; + } + + if (platform != NULL) { *platform = pf; } + if (minVersion != NULL) { *minVersion = flip(vc->version); } + if (sdkVersion != NULL) { *sdkVersion = flip(vc->sdk); } + return true; + } + + return false; +} // // Return the signing-limit length for this Mach-O binary image. diff --git a/OSX/libsecurity_utilities/lib/macho++.h b/OSX/libsecurity_utilities/lib/macho++.h index 23ede19d..a58ec309 100644 --- a/OSX/libsecurity_utilities/lib/macho++.h +++ b/OSX/libsecurity_utilities/lib/macho++.h @@ -108,10 +108,14 @@ public: const linkedit_data_command *findCodeSignature() const; const linkedit_data_command *findLibraryDependencies() const; - const version_min_command *findMinVersion() const; size_t signingOffset() const; // starting offset of CS section, or 0 if none size_t signingLength() const; // length of CS section, or 0 if none + + bool version(uint32_t *platform, uint32_t *minVersion, uint32_t *sdkVersion) const; + uint32_t platform() const { uint32_t ret; return version(&ret, NULL, NULL) ? ret : 0; }; + uint32_t minVersion() const { uint32_t ret; return version(NULL, &ret, NULL) ? ret : 0; }; + uint32_t sdkVersion() const { uint32_t ret; return version(NULL, NULL, &ret) ? ret : 0; }; protected: void initHeader(const mach_header *address); @@ -120,6 +124,9 @@ protected: size_t headerSize() const; // size of header size_t commandSize() const; // size of commands area + const version_min_command *findMinVersion() const; + const build_version_command *findBuildVersion() const; + private: const mach_header *mHeader; // Mach-O header const load_command *mCommands; // load commands diff --git a/OSX/sec/Security/Regressions/secitem/si-28-sectrustsettings.m b/OSX/sec/Security/Regressions/secitem/si-28-sectrustsettings.m index ee2af8d5..522b3fd4 100644 --- a/OSX/sec/Security/Regressions/secitem/si-28-sectrustsettings.m +++ b/OSX/sec/Security/Regressions/secitem/si-28-sectrustsettings.m @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -532,15 +533,39 @@ static void test_multiple_constraints(void) { } +#define kNumberPolicyNamePinnningConstraintsTests (1 + 1 + 5) +static void test_policy_name_pinning_constraints(void) { + /* allow all but */ + NSArray *allowAllBut = @[ + @{(__bridge NSString*)kSecTrustSettingsPolicy: (__bridge id)sslPolicy , + (__bridge NSString*)kSecTrustSettingsResult: @(kSecTrustSettingsResultUnspecified)}, + @{(__bridge NSString*)kSecTrustSettingsResult: @(kSecTrustSettingsResultTrustRoot) } + ]; + setTS(cert0, (__bridge CFArrayRef)allowAllBut); + + SecTrustRef trust = NULL; + SecTrustResultType trust_result; + ok_status(SecTrustCreateWithCertificates(sslChain, sslPolicy, &trust), "create trust with ssl policy"); + ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)verify_date), "set trust verify date"); + ok_status(SecTrustSetPinningPolicyName(trust, CFSTR("not-a-db-policy-name")), "set policy name"); + ok_status(SecTrustEvaluate(trust, &trust_result), "evaluate trust"); + is(trust_result, kSecTrustResultRecoverableTrustFailure, "check trust result for sslServer policy with policy name"); + CFReleaseSafe(trust); + + removeTS(cert0); +} + int si_28_sectrustsettings(int argc, char *const *argv) { - plan_tests(kNumberNoConstraintsTests + - kNumberPolicyConstraintsTests + - kNumberPolicyStringConstraintsTests + - kNumberApplicationsConstraintsTests + - kNumberKeyUsageConstraintsTests + - kNumberAllowedErrorsTests + - kNumberMultipleConstraintsTests); + plan_tests(kNumberNoConstraintsTests + + kNumberPolicyConstraintsTests + + kNumberPolicyStringConstraintsTests + + kNumberApplicationsConstraintsTests + + kNumberKeyUsageConstraintsTests + + kNumberAllowedErrorsTests + + kNumberMultipleConstraintsTests + + kNumberPolicyNamePinnningConstraintsTests + ); #if !TARGET_OS_IPHONE if (getuid() != 0) { @@ -558,6 +583,7 @@ int si_28_sectrustsettings(int argc, char *const *argv) test_key_usage_constraints(); test_allowed_errors(); test_multiple_constraints(); + test_policy_name_pinning_constraints(); cleanup_globals(); } diff --git a/OSX/sec/Security/SecExports.exp-in b/OSX/sec/Security/SecExports.exp-in index d6bc9c3d..0cae30c6 100644 --- a/OSX/sec/Security/SecExports.exp-in +++ b/OSX/sec/Security/SecExports.exp-in @@ -1723,6 +1723,7 @@ _kSecCodeInfoDigestAlgorithms _kSecCodeInfoEntitlements _kSecCodeInfoEntitlementsDict _kSecCodeInfoUnique +_kSecCodeInfoRuntimeVersion _kSecCFErrorResourceAdded _kSecCFErrorResourceAltered diff --git a/OSX/sec/securityd/SecPolicyServer.c b/OSX/sec/securityd/SecPolicyServer.c index fe2df07e..ccdce836 100644 --- a/OSX/sec/securityd/SecPolicyServer.c +++ b/OSX/sec/securityd/SecPolicyServer.c @@ -2612,6 +2612,13 @@ static bool SecPVCContainsPolicy(SecPVCRef pvc, CFStringRef searchOid, CFStringR if (policyIX) { *policyIX = ix; } return true; } + /* sslServer trust settings need to apply to evals using policyName pinning + * but make sure we don't use this for SSL Client trust settings or policies. */ + if (CFEqual(searchOid, policyOid) && + CFEqual(searchName, kSecPolicyNameSSLServer) && !CFEqual(policyName, kSecPolicyNameSSLClient)) { + if (policyIX) { *policyIX = ix; } + return true; + } } /* Next best is just OID. */ if (!searchName && searchOid && policyOid) { diff --git a/Security.exp-in b/Security.exp-in index de4daeb6..2eb7fe59 100644 --- a/Security.exp-in +++ b/Security.exp-in @@ -1537,6 +1537,7 @@ _kSecCodeSignerSigningTime _kSecCodeSignerRequireTimestamp _kSecCodeSignerTeamIdentifier _kSecCodeSignerPlatformIdentifier +_kSecCodeSignerRuntimeVersion _kSecCodeSignerTimestampServer _kSecCodeSignerTimestampAuthentication _kSecCodeSignerTimestampOmitCertificates @@ -1565,13 +1566,14 @@ _kSecCodeInfoTeamIdentifier _kSecCodeInfoTrust _kSecCodeInfoUnique _kSecCodeInfoCdHashes +_kSecCodeInfoRuntimeVersion _kSecCodeInfoCodeDirectory _kSecCodeInfoCodeOffset _kSecCodeInfoDiskRepInfo -_kSecCodeInfoDiskRepOSPlatform -_kSecCodeInfoDiskRepOSVersionMin -_kSecCodeInfoDiskRepOSSDKVersion _kSecCodeInfoDiskRepNoLibraryValidation +_kSecCodeInfoDiskRepVersionPlatform +_kSecCodeInfoDiskRepVersionMin +_kSecCodeInfoDiskRepVersionSDK _kSecCodeInfoResourceDirectory _kSecGuestAttributeCanonical _kSecGuestAttributeDynamicCode @@ -1702,9 +1704,9 @@ _kSecCodeInfoDigestAlgorithm _kSecCodeInfoDigestAlgorithms _kSecCodeInfoDiskRepInfo _kSecCodeInfoDiskRepNoLibraryValidation -_kSecCodeInfoDiskRepOSPlatform -_kSecCodeInfoDiskRepOSSDKVersion -_kSecCodeInfoDiskRepOSVersionMin +_kSecCodeInfoDiskRepVersionPlatform +_kSecCodeInfoDiskRepVersionMin +_kSecCodeInfoDiskRepVersionSDK _kSecCodeInfoFlags _kSecCodeInfoFormat _kSecCodeInfoImplicitDesignatedRequirement diff --git a/securityd/src/csproxy.cpp b/securityd/src/csproxy.cpp index 8c3e8661..7c8a27f5 100644 --- a/securityd/src/csproxy.cpp +++ b/securityd/src/csproxy.cpp @@ -314,8 +314,17 @@ void CodeSigningHost::removeGuest(SecGuestRef hostRef, SecGuestRef guestRef) // // The internal Guest object // +CodeSigningHost::Guest::Guest() : mAttrData(NULL), mAttrDataLength(0) +{ +} + CodeSigningHost::Guest::~Guest() -{ } +{ + if (mAttrData != NULL) { + vm_size_t rounded_size = mach_vm_round_page(mAttrDataLength); + vm_deallocate(mach_task_self(), reinterpret_cast(mAttrData), rounded_size); + } +} void CodeSigningHost::Guest::setAttributes(const CssmData &attrData) { @@ -330,14 +339,28 @@ void CodeSigningHost::Guest::setAttributes(const CssmData &attrData) } } -CFDataRef CodeSigningHost::Guest::attrData() const -{ - if (!mAttrData) - mAttrData = makeCFData(this->attributes.get()); - return mAttrData; +void CodeSigningHost::Guest::createAttrData() const { + if (!mAttrData) { + CFRef data = makeCFData(this->attributes.get()); + + /* cshosting_server_identifyGuest() will point to the attrData in a MIG + * OOL buffer. To prevent leaking surrounding memory, the attr data gets its + * own (zeroed) pages. */ + vm_address_t addr = 0; + vm_size_t rounded_size = mach_vm_round_page(CFDataGetLength(data.get())); + kern_return_t ret = vm_allocate(mach_task_self(), &addr, rounded_size, VM_FLAGS_ANYWHERE); + + if (ret == KERN_SUCCESS) { + mAttrData = reinterpret_cast(addr); + mAttrDataLength = CFDataGetLength(data.get()); + memcpy(mAttrData, CFDataGetBytePtr(data.get()), mAttrDataLength); + // pages returned by vm_allocate are zeroed, no need to fill out padding. + } else { + secerror("csproxy attrData vm_allocate failed: %d", ret); + } + } } - void CodeSigningHost::Guest::setHash(const CssmData &given, bool generate) { if (given.length()) // explicitly given @@ -487,9 +510,8 @@ kern_return_t cshosting_server_identifyGuest(CSH_ARGS, SecGuestRef guestRef, *hashLength = 0; // unavailable // visible attributes. This proxy returns all attributes set by the host - CFDataRef attrData = guest->attrData(); // (the guest will cache this until it dies) - *attributes = (void *)CFDataGetBytePtr(attrData); // MIG botch (it doesn't need a writable pointer) - *attributesLength = int_cast(CFDataGetLength(attrData)); + *attributes = (void*)guest->attrData(); // MIG botch (it doesn't need a writable pointer) + *attributesLength = int_cast(guest->attrDataLength()); END_IPC } diff --git a/securityd/src/csproxy.h b/securityd/src/csproxy.h index d22c28b5..37785f76 100644 --- a/securityd/src/csproxy.h +++ b/securityd/src/csproxy.h @@ -69,6 +69,7 @@ public: struct Guest : public RefCount, public HandleObject { public: + Guest(); ~Guest(); std::vector guestPath; // guest chain to this guest uint32_t status; // dynamic status @@ -80,7 +81,8 @@ public: operator bool() const { return attributes; } // exists SecGuestRef guestRef() const { return int_cast(handle()); } void setAttributes(const CssmData &attrData); - CFDataRef attrData() const; + uint8_t const *attrData() const { createAttrData(); return mAttrData; }; + CFIndex attrDataLength() const { createAttrData(); return mAttrDataLength; }; void setHash(const CssmData &given, bool generate); bool isGuestOf(Guest *host, GuestCheck check) const; @@ -89,7 +91,10 @@ public: IFDUMP(void dump() const); private: - mutable CFRef mAttrData; // XML form of attributes (must live until guest destruction) + void createAttrData() const; + + mutable uint8_t *mAttrData; // XML form of attributes (vm_allocate'd, must live until guest destruction) + mutable CFIndex mAttrDataLength; }; void registerCodeSigning(mach_port_t hostingPort, SecCSFlags flags); -- 2.45.2