• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/mac/process_requirement.h"
6 
7 #include <Kernel/kern/cs_blobs.h>
8 #include <Security/Security.h>
9 #include <mach/kern_return.h>
10 #include <stdint.h>
11 #include <sys/errno.h>
12 
13 #include <optional>
14 
15 #include "base/apple/mach_logging.h"
16 #include "base/apple/osstatus_logging.h"
17 #include "base/apple/scoped_cftyperef.h"
18 #include "base/check_op.h"
19 #include "base/containers/span.h"
20 #include "base/debug/crash_logging.h"
21 #include "base/debug/dump_without_crashing.h"
22 #include "base/features.h"
23 #include "base/logging.h"
24 #include "base/mac/code_signature.h"
25 #include "base/mac/code_signature_spi.h"
26 #include "base/mac/info_plist_data.h"
27 #include "base/mac/mac_util.h"
28 #include "base/metrics/histogram_functions.h"
29 #include "base/no_destructor.h"
30 #include "base/notreached.h"
31 #include "base/ranges/algorithm.h"
32 #include "base/strings/strcat.h"
33 #include "base/strings/string_util.h"
34 #include "base/task/thread_pool.h"
35 #include "base/types/expected.h"
36 #include "base/types/expected_macros.h"
37 #include "build/branding_buildflags.h"
38 
39 using base::apple::ScopedCFTypeRef;
40 
41 namespace base::mac {
42 
43 enum class ValidationCategory : unsigned int {
44   Invalid = CS_VALIDATION_CATEGORY_INVALID,
45   Platform = CS_VALIDATION_CATEGORY_PLATFORM,
46   TestFlight = CS_VALIDATION_CATEGORY_TESTFLIGHT,
47   Development = CS_VALIDATION_CATEGORY_DEVELOPMENT,
48   AppStore = CS_VALIDATION_CATEGORY_APP_STORE,
49   Enterprise = CS_VALIDATION_CATEGORY_ENTERPRISE,
50   DeveloperId = CS_VALIDATION_CATEGORY_DEVELOPER_ID,
51   LocalSigning = CS_VALIDATION_CATEGORY_LOCAL_SIGNING,
52   Rosetta = CS_VALIDATION_CATEGORY_ROSETTA,
53   OopJit = CS_VALIDATION_CATEGORY_OOPJIT,
54   None = CS_VALIDATION_CATEGORY_NONE,
55 };
56 
57 namespace {
58 
59 // Requirements derived from the designated requirements described in TN3127:
60 // Inside Code Signing: Requirements
61 // (https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements).
62 constexpr std::string_view kAnyDeveloperIdRequirement =
63     "(anchor apple generic and certificate "
64     "1[field.1.2.840.113635.100.6.2.6] exists and certificate "
65     "leaf[field.1.2.840.113635.100.6.1.13] exists)";
66 constexpr std::string_view kAnyAppStoreRequirement =
67     "(anchor apple generic and certificate "
68     "leaf[field.1.2.840.113635.100.6.1.9] exists)";
69 constexpr std::string_view kAnyDevelopmentRequirement =
70     "(anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.1] "
71     "exists)";
72 
73 // A requirement string that will match ad-hoc signed code. It will also match
74 // code signed with non-Apple certificates, but those are not supported by
75 // `ProcessRequirement`.
76 constexpr std::string_view kNonAppleAnchorRequirement =
77     "!(anchor apple generic)";
78 
79 struct CSOpsSystemCallProviderImpl
80     : ProcessRequirement::CSOpsSystemCallProvider {
81   ~CSOpsSystemCallProviderImpl() override = default;
82 
csopsbase::mac::__anone541d2d80111::CSOpsSystemCallProviderImpl83   int csops(pid_t pid,
84             unsigned int ops,
85             void* useraddr,
86             size_t usersize) override {
87     return ::csops(pid, ops, useraddr, usersize);
88   }
89 
SupportsValidationCategorybase::mac::__anone541d2d80111::CSOpsSystemCallProviderImpl90   bool SupportsValidationCategory() const override {
91     // macOS versions prior to macOS 13 do not support
92     // CS_OPS_VALIDATION_CATEGORY.
93     return MacOSMajorVersion() >= 13;
94   }
95 };
96 
DefaultCSOpsProvider()97 ProcessRequirement::CSOpsSystemCallProvider* DefaultCSOpsProvider() {
98   static NoDestructor<CSOpsSystemCallProviderImpl> default_provider;
99   return default_provider.get();
100 }
101 
CSOpsProvider()102 ProcessRequirement::CSOpsSystemCallProvider*& CSOpsProvider() {
103   static ProcessRequirement::CSOpsSystemCallProvider* provider =
104       DefaultCSOpsProvider();
105   return provider;
106 }
107 
TeamIdentifierOfCurrentProcess()108 base::expected<std::string, int> TeamIdentifierOfCurrentProcess() {
109   struct {
110     uint32_t type;
111     uint32_t length;
112     char identifier[CS_MAX_TEAMID_LEN + 1];
113   } result_data;
114   int result = CSOpsProvider()->csops(getpid(), CS_OPS_TEAMID, &result_data,
115                                       sizeof(result_data));
116   if (result < 0) {
117     if (errno != ENOENT && errno != EINVAL) {
118       // Don't log an error for `ENOENT` or `EINVAL` as they are expected in
119       // ad-hoc signed builds and unsigned builds respectively, such as during
120       // local development.
121       PLOG(ERROR) << "csops(CS_OPS_TEAMID) failed";
122     }
123 
124     return base::unexpected(errno);
125   }
126 
127   return std::string(result_data.identifier);
128 }
129 
ValidationCategoryOfCurrentProcess()130 base::expected<ValidationCategory, int> ValidationCategoryOfCurrentProcess() {
131   ValidationCategory validation_category = ValidationCategory::Invalid;
132   int result =
133       CSOpsProvider()->csops(getpid(), CS_OPS_VALIDATION_CATEGORY,
134                              &validation_category, sizeof(validation_category));
135   if (result < 0) {
136     if (errno != EINVAL) {
137       // Don't log an error for `EINVAL` as it is expected in unsigned builds,
138       // such as during local development.
139       PLOG(ERROR) << "csops(CS_OPS_VALIDATION_CATEGORY) failed";
140     }
141     return base::unexpected(errno);
142   }
143 
144   return validation_category;
145 }
146 
147 // Determine the validation category of the current process by evaluating
148 // the current process's code signature against requirements that represent
149 // each of the validation categories we're interested in.
150 base::expected<ValidationCategory, OSStatus>
FallbackValidationCategoryOfCurrentProcess()151 FallbackValidationCategoryOfCurrentProcess() {
152   ASSIGN_OR_RETURN(ScopedCFTypeRef<SecCodeRef> self_code,
153                    DynamicCodeObjectForCurrentProcess());
154 
155   // Do initial validation without a requirement to detect problems with the
156   // code signature itself. We do basic validation only as the validation is
157   // secondary to requirement matching in this case.
158   if (OSStatus status = SecStaticCodeCheckValidity(
159           self_code.get(), kSecCSBasicValidateOnly, nullptr)) {
160     if (status == errSecCSUnsigned) {
161       return ValidationCategory::None;
162     }
163 
164     OSSTATUS_LOG(ERROR, status)
165         << "Unable to derive validation category for current "
166            "process. Signature validation of current process failed";
167     return base::unexpected(status);
168   }
169 
170   std::pair<ValidationCategory, std::string_view> supported_categories[] = {
171       {ValidationCategory::DeveloperId, kAnyDeveloperIdRequirement},
172       {ValidationCategory::AppStore, kAnyAppStoreRequirement},
173       {ValidationCategory::Development, kAnyDevelopmentRequirement},
174       {ValidationCategory::None, kNonAppleAnchorRequirement},
175   };
176 
177   for (auto& [category, requirement] : supported_categories) {
178     OSStatus status =
179         SecStaticCodeCheckValidity(self_code.get(), kSecCSBasicValidateOnly,
180                                    RequirementFromString(requirement).get());
181     switch (status) {
182       case errSecSuccess:
183         // Requirement matched so we now know the validation category.
184         return category;
185 
186       case errSecCSReqFailed:
187         // Requirement did not match. On to the next one.
188         continue;
189 
190       default:
191         OSSTATUS_LOG(INFO, status)
192             << "Unexpected error when evaluating requirement " << requirement;
193     }
194   }
195 
196   LOG(ERROR) << "Unable to derive validation category for current process. "
197                 "Signature did not match any supported requirement.";
198   return base::unexpected(errSecFunctionFailed);
199 }
200 
RequirementStringForValidationCategory(ValidationCategory category)201 std::string RequirementStringForValidationCategory(
202     ValidationCategory category) {
203   // It is not meaningful to create a requirement string for an unsigned or
204   // ad-hoc signed process.
205   CHECK_NE(category, ValidationCategory::None);
206 
207   switch (category) {
208     case ValidationCategory::DeveloperId:
209       return std::string(kAnyDeveloperIdRequirement);
210     case ValidationCategory::AppStore:
211       return std::string(kAnyAppStoreRequirement);
212     case ValidationCategory::Development:
213       return std::string(kAnyDevelopmentRequirement);
214     default:
215       NOTREACHED() << "Unsupported process validation category: "
216                    << static_cast<unsigned int>(category);
217   }
218 }
219 
AuditTokenForCurrentProcess()220 audit_token_t AuditTokenForCurrentProcess() {
221   audit_token_t token;
222   mach_msg_type_number_t count = TASK_AUDIT_TOKEN_COUNT;
223   kern_return_t kr = task_info(mach_task_self(), TASK_AUDIT_TOKEN,
224                                reinterpret_cast<task_info_t>(&token), &count);
225   MACH_CHECK(kr == KERN_SUCCESS, kr) << "task_info(TASK_AUDIT_TOKEN)";
226   return token;
227 }
228 
229 }  // namespace
230 
231 ProcessRequirement::Builder::Builder() = default;
232 ProcessRequirement::Builder::~Builder() = default;
233 
234 ProcessRequirement::Builder::Builder(Builder&&) = default;
235 ProcessRequirement::Builder& ProcessRequirement::Builder::operator=(Builder&&) =
236     default;
237 
Identifier(std::string identifier)238 ProcessRequirement::Builder ProcessRequirement::Builder::Identifier(
239     std::string identifier) && {
240   CHECK(identifier.size());
241   CHECK(identifiers_.empty());
242   identifiers_.push_back(std::move(identifier));
243   return std::move(*this);
244 }
245 
IdentifierIsOneOf(std::vector<std::string> identifiers)246 ProcessRequirement::Builder ProcessRequirement::Builder::IdentifierIsOneOf(
247     std::vector<std::string> identifiers) && {
248   CHECK(identifiers.size());
249   CHECK(base::ranges::all_of(identifiers, &std::string::size));
250   CHECK(identifiers_.empty());
251   identifiers_ = std::move(identifiers);
252   return std::move(*this);
253 }
254 
255 ProcessRequirement::Builder
SignedWithSameIdentity()256 ProcessRequirement::Builder::SignedWithSameIdentity() && {
257   return std::move(*this).HasSameTeamIdentifier().HasSameCertificateType();
258 }
259 
260 ProcessRequirement::Builder
HasSameTeamIdentifier()261 ProcessRequirement::Builder::HasSameTeamIdentifier() && {
262   CHECK(team_identifier_.empty());
263 
264   has_same_team_identifier_called_ = true;
265 
266   auto team_identifier = TeamIdentifierOfCurrentProcess();
267   if (team_identifier.has_value()) {
268     team_identifier_ = std::move(*team_identifier);
269     return std::move(*this);
270   } else if (team_identifier.error() == ENOENT ||
271              team_identifier.error() == EINVAL) {
272     // ENOENT is returned when the process is ad-hoc signed and has no team
273     // identifier. EINVAL is returned when the process is unsigned.
274     team_identifier_ = "";
275     return std::move(*this);
276   }
277 
278   LOG(ERROR) << "HasSameTeamIdentifier failed to retrieve team identifier of "
279                 "current process";
280   failed_ = true;
281   return std::move(*this);
282 }
283 
284 ProcessRequirement::Builder
HasSameCertificateType()285 ProcessRequirement::Builder::HasSameCertificateType() && {
286   CHECK(!validation_category_);
287 
288   has_same_certificate_type_called_ = true;
289 
290   if (CSOpsProvider()->SupportsValidationCategory()) {
291     auto validation_category = ValidationCategoryOfCurrentProcess();
292     if (validation_category.has_value()) {
293       validation_category_ = *validation_category;
294     } else if (validation_category.error() == EINVAL) {
295       // EINVAL on versions of macOS that support CS_OPS_VALIDATION_CATEGORY
296       // indicates that the process is unsigned or the process has an invalid
297       // code signature.
298       validation_category_ = ValidationCategory::None;
299     } else {
300       failed_ = true;
301     }
302   } else {
303     // Older macOS versions do not support CS_OPS_VALIDATION_CATEGORY. Derive
304     // the validation category via Security.framework instead.
305     static auto validation_category =
306         FallbackValidationCategoryOfCurrentProcess();
307     if (validation_category.has_value()) {
308       validation_category_ = *validation_category;
309     } else {
310       failed_ = true;
311     }
312   }
313 
314   return std::move(*this);
315 }
316 
TeamIdentifier(std::string team_identifier)317 ProcessRequirement::Builder ProcessRequirement::Builder::TeamIdentifier(
318     std::string team_identifier) && {
319   CHECK(team_identifier_.empty());
320   CHECK(base::ranges::all_of(team_identifier, base::IsAsciiAlphaNumeric<char>));
321   team_identifier_ = std::move(team_identifier);
322   has_same_team_identifier_called_ = false;
323   return std::move(*this);
324 }
325 
326 ProcessRequirement::Builder
DeveloperIdCertificateType()327 ProcessRequirement::Builder::DeveloperIdCertificateType() && {
328   validation_category_ = ValidationCategory::DeveloperId;
329   has_same_certificate_type_called_ = false;
330   return std::move(*this);
331 }
332 
333 ProcessRequirement::Builder
AppStoreCertificateType()334 ProcessRequirement::Builder::AppStoreCertificateType() && {
335   validation_category_ = ValidationCategory::AppStore;
336   has_same_certificate_type_called_ = false;
337   return std::move(*this);
338 }
339 
340 ProcessRequirement::Builder
DevelopmentCertificateType()341 ProcessRequirement::Builder::DevelopmentCertificateType() && {
342   validation_category_ = ValidationCategory::Development;
343   has_same_certificate_type_called_ = false;
344   return std::move(*this);
345 }
346 
347 ProcessRequirement::Builder
CheckDynamicValidityOnly()348 ProcessRequirement::Builder::CheckDynamicValidityOnly() && {
349   dynamic_validity_only_ = true;
350   return std::move(*this);
351 }
352 
Build()353 std::optional<ProcessRequirement> ProcessRequirement::Builder::Build() && {
354   if (failed_) {
355     VLOG(2)
356         << "ProcessRequirement::Builder::Build: failed validation -> nullopt";
357     return std::nullopt;
358   }
359 
360   ValidationCategory validation_category =
361       validation_category_.value_or(ValidationCategory::None);
362 
363   if (validation_category == ValidationCategory::None ||
364       validation_category == ValidationCategory::Platform) {
365     // A validation category of None or Platform with a non-empty team ID is not
366     // a valid combination, but should not be treated as programmer error if the
367     // validation category came from the kernel.
368     if (team_identifier_.size() && has_same_certificate_type_called_) {
369       VLOG(2) << "ProcessRequirement::Builder::Build: have team ID but kernel "
370                  "returned validation category of none or platform -> nullopt";
371       return std::nullopt;
372     }
373 
374     CHECK(team_identifier_.empty())
375         << "A process requirement matching on a team identifier without "
376            "specifying a certificate type is unsafe.";
377   } else {
378     // An empty team ID with a valid validation category is not a valid
379     // combination, but should not be treated as programmer error if the empty
380     // team ID came from the kernel.
381     if (team_identifier_.empty() && has_same_team_identifier_called_) {
382       VLOG(2) << "ProcessRequirement::Builder::Build: have validation category "
383                  "but kernel returned empty team ID -> nullopt";
384       return std::nullopt;
385     }
386 
387     CHECK(team_identifier_.size())
388         << "A process requirement without a team identifier is unsafe as it "
389            "can be matched by any signing identity of that type.";
390   }
391 
392   return ProcessRequirement(std::move(identifiers_),
393                             std::move(team_identifier_), validation_category,
394                             dynamic_validity_only_);
395 }
396 
ProcessRequirement(std::vector<std::string> identifiers,std::string team_identifier,ValidationCategory validation_category,bool dynamic_validity_only)397 ProcessRequirement::ProcessRequirement(std::vector<std::string> identifiers,
398                                        std::string team_identifier,
399                                        ValidationCategory validation_category,
400                                        bool dynamic_validity_only)
401     : identifiers_(std::move(identifiers)),
402       team_identifier_(std::move(team_identifier)),
403       validation_category_(validation_category),
404       dynamic_validity_only_(dynamic_validity_only) {
405   CHECK(validation_category_ != ValidationCategory::Invalid);
406 }
407 
ProcessRequirement(ForTesting for_testing)408 ProcessRequirement::ProcessRequirement(ForTesting for_testing)
409     : for_testing_(for_testing),
410       validation_category_(ValidationCategory::Invalid) {}
411 
412 ProcessRequirement::~ProcessRequirement() = default;
413 
414 ProcessRequirement::ProcessRequirement(const ProcessRequirement&) = default;
415 ProcessRequirement& ProcessRequirement::operator=(const ProcessRequirement&) =
416     default;
417 
418 ProcessRequirement::ProcessRequirement(ProcessRequirement&&) = default;
419 ProcessRequirement& ProcessRequirement::operator=(ProcessRequirement&&) =
420     default;
421 
422 // static
AlwaysMatchesForTesting()423 ProcessRequirement ProcessRequirement::AlwaysMatchesForTesting() {
424   return ProcessRequirement(ForTesting::AlwaysMatches);
425 }
426 
427 // static
NeverMatchesForTesting()428 ProcessRequirement ProcessRequirement::NeverMatchesForTesting() {
429   return ProcessRequirement(ForTesting::NeverMatches);
430 }
431 
SetShouldCheckDynamicValidityOnlyForTesting()432 void ProcessRequirement::SetShouldCheckDynamicValidityOnlyForTesting() {
433   dynamic_validity_only_ = true;
434 }
435 
RequiresSignatureValidation() const436 bool ProcessRequirement::RequiresSignatureValidation() const {
437   if (for_testing_.has_value()) {
438     // `ForTesting::AlwaysMatches` does not require validation because
439     // a test process is likely to be unsigned.
440     // `ForTesting::NeverMatches` will fail signature validation with
441     // `errSecCSUnsigned` if the process is unsigned, and will fail requirement
442     // evaluation if the process has a valid ad-hoc signature.
443     return for_testing_.value() == ForTesting::NeverMatches;
444   }
445 
446   // All validation categories besides none (ad-hoc signature or unsigned) and
447   // platform require validation.
448   //
449   // It is not useful to validate an ad-hoc signature as anyone can create an
450   // ad-hoc signature that matches this requirement.
451   //
452   // Being classified as a platform binary indicates that the
453   // `amfi_get_out_of_my_way=1` boot argument is set and there are no
454   // guarantees around process integrity.
455   return validation_category_ != ValidationCategory::None &&
456          validation_category_ != ValidationCategory::Platform;
457 }
458 
AsSecRequirement() const459 ScopedCFTypeRef<SecRequirementRef> ProcessRequirement::AsSecRequirement()
460     const {
461   if (for_testing_.has_value()) {
462     return AsSecRequirementForTesting(for_testing_.value());  // IN-TEST
463   }
464 
465   if (!RequiresSignatureValidation()) {
466     VLOG(2) << "ProcessRequirement::AsSecRequirement -> nullptr";
467     return ScopedCFTypeRef<SecRequirementRef>{nullptr};
468   }
469 
470   std::vector<std::string> clauses;
471 
472   if (identifiers_.size()) {
473     std::vector<std::string> identifier_clauses;
474     for (const std::string& identifier : identifiers_) {
475       identifier_clauses.push_back(StrCat({"identifier \"", identifier, "\""}));
476     }
477     if (identifier_clauses.size() == 1) {
478       clauses.push_back(std::move(identifier_clauses.front()));
479     } else {
480       std::string identifier_clause =
481           base::JoinString(identifier_clauses, " or ");
482       clauses.push_back(StrCat({"(", identifier_clause, ")"}));
483     }
484   }
485 
486   if (team_identifier_.size()) {
487     clauses.push_back(
488         StrCat({"certificate leaf[subject.OU] = \"", team_identifier_, "\""}));
489   }
490 
491   clauses.push_back(
492       RequirementStringForValidationCategory(validation_category_));
493 
494   std::string requirement_string = base::JoinString(clauses, " and ");
495   VLOG(2) << "ProcessRequirement::AsSecRequirement -> " << requirement_string;
496   apple::ScopedCFTypeRef<SecRequirementRef> requirement =
497       RequirementFromString(requirement_string);
498   CHECK(requirement) << "ProcessRequirement::AsSecRequirement generated a "
499                         "requirement string that could not be parsed.";
500   return requirement;
501 }
502 
503 // static
504 ScopedCFTypeRef<SecRequirementRef>
AsSecRequirementForTesting(ProcessRequirement::ForTesting for_testing)505 ProcessRequirement::AsSecRequirementForTesting(
506     ProcessRequirement::ForTesting for_testing) {
507   std::string requirement_string;
508   switch (for_testing) {
509     case ForTesting::AlwaysMatches: {
510       requirement_string = "(!info[ThisKeyDoesNotExist])";
511       break;
512     }
513     case ForTesting::NeverMatches: {
514       requirement_string = R"(identifier = "this is not the identifier")";
515       break;
516     }
517   }
518   ScopedCFTypeRef<SecRequirementRef> requirement =
519       RequirementFromString(requirement_string);
520   CHECK(requirement)
521       << "ProcessRequirement::AsSecRequirementForTesting generated a "
522          "requirement string that could not be parsed.";
523   return requirement;
524 }
525 
526 // static
SetCSOpsSystemCallProviderForTesting(CSOpsSystemCallProvider * csops_provider)527 void ProcessRequirement::SetCSOpsSystemCallProviderForTesting(
528     CSOpsSystemCallProvider* csops_provider) {
529   if (csops_provider) {
530     CSOpsProvider() = csops_provider;
531   } else {
532     CSOpsProvider() = DefaultCSOpsProvider();
533   }
534 }
535 
ValidateProcess(audit_token_t audit_token,base::span<const uint8_t> info_plist_data) const536 bool ProcessRequirement::ValidateProcess(
537     audit_token_t audit_token,
538     base::span<const uint8_t> info_plist_data) const {
539   if (!RequiresSignatureValidation()) {
540     // No signature validation required. Return success.
541     base::UmaHistogramBoolean("Mac.ProcessRequirement.ValidationRequired",
542                               false);
543     return true;
544   }
545   base::UmaHistogramBoolean("Mac.ProcessRequirement.ValidationRequired", true);
546 
547   // If the requirement specifies we are checking only the validity of the
548   // dynamic code then we must have Info.plist data.
549   if (dynamic_validity_only_) {
550     CHECK(info_plist_data.size())
551         << "info_plist_data is required when checking dynamic validity only.";
552   }
553 
554   if (OSStatus status = ProcessIsSignedAndFulfillsRequirement(
555           audit_token, AsSecRequirement().get(),
556           dynamic_validity_only_ ? SignatureValidationType::DynamicOnly
557                                  : SignatureValidationType::DynamicAndStatic,
558           base::as_string_view(info_plist_data))) {
559     OSSTATUS_LOG(ERROR, status) << "ProcessIsSignedAndFulfillsRequirement";
560     base::UmaHistogramSparse("Mac.ProcessRequirement.ValidationResult", status);
561     return false;
562   }
563 
564   base::UmaHistogramSparse("Mac.ProcessRequirement.ValidationResult",
565                            errSecSuccess);
566   return true;
567 }
568 
569 // static
MaybeGatherMetrics()570 void ProcessRequirement::MaybeGatherMetrics() {
571   static BASE_FEATURE(kGatherProcessRequirementMetrics,
572                       "GatherProcessRequirementMetrics",
573                       base::FEATURE_ENABLED_BY_DEFAULT);
574   if (base::FeatureList::IsEnabled(kGatherProcessRequirementMetrics)) {
575     base::ThreadPool::PostTask(
576         FROM_HERE,
577         {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
578          base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
579         base::BindOnce(&ProcessRequirement::GatherMetrics));
580   }
581 }
582 
583 namespace {
584 
585 template <typename T>
RecordResultHistogram(const std::string & field_name,const base::expected<T,int> & value)586 void RecordResultHistogram(const std::string& field_name,
587                            const base::expected<T, int>& value) {
588   base::UmaHistogramSparse("Mac.ProcessRequirement." + field_name + ".Result",
589                            value.error_or(0));
590 }
591 
592 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
RecordHasExpectedValueHistogram(const std::string & field_name,bool has_expected_value)593 void RecordHasExpectedValueHistogram(const std::string& field_name,
594                                      bool has_expected_value) {
595   base::UmaHistogramBoolean(
596       "Mac.ProcessRequirement." + field_name + ".HasExpectedValue",
597       has_expected_value);
598 }
599 #endif
600 
601 template <typename T>
StringForCrashKey(const base::expected<T,int> & value)602 std::string StringForCrashKey(const base::expected<T, int>& value) {
603   if (value.has_value()) {
604     if constexpr (std::is_same_v<T, std::string>) {
605       return value.value();
606     } else {
607       return NumberToString(static_cast<uint64_t>(value.value()));
608     }
609   }
610   return "error: " + NumberToString(value.error());
611 }
612 
613 }  // namespace
614 
615 // static
GatherMetrics()616 void ProcessRequirement::GatherMetrics() {
617   auto team_id = TeamIdentifierOfCurrentProcess();
618   auto validation_category = ValidationCategoryOfCurrentProcess();
619   auto fallback_validation_category =
620       FallbackValidationCategoryOfCurrentProcess();
621 
622   RecordResultHistogram("TeamIdentifier", team_id);
623 
624   RecordResultHistogram("ValidationCategory", validation_category);
625 
626   RecordResultHistogram("FallbackValidationCategory",
627                         fallback_validation_category);
628 
629 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
630   bool team_id_has_expected_value = team_id == std::string("EQHXZ8M8AV");
631   if (team_id.has_value()) {
632     RecordHasExpectedValueHistogram("TeamIdentifier",
633                                     team_id_has_expected_value);
634   }
635 
636   bool validation_category_has_expected_value =
637       validation_category == ValidationCategory::DeveloperId;
638   if (validation_category.has_value()) {
639     RecordHasExpectedValueHistogram("ValidationCategory",
640                                     validation_category_has_expected_value);
641   }
642 
643   bool fallback_validation_category_has_expected_value =
644       fallback_validation_category == ValidationCategory::DeveloperId;
645   if (fallback_validation_category.has_value()) {
646     RecordHasExpectedValueHistogram(
647         "FallbackValidationCategory",
648         fallback_validation_category_has_expected_value);
649   }
650 #endif
651 
652   std::optional<ProcessRequirement> requirement;
653   {
654     ScopedUmaHistogramTimer timer(
655         "Mac.ProcessRequirement.Timing.BuildSameIdentityRequirement");
656     requirement =
657         Builder().SignedWithSameIdentity().CheckDynamicValidityOnly().Build();
658   }
659 
660   if (requirement) {
661     ScopedUmaHistogramTimer timer(
662         "Mac.ProcessRequirement.Timing.ValidateSameIdentity");
663     bool result = requirement->ValidateProcess(
664         AuditTokenForCurrentProcess(), OuterBundleCachedInfoPlistData());
665     base::UmaHistogramBoolean("Mac.ProcessRequirement.CurrentProcessValid",
666                               result);
667   }
668 }
669 
670 }  // namespace base::mac
671