| // 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 |