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