| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/mac/process_requirement.h" |
| |
| #include <Kernel/kern/cs_blobs.h> |
| #include <Security/Security.h> |
| #include <mach/kern_return.h> |
| #include <stdint.h> |
| #include <sys/errno.h> |
| |
| #include <algorithm> |
| #include <optional> |
| |
| #include "base/apple/mach_logging.h" |
| #include "base/apple/osstatus_logging.h" |
| #include "base/apple/scoped_cftyperef.h" |
| #include "base/check_op.h" |
| #include "base/containers/span.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/features.h" |
| #include "base/logging.h" |
| #include "base/mac/code_signature.h" |
| #include "base/mac/code_signature_spi.h" |
| #include "base/mac/info_plist_data.h" |
| #include "base/mac/mac_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/thread_pool.h" |
| #include "base/types/expected.h" |
| #include "base/types/expected_macros.h" |
| #include "build/branding_buildflags.h" |
| |
| using base::apple::ScopedCFTypeRef; |
| |
| namespace base::mac { |
| |
| enum class ValidationCategory : unsigned int { |
| Invalid = CS_VALIDATION_CATEGORY_INVALID, |
| Platform = CS_VALIDATION_CATEGORY_PLATFORM, |
| TestFlight = CS_VALIDATION_CATEGORY_TESTFLIGHT, |
| Development = CS_VALIDATION_CATEGORY_DEVELOPMENT, |
| AppStore = CS_VALIDATION_CATEGORY_APP_STORE, |
| Enterprise = CS_VALIDATION_CATEGORY_ENTERPRISE, |
| DeveloperId = CS_VALIDATION_CATEGORY_DEVELOPER_ID, |
| LocalSigning = CS_VALIDATION_CATEGORY_LOCAL_SIGNING, |
| Rosetta = CS_VALIDATION_CATEGORY_ROSETTA, |
| OopJit = CS_VALIDATION_CATEGORY_OOPJIT, |
| None = CS_VALIDATION_CATEGORY_NONE, |
| }; |
| |
| namespace { |
| |
| // Requirements derived from the designated requirements described in TN3127: |
| // Inside Code Signing: Requirements |
| // (https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements). |
| constexpr std::string_view kAnyDeveloperIdRequirement = |
| "(anchor apple generic and certificate " |
| "1[field.1.2.840.113635.100.6.2.6] exists and certificate " |
| "leaf[field.1.2.840.113635.100.6.1.13] exists)"; |
| constexpr std::string_view kAnyAppStoreRequirement = |
| "(anchor apple generic and certificate " |
| "leaf[field.1.2.840.113635.100.6.1.9] exists)"; |
| constexpr std::string_view kAnyDevelopmentRequirement = |
| "(anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.1] " |
| "exists)"; |
| |
| // A requirement string that will match ad-hoc signed code. It will also match |
| // code signed with non-Apple certificates, but those are not supported by |
| // `ProcessRequirement`. |
| constexpr std::string_view kNonAppleAnchorRequirement = |
| "!(anchor apple generic)"; |
| |
| struct CSOpsSystemCallProviderImpl |
| : ProcessRequirement::CSOpsSystemCallProvider { |
| ~CSOpsSystemCallProviderImpl() override = default; |
| |
| int csops(pid_t pid, |
| unsigned int ops, |
| void* useraddr, |
| size_t usersize) override { |
| return ::csops(pid, ops, useraddr, usersize); |
| } |
| |
| bool SupportsValidationCategory() const override { |
| // macOS versions prior to macOS 13 do not support |
| // CS_OPS_VALIDATION_CATEGORY. |
| return MacOSMajorVersion() >= 13; |
| } |
| }; |
| |
| ProcessRequirement::CSOpsSystemCallProvider* DefaultCSOpsProvider() { |
| static NoDestructor<CSOpsSystemCallProviderImpl> default_provider; |
| return default_provider.get(); |
| } |
| |
| ProcessRequirement::CSOpsSystemCallProvider*& CSOpsProvider() { |
| static ProcessRequirement::CSOpsSystemCallProvider* provider = |
| DefaultCSOpsProvider(); |
| return provider; |
| } |
| |
| base::expected<std::string, int> TeamIdentifierOfCurrentProcess() { |
| struct { |
| uint32_t type; |
| uint32_t length; |
| char identifier[CS_MAX_TEAMID_LEN + 1]; |
| } result_data; |
| int result = CSOpsProvider()->csops(getpid(), CS_OPS_TEAMID, &result_data, |
| sizeof(result_data)); |
| if (result < 0) { |
| if (errno != ENOENT && errno != EINVAL) { |
| // Don't log an error for `ENOENT` or `EINVAL` as they are expected in |
| // ad-hoc signed builds and unsigned builds respectively, such as during |
| // local development. |
| PLOG(ERROR) << "csops(CS_OPS_TEAMID) failed"; |
| } |
| |
| return base::unexpected(errno); |
| } |
| |
| return std::string(result_data.identifier); |
| } |
| |
| base::expected<ValidationCategory, int> ValidationCategoryOfCurrentProcess() { |
| ValidationCategory validation_category = ValidationCategory::Invalid; |
| int result = |
| CSOpsProvider()->csops(getpid(), CS_OPS_VALIDATION_CATEGORY, |
| &validation_category, sizeof(validation_category)); |
| if (result < 0) { |
| if (errno != EINVAL) { |
| // Don't log an error for `EINVAL` as it is expected in unsigned builds, |
| // such as during local development. |
| PLOG(ERROR) << "csops(CS_OPS_VALIDATION_CATEGORY) failed"; |
| } |
| return base::unexpected(errno); |
| } |
| |
| return validation_category; |
| } |
| |
| // Determine the validation category of the current process by evaluating |
| // the current process's code signature against requirements that represent |
| // each of the validation categories we're interested in. |
| base::expected<ValidationCategory, OSStatus> |
| FallbackValidationCategoryOfCurrentProcess() { |
| ASSIGN_OR_RETURN(ScopedCFTypeRef<SecCodeRef> self_code, |
| DynamicCodeObjectForCurrentProcess()); |
| |
| // Do initial validation without a requirement to detect problems with the |
| // code signature itself. We do basic validation only as the validation is |
| // secondary to requirement matching in this case. |
| if (OSStatus status = SecStaticCodeCheckValidity( |
| self_code.get(), kSecCSBasicValidateOnly, nullptr)) { |
| if (status == errSecCSUnsigned) { |
| return ValidationCategory::None; |
| } |
| |
| OSSTATUS_LOG(ERROR, status) |
| << "Unable to derive validation category for current " |
| "process. Signature validation of current process failed"; |
| return base::unexpected(status); |
| } |
| |
| std::pair<ValidationCategory, std::string_view> supported_categories[] = { |
| {ValidationCategory::DeveloperId, kAnyDeveloperIdRequirement}, |
| {ValidationCategory::AppStore, kAnyAppStoreRequirement}, |
| {ValidationCategory::Development, kAnyDevelopmentRequirement}, |
| {ValidationCategory::None, kNonAppleAnchorRequirement}, |
| }; |
| |
| for (auto& [category, requirement] : supported_categories) { |
| OSStatus status = |
| SecStaticCodeCheckValidity(self_code.get(), kSecCSBasicValidateOnly, |
| RequirementFromString(requirement).get()); |
| switch (status) { |
| case errSecSuccess: |
| // Requirement matched so we now know the validation category. |
| return category; |
| |
| case errSecCSReqFailed: |
| // Requirement did not match. On to the next one. |
| continue; |
| |
| default: |
| OSSTATUS_LOG(INFO, status) |
| << "Unexpected error when evaluating requirement " << requirement; |
| } |
| } |
| |
| LOG(ERROR) << "Unable to derive validation category for current process. " |
| "Signature did not match any supported requirement."; |
| return base::unexpected(errSecFunctionFailed); |
| } |
| |
| std::string RequirementStringForValidationCategory( |
| ValidationCategory category) { |
| // It is not meaningful to create a requirement string for an unsigned or |
| // ad-hoc signed process. |
| CHECK_NE(category, ValidationCategory::None); |
| |
| switch (category) { |
| case ValidationCategory::DeveloperId: |
| return std::string(kAnyDeveloperIdRequirement); |
| case ValidationCategory::AppStore: |
| return std::string(kAnyAppStoreRequirement); |
| case ValidationCategory::Development: |
| return std::string(kAnyDevelopmentRequirement); |
| default: |
| NOTREACHED() << "Unsupported process validation category: " |
| << static_cast<unsigned int>(category); |
| } |
| } |
| |
| audit_token_t AuditTokenForCurrentProcess() { |
| audit_token_t token; |
| mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT; |
| kern_return_t kr = task_info(mach_task_self(), TASK_AUDIT_TOKEN, |
| reinterpret_cast<task_info_t>(&token), &count); |
| MACH_CHECK(kr == KERN_SUCCESS, kr) << "task_info(TASK_AUDIT_TOKEN)"; |
| return token; |
| } |
| |
| } // namespace |
| |
| ProcessRequirement::Builder::Builder() = default; |
| ProcessRequirement::Builder::~Builder() = default; |
| |
| ProcessRequirement::Builder::Builder(Builder&&) = default; |
| ProcessRequirement::Builder& ProcessRequirement::Builder::operator=(Builder&&) = |
| default; |
| |
| ProcessRequirement::Builder ProcessRequirement::Builder::Identifier( |
| std::string identifier) && { |
| CHECK(identifier.size()); |
| CHECK(identifiers_.empty()); |
| identifiers_.push_back(std::move(identifier)); |
| return std::move(*this); |
| } |
| |
| ProcessRequirement::Builder ProcessRequirement::Builder::IdentifierIsOneOf( |
| std::vector<std::string> identifiers) && { |
| CHECK(identifiers.size()); |
| CHECK(std::ranges::all_of(identifiers, &std::string::size)); |
| CHECK(identifiers_.empty()); |
| identifiers_ = std::move(identifiers); |
| return std::move(*this); |
| } |
| |
| ProcessRequirement::Builder |
| ProcessRequirement::Builder::SignedWithSameIdentity() && { |
| return std::move(*this).HasSameTeamIdentifier().HasSameCertificateType(); |
| } |
| |
| ProcessRequirement::Builder |
| ProcessRequirement::Builder::HasSameTeamIdentifier() && { |
| CHECK(team_identifier_.empty()); |
| |
| has_same_team_identifier_called_ = true; |
| |
| auto team_identifier = TeamIdentifierOfCurrentProcess(); |
| if (team_identifier.has_value()) { |
| team_identifier_ = std::move(*team_identifier); |
| return std::move(*this); |
| } else if (team_identifier.error() == ENOENT || |
| team_identifier.error() == EINVAL) { |
| // ENOENT is returned when the process is ad-hoc signed and has no team |
| // identifier. EINVAL is returned when the process is unsigned. |
| team_identifier_ = ""; |
| return std::move(*this); |
| } |
| |
| LOG(ERROR) << "HasSameTeamIdentifier failed to retrieve team identifier of " |
| "current process"; |
| failed_ = true; |
| return std::move(*this); |
| } |
| |
| ProcessRequirement::Builder |
| ProcessRequirement::Builder::HasSameCertificateType() && { |
| CHECK(!validation_category_); |
| |
| has_same_certificate_type_called_ = true; |
| |
| if (CSOpsProvider()->SupportsValidationCategory()) { |
| auto validation_category = ValidationCategoryOfCurrentProcess(); |
| if (validation_category.has_value()) { |
| validation_category_ = *validation_category; |
| } else if (validation_category.error() == EINVAL) { |
| // EINVAL on versions of macOS that support CS_OPS_VALIDATION_CATEGORY |
| // indicates that the process is unsigned or the process has an invalid |
| // code signature. |
| validation_category_ = ValidationCategory::None; |
| } else { |
| failed_ = true; |
| } |
| } else { |
| // Older macOS versions do not support CS_OPS_VALIDATION_CATEGORY. Derive |
| // the validation category via Security.framework instead. |
| static auto validation_category = |
| FallbackValidationCategoryOfCurrentProcess(); |
| if (validation_category.has_value()) { |
| validation_category_ = *validation_category; |
| } else { |
| failed_ = true; |
| } |
| } |
| |
| return std::move(*this); |
| } |
| |
| ProcessRequirement::Builder ProcessRequirement::Builder::TeamIdentifier( |
| std::string team_identifier) && { |
| CHECK(team_identifier_.empty()); |
| CHECK(std::ranges::all_of(team_identifier, base::IsAsciiAlphaNumeric<char>)); |
| team_identifier_ = std::move(team_identifier); |
| has_same_team_identifier_called_ = false; |
| return std::move(*this); |
| } |
| |
| ProcessRequirement::Builder |
| ProcessRequirement::Builder::DeveloperIdCertificateType() && { |
| validation_category_ = ValidationCategory::DeveloperId; |
| has_same_certificate_type_called_ = false; |
| return std::move(*this); |
| } |
| |
| ProcessRequirement::Builder |
| ProcessRequirement::Builder::AppStoreCertificateType() && { |
| validation_category_ = ValidationCategory::AppStore; |
| has_same_certificate_type_called_ = false; |
| return std::move(*this); |
| } |
| |
| ProcessRequirement::Builder |
| ProcessRequirement::Builder::DevelopmentCertificateType() && { |
| validation_category_ = ValidationCategory::Development; |
| has_same_certificate_type_called_ = false; |
| return std::move(*this); |
| } |
| |
| ProcessRequirement::Builder |
| ProcessRequirement::Builder::CheckDynamicValidityOnly() && { |
| dynamic_validity_only_ = true; |
| return std::move(*this); |
| } |
| |
| std::optional<ProcessRequirement> ProcessRequirement::Builder::Build() && { |
| if (failed_) { |
| VLOG(2) |
| << "ProcessRequirement::Builder::Build: failed validation -> nullopt"; |
| return std::nullopt; |
| } |
| |
| ValidationCategory validation_category = |
| validation_category_.value_or(ValidationCategory::None); |
| |
| if (validation_category == ValidationCategory::None || |
| validation_category == ValidationCategory::Platform) { |
| // A validation category of None or Platform with a non-empty team ID is not |
| // a valid combination, but should not be treated as programmer error if the |
| // validation category came from the kernel. |
| if (team_identifier_.size() && has_same_certificate_type_called_) { |
| VLOG(2) << "ProcessRequirement::Builder::Build: have team ID but kernel " |
| "returned validation category of none or platform -> nullopt"; |
| return std::nullopt; |
| } |
| |
| CHECK(team_identifier_.empty()) |
| << "A process requirement matching on a team identifier without " |
| "specifying a certificate type is unsafe."; |
| } else { |
| // An empty team ID with a valid validation category is not a valid |
| // combination, but should not be treated as programmer error if the empty |
| // team ID came from the kernel. |
| if (team_identifier_.empty() && has_same_team_identifier_called_) { |
| VLOG(2) << "ProcessRequirement::Builder::Build: have validation category " |
| "but kernel returned empty team ID -> nullopt"; |
| return std::nullopt; |
| } |
| |
| CHECK(team_identifier_.size()) |
| << "A process requirement without a team identifier is unsafe as it " |
| "can be matched by any signing identity of that type."; |
| } |
| |
| return ProcessRequirement(std::move(identifiers_), |
| std::move(team_identifier_), validation_category, |
| dynamic_validity_only_); |
| } |
| |
| ProcessRequirement::ProcessRequirement(std::vector<std::string> identifiers, |
| std::string team_identifier, |
| ValidationCategory validation_category, |
| bool dynamic_validity_only) |
| : identifiers_(std::move(identifiers)), |
| team_identifier_(std::move(team_identifier)), |
| validation_category_(validation_category), |
| dynamic_validity_only_(dynamic_validity_only) { |
| CHECK(validation_category_ != ValidationCategory::Invalid); |
| } |
| |
| ProcessRequirement::ProcessRequirement(ForTesting for_testing) |
| : for_testing_(for_testing), |
| validation_category_(ValidationCategory::Invalid) {} |
| |
| ProcessRequirement::~ProcessRequirement() = default; |
| |
| ProcessRequirement::ProcessRequirement(const ProcessRequirement&) = default; |
| ProcessRequirement& ProcessRequirement::operator=(const ProcessRequirement&) = |
| default; |
| |
| ProcessRequirement::ProcessRequirement(ProcessRequirement&&) = default; |
| ProcessRequirement& ProcessRequirement::operator=(ProcessRequirement&&) = |
| default; |
| |
| // static |
| ProcessRequirement ProcessRequirement::AlwaysMatchesForTesting() { |
| return ProcessRequirement(ForTesting::AlwaysMatches); |
| } |
| |
| // static |
| ProcessRequirement ProcessRequirement::NeverMatchesForTesting() { |
| return ProcessRequirement(ForTesting::NeverMatches); |
| } |
| |
| void ProcessRequirement::SetShouldCheckDynamicValidityOnlyForTesting() { |
| dynamic_validity_only_ = true; |
| } |
| |
| bool ProcessRequirement::RequiresSignatureValidation() const { |
| if (for_testing_.has_value()) { |
| // `ForTesting::AlwaysMatches` does not require validation because |
| // a test process is likely to be unsigned. |
| // `ForTesting::NeverMatches` will fail signature validation with |
| // `errSecCSUnsigned` if the process is unsigned, and will fail requirement |
| // evaluation if the process has a valid ad-hoc signature. |
| return for_testing_.value() == ForTesting::NeverMatches; |
| } |
| |
| // All validation categories besides none (ad-hoc signature or unsigned) and |
| // platform require validation. |
| // |
| // It is not useful to validate an ad-hoc signature as anyone can create an |
| // ad-hoc signature that matches this requirement. |
| // |
| // Being classified as a platform binary indicates that the |
| // `amfi_get_out_of_my_way=1` boot argument is set and there are no |
| // guarantees around process integrity. |
| return validation_category_ != ValidationCategory::None && |
| validation_category_ != ValidationCategory::Platform; |
| } |
| |
| ScopedCFTypeRef<SecRequirementRef> ProcessRequirement::AsSecRequirement() |
| const { |
| if (for_testing_.has_value()) { |
| return AsSecRequirementForTesting(for_testing_.value()); // IN-TEST |
| } |
| |
| if (!RequiresSignatureValidation()) { |
| VLOG(2) << "ProcessRequirement::AsSecRequirement -> nullptr"; |
| return ScopedCFTypeRef<SecRequirementRef>{nullptr}; |
| } |
| |
| std::vector<std::string> clauses; |
| |
| if (identifiers_.size()) { |
| std::vector<std::string> identifier_clauses; |
| for (const std::string& identifier : identifiers_) { |
| identifier_clauses.push_back(StrCat({"identifier \"", identifier, "\""})); |
| } |
| if (identifier_clauses.size() == 1) { |
| clauses.push_back(std::move(identifier_clauses.front())); |
| } else { |
| std::string identifier_clause = |
| base::JoinString(identifier_clauses, " or "); |
| clauses.push_back(StrCat({"(", identifier_clause, ")"})); |
| } |
| } |
| |
| if (team_identifier_.size()) { |
| clauses.push_back( |
| StrCat({"certificate leaf[subject.OU] = \"", team_identifier_, "\""})); |
| } |
| |
| clauses.push_back( |
| RequirementStringForValidationCategory(validation_category_)); |
| |
| std::string requirement_string = base::JoinString(clauses, " and "); |
| VLOG(2) << "ProcessRequirement::AsSecRequirement -> " << requirement_string; |
| apple::ScopedCFTypeRef<SecRequirementRef> requirement = |
| RequirementFromString(requirement_string); |
| CHECK(requirement) << "ProcessRequirement::AsSecRequirement generated a " |
| "requirement string that could not be parsed."; |
| return requirement; |
| } |
| |
| // static |
| ScopedCFTypeRef<SecRequirementRef> |
| ProcessRequirement::AsSecRequirementForTesting( |
| ProcessRequirement::ForTesting for_testing) { |
| std::string requirement_string; |
| switch (for_testing) { |
| case ForTesting::AlwaysMatches: { |
| requirement_string = "(!info[ThisKeyDoesNotExist])"; |
| break; |
| } |
| case ForTesting::NeverMatches: { |
| requirement_string = R"(identifier = "this is not the identifier")"; |
| break; |
| } |
| } |
| ScopedCFTypeRef<SecRequirementRef> requirement = |
| RequirementFromString(requirement_string); |
| CHECK(requirement) |
| << "ProcessRequirement::AsSecRequirementForTesting generated a " |
| "requirement string that could not be parsed."; |
| return requirement; |
| } |
| |
| // static |
| void ProcessRequirement::SetCSOpsSystemCallProviderForTesting( |
| CSOpsSystemCallProvider* csops_provider) { |
| if (csops_provider) { |
| CSOpsProvider() = csops_provider; |
| } else { |
| CSOpsProvider() = DefaultCSOpsProvider(); |
| } |
| } |
| |
| bool ProcessRequirement::ValidateProcess( |
| audit_token_t audit_token, |
| base::span<const uint8_t> info_plist_data) const { |
| if (!RequiresSignatureValidation()) { |
| // No signature validation required. Return success. |
| base::UmaHistogramBoolean("Mac.ProcessRequirement.ValidationRequired", |
| false); |
| return true; |
| } |
| base::UmaHistogramBoolean("Mac.ProcessRequirement.ValidationRequired", true); |
| |
| // If the requirement specifies we are checking only the validity of the |
| // dynamic code then we must have Info.plist data. |
| if (dynamic_validity_only_) { |
| CHECK(info_plist_data.size()) |
| << "info_plist_data is required when checking dynamic validity only."; |
| } |
| |
| if (OSStatus status = ProcessIsSignedAndFulfillsRequirement( |
| audit_token, AsSecRequirement().get(), |
| dynamic_validity_only_ ? SignatureValidationType::DynamicOnly |
| : SignatureValidationType::DynamicAndStatic, |
| base::as_string_view(info_plist_data))) { |
| OSSTATUS_LOG(ERROR, status) << "ProcessIsSignedAndFulfillsRequirement"; |
| base::UmaHistogramSparse("Mac.ProcessRequirement.ValidationResult", status); |
| return false; |
| } |
| |
| base::UmaHistogramSparse("Mac.ProcessRequirement.ValidationResult", |
| errSecSuccess); |
| return true; |
| } |
| |
| // static |
| void ProcessRequirement::MaybeGatherMetrics() { |
| static BASE_FEATURE(kGatherProcessRequirementMetrics, |
| "GatherProcessRequirementMetrics", |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| if (base::FeatureList::IsEnabled(kGatherProcessRequirementMetrics)) { |
| base::ThreadPool::PostTask( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, |
| base::BindOnce(&ProcessRequirement::GatherMetrics)); |
| } |
| } |
| |
| namespace { |
| |
| template <typename T> |
| void RecordResultHistogram(const std::string& field_name, |
| const base::expected<T, int>& value) { |
| base::UmaHistogramSparse("Mac.ProcessRequirement." + field_name + ".Result", |
| value.error_or(0)); |
| } |
| |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| void RecordHasExpectedValueHistogram(const std::string& field_name, |
| bool has_expected_value) { |
| base::UmaHistogramBoolean( |
| "Mac.ProcessRequirement." + field_name + ".HasExpectedValue", |
| has_expected_value); |
| } |
| #endif |
| |
| template <typename T> |
| std::string StringForCrashKey(const base::expected<T, int>& value) { |
| if (value.has_value()) { |
| if constexpr (std::is_same_v<T, std::string>) { |
| return value.value(); |
| } else { |
| return NumberToString(static_cast<uint64_t>(value.value())); |
| } |
| } |
| return "error: " + NumberToString(value.error()); |
| } |
| |
| } // namespace |
| |
| // static |
| void ProcessRequirement::GatherMetrics() { |
| auto team_id = TeamIdentifierOfCurrentProcess(); |
| auto validation_category = ValidationCategoryOfCurrentProcess(); |
| auto fallback_validation_category = |
| FallbackValidationCategoryOfCurrentProcess(); |
| |
| RecordResultHistogram("TeamIdentifier", team_id); |
| |
| RecordResultHistogram("ValidationCategory", validation_category); |
| |
| RecordResultHistogram("FallbackValidationCategory", |
| fallback_validation_category); |
| |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| bool team_id_has_expected_value = team_id == std::string("EQHXZ8M8AV"); |
| if (team_id.has_value()) { |
| RecordHasExpectedValueHistogram("TeamIdentifier", |
| team_id_has_expected_value); |
| } |
| |
| bool validation_category_has_expected_value = |
| validation_category == ValidationCategory::DeveloperId; |
| if (validation_category.has_value()) { |
| RecordHasExpectedValueHistogram("ValidationCategory", |
| validation_category_has_expected_value); |
| } |
| |
| bool fallback_validation_category_has_expected_value = |
| fallback_validation_category == ValidationCategory::DeveloperId; |
| if (fallback_validation_category.has_value()) { |
| RecordHasExpectedValueHistogram( |
| "FallbackValidationCategory", |
| fallback_validation_category_has_expected_value); |
| } |
| #endif |
| |
| std::optional<ProcessRequirement> requirement; |
| { |
| ScopedUmaHistogramTimer timer( |
| "Mac.ProcessRequirement.Timing.BuildSameIdentityRequirement"); |
| requirement = |
| Builder().SignedWithSameIdentity().CheckDynamicValidityOnly().Build(); |
| } |
| |
| if (requirement) { |
| ScopedUmaHistogramTimer timer( |
| "Mac.ProcessRequirement.Timing.ValidateSameIdentity"); |
| bool result = requirement->ValidateProcess( |
| AuditTokenForCurrentProcess(), OuterBundleCachedInfoPlistData()); |
| base::UmaHistogramBoolean("Mac.ProcessRequirement.CurrentProcessValid", |
| result); |
| } |
| } |
| |
| } // namespace base::mac |