1use anyhow::{format_err, Context, Error};
6use camino::Utf8PathBuf;
7use cm_rust::{CapabilityTypeName, FidlIntoNative};
8use cm_types::{symmetrical_enums, Name, ParseError, Url};
9use fidl::unpersist;
10use fidl_fuchsia_component_decl as fdecl;
11use fidl_fuchsia_component_internal::{
12 self as component_internal, BuiltinBootResolver, CapabilityPolicyAllowlists,
13 DebugRegistrationPolicyAllowlists, LogDestination, RealmBuilderResolverAndRunner,
14};
15use log::warn;
16use moniker::{ChildName, ExtendedMoniker, Moniker, MonikerError};
17use std::collections::{HashMap, HashSet};
18use std::sync::Arc;
19use thiserror::Error;
20use version_history::{AbiRevision, AbiRevisionError, VersionHistory};
21
22#[derive(Debug, PartialEq, Eq)]
26pub struct RuntimeConfig {
27 pub list_children_batch_size: usize,
29
30 pub security_policy: Arc<SecurityPolicy>,
32
33 pub debug: bool,
40
41 pub trace_provider: TraceProvider,
44
45 pub enable_introspection: bool,
48
49 pub use_builtin_process_launcher: bool,
58
59 pub maintain_utc_clock: bool,
63
64 pub num_threads: u8,
67
68 pub namespace_capabilities: Vec<cm_rust::CapabilityDecl>,
70
71 pub builtin_capabilities: Vec<cm_rust::CapabilityDecl>,
73
74 pub root_component_url: Option<Url>,
78
79 pub component_id_index_path: Option<Utf8PathBuf>,
82
83 pub log_destination: LogDestination,
85
86 pub log_all_events: bool,
88
89 pub builtin_boot_resolver: BuiltinBootResolver,
92
93 pub realm_builder_resolver_and_runner: RealmBuilderResolverAndRunner,
95
96 pub abi_revision_policy: AbiRevisionPolicy,
98
99 pub vmex_source: VmexSource,
101
102 pub health_check: HealthCheck,
104}
105
106#[derive(Debug, PartialEq, Eq, Hash, Clone)]
108pub struct AllowlistEntry {
109 pub matchers: Vec<AllowlistMatcher>,
112}
113
114impl AllowlistEntry {
115 pub fn matches(&self, target_moniker: &Moniker) -> bool {
116 let mut iter = target_moniker.path().iter();
117
118 if self.matchers.is_empty() && !target_moniker.is_root() {
119 return false;
122 }
123
124 for matcher in &self.matchers {
125 let cur_child = if let Some(target_child) = iter.next() {
126 target_child
127 } else {
128 return false;
130 };
131 match matcher {
132 AllowlistMatcher::Exact(child) => {
133 if cur_child != child {
134 return false;
136 }
137 }
138 AllowlistMatcher::AnyChild => continue,
140 AllowlistMatcher::AnyDescendant => return true,
142 AllowlistMatcher::AnyDescendantInCollection(expected_collection) => {
143 if let Some(collection) = cur_child.collection() {
144 if collection == expected_collection {
145 return true;
148 } else {
149 return false;
151 }
152 } else {
153 return false;
155 }
156 }
157 AllowlistMatcher::AnyChildInCollection(expected_collection) => {
158 if let Some(collection) = cur_child.collection() {
159 if collection != expected_collection {
160 return false;
162 }
163 } else {
164 return false;
166 }
167 }
168 }
169 }
170
171 if iter.next().is_some() {
172 false
176 } else {
177 true
178 }
179 }
180}
181
182#[derive(Debug, PartialEq, Eq, Hash, Clone)]
183pub enum AllowlistMatcher {
184 Exact(ChildName),
187 AnyDescendant,
190 AnyChild,
193 AnyChildInCollection(Name),
196 AnyDescendantInCollection(Name),
199}
200
201pub struct AllowlistEntryBuilder {
202 parts: Vec<AllowlistMatcher>,
203}
204
205impl AllowlistEntryBuilder {
206 pub fn new() -> Self {
207 Self { parts: vec![] }
208 }
209
210 pub fn build_exact_from_moniker(m: &Moniker) -> AllowlistEntry {
211 Self::new().exact_from_moniker(m).build()
212 }
213
214 pub fn exact(mut self, name: &str) -> Self {
215 self.parts.push(AllowlistMatcher::Exact(ChildName::parse(name).unwrap()));
216 self
217 }
218
219 pub fn exact_from_moniker(mut self, m: &Moniker) -> Self {
220 let mut parts = m.path().clone().into_iter().map(|c| AllowlistMatcher::Exact(c)).collect();
221 self.parts.append(&mut parts);
222 self
223 }
224
225 pub fn any_child(mut self) -> Self {
226 self.parts.push(AllowlistMatcher::AnyChild);
227 self
228 }
229
230 pub fn any_descendant(mut self) -> AllowlistEntry {
231 self.parts.push(AllowlistMatcher::AnyDescendant);
232 self.build()
233 }
234
235 pub fn any_descendant_in_collection(mut self, collection: &str) -> AllowlistEntry {
236 self.parts
237 .push(AllowlistMatcher::AnyDescendantInCollection(Name::new(collection).unwrap()));
238 self.build()
239 }
240
241 pub fn any_child_in_collection(mut self, collection: &str) -> Self {
242 self.parts.push(AllowlistMatcher::AnyChildInCollection(Name::new(collection).unwrap()));
243 self
244 }
245
246 pub fn build(self) -> AllowlistEntry {
247 AllowlistEntry { matchers: self.parts }
248 }
249}
250
251#[derive(Debug, Clone, Default, PartialEq, Eq)]
253pub struct SecurityPolicy {
254 pub job_policy: JobPolicyAllowlists,
256
257 pub capability_policy: HashMap<CapabilityAllowlistKey, HashSet<AllowlistEntry>>,
262
263 pub debug_capability_policy:
268 HashMap<DebugCapabilityKey, HashSet<DebugCapabilityAllowlistEntry>>,
269
270 pub child_policy: ChildPolicyAllowlists,
273}
274
275#[derive(Debug, Hash, PartialEq, Eq, Clone)]
278pub struct DebugCapabilityKey {
279 pub name: Name,
280 pub source: CapabilityAllowlistSource,
281 pub capability: CapabilityTypeName,
282 pub env_name: Name,
283}
284
285#[derive(Debug, PartialEq, Eq, Clone, Hash)]
287pub struct DebugCapabilityAllowlistEntry {
288 dest: AllowlistEntry,
289}
290
291impl DebugCapabilityAllowlistEntry {
292 pub fn new(dest: AllowlistEntry) -> Self {
293 Self { dest }
294 }
295
296 pub fn matches(&self, dest: &Moniker) -> bool {
297 self.dest.matches(dest)
298 }
299}
300
301#[derive(Debug, Clone, Default, PartialEq, Eq)]
303pub struct JobPolicyAllowlists {
304 pub ambient_mark_vmo_exec: Vec<AllowlistEntry>,
310
311 pub main_process_critical: Vec<AllowlistEntry>,
317
318 pub create_raw_processes: Vec<AllowlistEntry>,
324}
325
326#[derive(Debug, Default, PartialEq, Eq, Clone)]
328pub struct ChildPolicyAllowlists {
329 pub reboot_on_terminate: Vec<AllowlistEntry>,
332}
333
334#[derive(Debug, PartialEq, Eq, Hash, Clone)]
337pub enum CapabilityAllowlistSource {
338 Self_,
339 Framework,
340 Capability,
341 Environment,
342 Void,
343}
344
345#[derive(Debug, Clone, Error, PartialEq, Eq)]
346pub enum CompatibilityCheckError {
347 #[error("Component did not present an ABI revision")]
348 AbiRevisionAbsent,
349 #[error(transparent)]
350 AbiRevisionInvalid(#[from] AbiRevisionError),
351}
352
353#[derive(Debug, PartialEq, Eq, Default, Clone)]
356pub struct AbiRevisionPolicy {
357 allowlist: Vec<AllowlistEntry>,
358}
359
360impl AbiRevisionPolicy {
361 pub fn new(allowlist: Vec<AllowlistEntry>) -> Self {
362 Self { allowlist }
363 }
364
365 pub fn check_compatibility(
369 &self,
370 version_history: &VersionHistory,
371 moniker: &Moniker,
372 abi_revision: Option<AbiRevision>,
373 ) -> Result<(), CompatibilityCheckError> {
374 let only_warn = self.allowlist.iter().any(|matcher| matcher.matches(moniker));
375
376 let Some(abi_revision) = abi_revision else {
377 return if only_warn {
378 warn!("Ignoring missing ABI revision in {} because it is allowlisted.", moniker);
379 Ok(())
380 } else {
381 Err(CompatibilityCheckError::AbiRevisionAbsent)
382 };
383 };
384
385 let abi_error = match version_history.check_abi_revision_for_runtime(abi_revision) {
386 Ok(()) => return Ok(()),
387 Err(AbiRevisionError::PlatformMismatch { .. })
388 | Err(AbiRevisionError::UnstableMismatch { .. })
389 | Err(AbiRevisionError::Malformed { .. }) => {
390 warn!(
392 "Unsupported platform ABI revision: 0x{}.
393This will become an error soon! See https://fxbug.dev/347724655",
394 abi_revision
395 );
396 return Ok(());
397 }
398 Err(e @ AbiRevisionError::TooNew { .. })
399 | Err(e @ AbiRevisionError::Retired { .. })
400 | Err(e @ AbiRevisionError::Invalid) => e,
401 };
402
403 if only_warn {
404 warn!(
405 "Ignoring AbiRevisionError in {} because it is allowlisted: {}",
406 moniker, abi_error
407 );
408 Ok(())
409 } else {
410 Err(CompatibilityCheckError::AbiRevisionInvalid(abi_error))
411 }
412 }
413}
414
415impl TryFrom<component_internal::AbiRevisionPolicy> for AbiRevisionPolicy {
416 type Error = Error;
417
418 fn try_from(abi_revision_policy: component_internal::AbiRevisionPolicy) -> Result<Self, Error> {
419 Ok(Self::new(parse_allowlist_entries(&abi_revision_policy.allowlist)?))
420 }
421}
422
423#[derive(Debug, PartialEq, Eq, Clone)]
426pub enum VmexSource {
427 SystemResource,
428 Namespace,
429}
430
431symmetrical_enums!(VmexSource, component_internal::VmexSource, SystemResource, Namespace);
432
433impl Default for VmexSource {
434 fn default() -> Self {
435 VmexSource::SystemResource
436 }
437}
438
439#[derive(Debug, PartialEq, Eq, Clone)]
442pub enum TraceProvider {
443 Namespace,
444 RootExposed,
445}
446
447symmetrical_enums!(TraceProvider, component_internal::TraceProvider, Namespace, RootExposed);
448
449impl Default for TraceProvider {
450 fn default() -> Self {
451 TraceProvider::Namespace
452 }
453}
454
455#[derive(Debug, PartialEq, Eq, Default, Clone)]
457pub struct HealthCheck {
458 pub monikers: Vec<String>,
459}
460
461impl HealthCheck {
462 pub fn new(monikers: Vec<String>) -> Self {
463 Self { monikers }
464 }
465}
466
467impl TryFrom<component_internal::HealthCheck> for HealthCheck {
468 type Error = Error;
469
470 fn try_from(health_check: component_internal::HealthCheck) -> Result<Self, Error> {
471 Ok(Self::new(health_check.monikers.unwrap()))
472 }
473}
474
475#[derive(Debug, PartialEq, Eq, Hash, Clone)]
479pub struct CapabilityAllowlistKey {
480 pub source_moniker: ExtendedMoniker,
481 pub source_name: Name,
482 pub source: CapabilityAllowlistSource,
483 pub capability: CapabilityTypeName,
484}
485
486impl Default for RuntimeConfig {
487 fn default() -> Self {
488 Self {
489 list_children_batch_size: 1000,
490 security_policy: Default::default(),
493 debug: false,
494 trace_provider: Default::default(),
495 enable_introspection: false,
496 use_builtin_process_launcher: false,
497 maintain_utc_clock: false,
498 num_threads: 1,
499 namespace_capabilities: vec![],
500 builtin_capabilities: vec![],
501 root_component_url: Default::default(),
502 component_id_index_path: None,
503 log_destination: LogDestination::Syslog,
504 log_all_events: false,
505 builtin_boot_resolver: BuiltinBootResolver::None,
506 realm_builder_resolver_and_runner: RealmBuilderResolverAndRunner::None,
507 abi_revision_policy: Default::default(),
508 vmex_source: Default::default(),
509 health_check: Default::default(),
510 }
511 }
512}
513
514impl RuntimeConfig {
515 pub fn new_from_bytes(bytes: &Vec<u8>) -> Result<Self, Error> {
516 Ok(Self::try_from(unpersist::<component_internal::Config>(&bytes)?)?)
517 }
518
519 fn translate_namespace_capabilities(
520 capabilities: Option<Vec<fdecl::Capability>>,
521 ) -> Result<Vec<cm_rust::CapabilityDecl>, Error> {
522 let capabilities = capabilities.unwrap_or(vec![]);
523 if let Some(c) = capabilities.iter().find(|c| {
524 !matches!(c, fdecl::Capability::Protocol(_) | fdecl::Capability::Directory(_))
525 }) {
526 return Err(format_err!("Type unsupported for namespace capability: {:?}", c));
527 }
528 cm_fidl_validator::validate_namespace_capabilities(&capabilities)?;
529 Ok(capabilities.into_iter().map(FidlIntoNative::fidl_into_native).collect())
530 }
531
532 fn translate_builtin_capabilities(
533 capabilities: Option<Vec<fdecl::Capability>>,
534 ) -> Result<Vec<cm_rust::CapabilityDecl>, Error> {
535 let capabilities = capabilities.unwrap_or(vec![]);
536 cm_fidl_validator::validate_builtin_capabilities(&capabilities)?;
537 Ok(capabilities.into_iter().map(FidlIntoNative::fidl_into_native).collect())
538 }
539}
540
541#[derive(Debug, Clone, Error, PartialEq, Eq)]
542pub enum AllowlistEntryParseError {
543 #[error("Invalid child moniker ({0:?}) in allowlist entry: {1:?}")]
544 InvalidChildName(String, #[source] MonikerError),
545 #[error("Invalid collection name ({0:?}) in allowlist entry: {1:?}")]
546 InvalidCollectionName(String, #[source] ParseError),
547 #[error("Allowlist entry ({0:?}) must start with a '/'")]
548 NoLeadingSlash(String),
549 #[error("Allowlist entry ({0:?}) must have '**' wildcard only at the end")]
550 DescendantWildcardOnlyAtEnd(String),
551}
552
553fn parse_allowlist_entries(strs: &Option<Vec<String>>) -> Result<Vec<AllowlistEntry>, Error> {
554 let strs = match strs {
555 Some(strs) => strs,
556 None => return Ok(Vec::new()),
557 };
558
559 let mut entries = vec![];
560 for input in strs {
561 let entry = parse_allowlist_entry(input)?;
562 entries.push(entry);
563 }
564 Ok(entries)
565}
566
567fn parse_allowlist_entry(input: &str) -> Result<AllowlistEntry, AllowlistEntryParseError> {
568 let entry = if let Some(entry) = input.strip_prefix('/') {
569 entry
570 } else {
571 return Err(AllowlistEntryParseError::NoLeadingSlash(input.to_string()));
572 };
573
574 if entry.is_empty() {
575 return Ok(AllowlistEntry { matchers: vec![] });
576 }
577
578 if entry.contains("**") && !entry.ends_with("**") {
579 return Err(AllowlistEntryParseError::DescendantWildcardOnlyAtEnd(input.to_string()));
580 }
581
582 let mut parts = vec![];
583 for name in entry.split('/') {
584 let part = match name {
585 "**" => AllowlistMatcher::AnyDescendant,
586 "*" => AllowlistMatcher::AnyChild,
587 name => {
588 if let Some(collection_name) = name.strip_suffix(":**") {
589 let collection_name = Name::new(collection_name).map_err(|e| {
590 AllowlistEntryParseError::InvalidCollectionName(
591 collection_name.to_string(),
592 e,
593 )
594 })?;
595 AllowlistMatcher::AnyDescendantInCollection(collection_name)
596 } else if let Some(collection_name) = name.strip_suffix(":*") {
597 let collection_name = Name::new(collection_name).map_err(|e| {
598 AllowlistEntryParseError::InvalidCollectionName(
599 collection_name.to_string(),
600 e,
601 )
602 })?;
603 AllowlistMatcher::AnyChildInCollection(collection_name)
604 } else {
605 let child_moniker = ChildName::parse(name).map_err(|e| {
606 AllowlistEntryParseError::InvalidChildName(name.to_string(), e)
607 })?;
608 AllowlistMatcher::Exact(child_moniker)
609 }
610 }
611 };
612 parts.push(part);
613 }
614
615 Ok(AllowlistEntry { matchers: parts })
616}
617
618fn as_usize_or_default(value: Option<u32>, default: usize) -> usize {
619 match value {
620 Some(value) => value as usize,
621 None => default,
622 }
623}
624
625#[derive(Debug, Clone, Error, PartialEq, Eq)]
626pub enum PolicyConfigError {
627 #[error("Capability source name was empty in a capability policy entry.")]
628 EmptyCapabilitySourceName,
629 #[error("Capability type was empty in a capability policy entry.")]
630 EmptyAllowlistedCapability,
631 #[error("Debug registration type was empty in a debug policy entry.")]
632 EmptyAllowlistedDebugRegistration,
633 #[error("Target moniker was empty in a debug policy entry.")]
634 EmptyTargetMonikerDebugRegistration,
635 #[error("Environment name was empty or invalid in a debug policy entry.")]
636 InvalidEnvironmentNameDebugRegistration,
637 #[error("Capability from type was empty in a capability policy entry.")]
638 EmptyFromType,
639 #[error("Capability source_moniker was empty in a capability policy entry.")]
640 EmptySourceMoniker,
641 #[error("Invalid source capability.")]
642 InvalidSourceCapability,
643 #[error("Unsupported allowlist capability type")]
644 UnsupportedAllowlistedCapability,
645}
646
647impl TryFrom<component_internal::Config> for RuntimeConfig {
648 type Error = Error;
649
650 fn try_from(config: component_internal::Config) -> Result<Self, Error> {
651 let default = RuntimeConfig::default();
652
653 let list_children_batch_size =
654 as_usize_or_default(config.list_children_batch_size, default.list_children_batch_size);
655 let num_threads = config.num_threads.unwrap_or(default.num_threads);
656
657 let root_component_url = config.root_component_url.map(Url::new).transpose()?;
658
659 let security_policy = config
660 .security_policy
661 .map(SecurityPolicy::try_from)
662 .transpose()
663 .context("Unable to parse security policy")?
664 .unwrap_or_default();
665
666 let abi_revision_policy = config
667 .abi_revision_policy
668 .map(AbiRevisionPolicy::try_from)
669 .transpose()
670 .context("Unable to parse ABI revision policy")?
671 .unwrap_or_default();
672
673 let vmex_source = config.vmex_source.map(VmexSource::from).unwrap_or_default();
674
675 let trace_provider = config.trace_provider.map(TraceProvider::from).unwrap_or_default();
676
677 let health_check = config
678 .health_check
679 .map(HealthCheck::try_from)
680 .transpose()
681 .context("Unable to parse health checks policy")?
682 .unwrap_or_default();
683
684 Ok(RuntimeConfig {
685 list_children_batch_size,
686 security_policy: Arc::new(security_policy),
687 namespace_capabilities: Self::translate_namespace_capabilities(
688 config.namespace_capabilities,
689 )?,
690 builtin_capabilities: Self::translate_builtin_capabilities(
691 config.builtin_capabilities,
692 )?,
693 debug: config.debug.unwrap_or(default.debug),
694 trace_provider,
695 enable_introspection: config
696 .enable_introspection
697 .unwrap_or(default.enable_introspection),
698 use_builtin_process_launcher: config
699 .use_builtin_process_launcher
700 .unwrap_or(default.use_builtin_process_launcher),
701 maintain_utc_clock: config.maintain_utc_clock.unwrap_or(default.maintain_utc_clock),
702 num_threads,
703 root_component_url,
704 component_id_index_path: config.component_id_index_path.map(Into::into),
705 log_destination: config.log_destination.unwrap_or(default.log_destination),
706 log_all_events: config.log_all_events.unwrap_or(default.log_all_events),
707 builtin_boot_resolver: config
708 .builtin_boot_resolver
709 .unwrap_or(default.builtin_boot_resolver),
710 realm_builder_resolver_and_runner: config
711 .realm_builder_resolver_and_runner
712 .unwrap_or(default.realm_builder_resolver_and_runner),
713 abi_revision_policy,
714 vmex_source,
715 health_check,
716 })
717 }
718}
719
720fn parse_capability_policy(
721 capability_policy: Option<CapabilityPolicyAllowlists>,
722) -> Result<HashMap<CapabilityAllowlistKey, HashSet<AllowlistEntry>>, Error> {
723 let capability_policy = if let Some(capability_policy) = capability_policy {
724 if let Some(allowlist) = capability_policy.allowlist {
725 let mut policies = HashMap::new();
726 for e in allowlist.into_iter() {
727 let source_moniker = ExtendedMoniker::parse_str(
728 e.source_moniker
729 .as_deref()
730 .ok_or_else(|| Error::new(PolicyConfigError::EmptySourceMoniker))?,
731 )?;
732 let source_name = if let Some(source_name) = e.source_name {
733 Ok(source_name
734 .parse()
735 .map_err(|_| Error::new(PolicyConfigError::InvalidSourceCapability))?)
736 } else {
737 Err(PolicyConfigError::EmptyCapabilitySourceName)
738 }?;
739 let source = match e.source {
740 Some(fdecl::Ref::Self_(_)) => Ok(CapabilityAllowlistSource::Self_),
741 Some(fdecl::Ref::Framework(_)) => Ok(CapabilityAllowlistSource::Framework),
742 Some(fdecl::Ref::Capability(_)) => Ok(CapabilityAllowlistSource::Capability),
743 Some(fdecl::Ref::Environment(_)) => Ok(CapabilityAllowlistSource::Environment),
744 _ => Err(Error::new(PolicyConfigError::InvalidSourceCapability)),
745 }?;
746
747 let capability = if let Some(capability) = e.capability.as_ref() {
748 match &capability {
749 component_internal::AllowlistedCapability::Directory(_) => {
750 Ok(CapabilityTypeName::Directory)
751 }
752 component_internal::AllowlistedCapability::Protocol(_) => {
753 Ok(CapabilityTypeName::Protocol)
754 }
755 component_internal::AllowlistedCapability::Service(_) => {
756 Ok(CapabilityTypeName::Service)
757 }
758 component_internal::AllowlistedCapability::Storage(_) => {
759 Ok(CapabilityTypeName::Storage)
760 }
761 component_internal::AllowlistedCapability::Runner(_) => {
762 Ok(CapabilityTypeName::Runner)
763 }
764 component_internal::AllowlistedCapability::Resolver(_) => {
765 Ok(CapabilityTypeName::Resolver)
766 }
767 _ => Err(Error::new(PolicyConfigError::EmptyAllowlistedCapability)),
768 }
769 } else {
770 Err(Error::new(PolicyConfigError::EmptyAllowlistedCapability))
771 }?;
772
773 let target_monikers =
774 HashSet::from_iter(parse_allowlist_entries(&e.target_monikers)?);
775
776 policies.insert(
777 CapabilityAllowlistKey { source_moniker, source_name, source, capability },
778 target_monikers,
779 );
780 }
781 policies
782 } else {
783 HashMap::new()
784 }
785 } else {
786 HashMap::new()
787 };
788 Ok(capability_policy)
789}
790
791fn parse_debug_capability_policy(
792 debug_registration_policy: Option<DebugRegistrationPolicyAllowlists>,
793) -> Result<HashMap<DebugCapabilityKey, HashSet<DebugCapabilityAllowlistEntry>>, Error> {
794 let debug_capability_policy = if let Some(debug_capability_policy) = debug_registration_policy {
795 if let Some(allowlist) = debug_capability_policy.allowlist {
796 let mut policies: HashMap<DebugCapabilityKey, HashSet<DebugCapabilityAllowlistEntry>> =
797 HashMap::new();
798 for e in allowlist.into_iter() {
799 let moniker = parse_allowlist_entry(
800 e.moniker
801 .as_deref()
802 .ok_or_else(|| Error::new(PolicyConfigError::EmptySourceMoniker))?,
803 )?;
804 let name = if let Some(name) = e.name.as_ref() {
805 Ok(name
806 .parse()
807 .map_err(|_| Error::new(PolicyConfigError::InvalidSourceCapability))?)
808 } else {
809 Err(PolicyConfigError::EmptyCapabilitySourceName)
810 }?;
811
812 let capability = if let Some(capability) = e.debug.as_ref() {
813 match &capability {
814 component_internal::AllowlistedDebugRegistration::Protocol(_) => {
815 Ok(CapabilityTypeName::Protocol)
816 }
817 _ => Err(Error::new(PolicyConfigError::EmptyAllowlistedDebugRegistration)),
818 }
819 } else {
820 Err(Error::new(PolicyConfigError::EmptyAllowlistedDebugRegistration))
821 }?;
822
823 let env_name = e
824 .environment_name
825 .map(|n| n.parse().ok())
826 .flatten()
827 .ok_or(PolicyConfigError::InvalidEnvironmentNameDebugRegistration)?;
828
829 let key = DebugCapabilityKey {
830 name,
831 source: CapabilityAllowlistSource::Self_,
832 capability,
833 env_name,
834 };
835 let value = DebugCapabilityAllowlistEntry::new(moniker);
836 if let Some(h) = policies.get_mut(&key) {
837 h.insert(value);
838 } else {
839 policies.insert(key, vec![value].into_iter().collect());
840 }
841 }
842 policies
843 } else {
844 HashMap::new()
845 }
846 } else {
847 HashMap::new()
848 };
849 Ok(debug_capability_policy)
850}
851
852impl TryFrom<component_internal::SecurityPolicy> for SecurityPolicy {
853 type Error = Error;
854
855 fn try_from(security_policy: component_internal::SecurityPolicy) -> Result<Self, Error> {
856 let job_policy = if let Some(job_policy) = &security_policy.job_policy {
857 let ambient_mark_vmo_exec = parse_allowlist_entries(&job_policy.ambient_mark_vmo_exec)?;
858 let main_process_critical = parse_allowlist_entries(&job_policy.main_process_critical)?;
859 let create_raw_processes = parse_allowlist_entries(&job_policy.create_raw_processes)?;
860 JobPolicyAllowlists {
861 ambient_mark_vmo_exec,
862 main_process_critical,
863 create_raw_processes,
864 }
865 } else {
866 JobPolicyAllowlists::default()
867 };
868
869 let capability_policy = parse_capability_policy(security_policy.capability_policy)?;
870
871 let debug_capability_policy =
872 parse_debug_capability_policy(security_policy.debug_registration_policy)?;
873
874 let child_policy = if let Some(child_policy) = &security_policy.child_policy {
875 let reboot_on_terminate = parse_allowlist_entries(&child_policy.reboot_on_terminate)?;
876 ChildPolicyAllowlists { reboot_on_terminate }
877 } else {
878 ChildPolicyAllowlists::default()
879 };
880
881 Ok(SecurityPolicy { job_policy, capability_policy, debug_capability_policy, child_policy })
882 }
883}
884
885#[cfg(test)]
886mod tests {
887 use super::*;
888 use assert_matches::assert_matches;
889 use fidl_fuchsia_io as fio;
890 use version_history::{ApiLevel, Version, VersionVec};
891
892 const FOO_PKG_URL: &str = "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm";
893
894 macro_rules! test_function_ok {
895 ( $function:path, $($test_name:ident => ($input:expr, $expected:expr)),+ ) => {
896 $(
897 #[test]
898 fn $test_name() {
899 assert_matches!($function($input), Ok(v) if v == $expected);
900 }
901 )+
902 };
903 }
904
905 macro_rules! test_function_err {
906 ( $function:path, $($test_name:ident => ($input:expr, $type:ty, $expected:expr)),+ ) => {
907 $(
908 #[test]
909 fn $test_name() {
910 assert_eq!(*$function($input).unwrap_err().downcast_ref::<$type>().unwrap(), $expected);
911 }
912 )+
913 };
914 }
915
916 macro_rules! test_config_ok {
917 ( $($test_name:ident => ($input:expr, $expected:expr)),+ $(,)? ) => {
918 test_function_ok! { RuntimeConfig::try_from, $($test_name => ($input, $expected)),+ }
919 };
920 }
921
922 macro_rules! test_config_err {
923 ( $($test_name:ident => ($input:expr, $type:ty, $expected:expr)),+ $(,)? ) => {
924 test_function_err! { RuntimeConfig::try_from, $($test_name => ($input, $type, $expected)),+ }
925 };
926 }
927
928 test_config_ok! {
929 all_fields_none => (component_internal::Config {
930 debug: None,
931 trace_provider: None,
932 enable_introspection: None,
933 list_children_batch_size: None,
934 security_policy: None,
935 maintain_utc_clock: None,
936 use_builtin_process_launcher: None,
937 num_threads: None,
938 namespace_capabilities: None,
939 builtin_capabilities: None,
940 root_component_url: None,
941 component_id_index_path: None,
942 ..Default::default()
943 }, RuntimeConfig::default()),
944 all_leaf_nodes_none => (component_internal::Config {
945 debug: Some(false),
946 trace_provider: Some(component_internal::TraceProvider::Namespace),
947 enable_introspection: Some(false),
948 list_children_batch_size: Some(5),
949 maintain_utc_clock: Some(false),
950 use_builtin_process_launcher: Some(true),
951 security_policy: Some(component_internal::SecurityPolicy {
952 job_policy: Some(component_internal::JobPolicyAllowlists {
953 main_process_critical: None,
954 ambient_mark_vmo_exec: None,
955 create_raw_processes: None,
956 ..Default::default()
957 }),
958 capability_policy: None,
959 ..Default::default()
960 }),
961 num_threads: Some(10),
962 namespace_capabilities: None,
963 builtin_capabilities: None,
964 root_component_url: None,
965 component_id_index_path: None,
966 log_destination: None,
967 log_all_events: None,
968 ..Default::default()
969 }, RuntimeConfig {
970 debug: false,
971 trace_provider: TraceProvider::Namespace,
972 enable_introspection: false,
973 list_children_batch_size: 5,
974 maintain_utc_clock: false,
975 use_builtin_process_launcher:true,
976 num_threads: 10,
977 ..Default::default() }),
978 all_fields_some => (
979 component_internal::Config {
980 debug: Some(true),
981 trace_provider: Some(component_internal::TraceProvider::RootExposed),
982 enable_introspection: Some(true),
983 list_children_batch_size: Some(42),
984 maintain_utc_clock: Some(true),
985 use_builtin_process_launcher: Some(false),
986 security_policy: Some(component_internal::SecurityPolicy {
987 job_policy: Some(component_internal::JobPolicyAllowlists {
988 main_process_critical: Some(vec!["/something/important".to_string()]),
989 ambient_mark_vmo_exec: Some(vec!["/".to_string(), "/foo/bar".to_string()]),
990 create_raw_processes: Some(vec!["/another/thing".to_string()]),
991 ..Default::default()
992 }),
993 capability_policy: Some(component_internal::CapabilityPolicyAllowlists {
994 allowlist: Some(vec![
995 component_internal::CapabilityAllowlistEntry {
996 source_moniker: Some("<component_manager>".to_string()),
997 source_name: Some("fuchsia.kernel.MmioResource".to_string()),
998 source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
999 capability: Some(component_internal::AllowlistedCapability::Protocol(component_internal::AllowlistedProtocol::default())),
1000 target_monikers: Some(vec![
1001 "/bootstrap".to_string(),
1002 "/core/**".to_string(),
1003 "/core/test_manager/tests:**".to_string()
1004 ]),
1005 ..Default::default()
1006 },
1007 ]), ..Default::default()}),
1008 debug_registration_policy: Some(component_internal::DebugRegistrationPolicyAllowlists{
1009 allowlist: Some(vec![
1010 component_internal::DebugRegistrationAllowlistEntry {
1011 name: Some("fuchsia.foo.bar".to_string()),
1012 debug: Some(component_internal::AllowlistedDebugRegistration::Protocol(component_internal::AllowlistedProtocol::default())),
1013 moniker: Some("/foo/bar".to_string()),
1014 environment_name: Some("bar_env1".to_string()),
1015 ..Default::default()
1016 },
1017 component_internal::DebugRegistrationAllowlistEntry {
1018 name: Some("fuchsia.foo.bar".to_string()),
1019 debug: Some(component_internal::AllowlistedDebugRegistration::Protocol(component_internal::AllowlistedProtocol::default())),
1020 moniker: Some("/foo".to_string()),
1021 environment_name: Some("foo_env1".to_string()),
1022 ..Default::default()
1023 },
1024 component_internal::DebugRegistrationAllowlistEntry {
1025 name: Some("fuchsia.foo.baz".to_string()),
1026 debug: Some(component_internal::AllowlistedDebugRegistration::Protocol(component_internal::AllowlistedProtocol::default())),
1027 moniker: Some("/foo/**".to_string()),
1028 environment_name: Some("foo_env2".to_string()),
1029 ..Default::default()
1030 },
1031 component_internal::DebugRegistrationAllowlistEntry {
1032 name: Some("fuchsia.foo.baz".to_string()),
1033 debug: Some(component_internal::AllowlistedDebugRegistration::Protocol(component_internal::AllowlistedProtocol::default())),
1034 moniker: Some("/root".to_string()),
1035 environment_name: Some("root_env".to_string()),
1036 ..Default::default()
1037 },
1038 ]), ..Default::default()}),
1039 child_policy: Some(component_internal::ChildPolicyAllowlists {
1040 reboot_on_terminate: Some(vec!["/something/important".to_string()]),
1041 ..Default::default()
1042 }),
1043 ..Default::default()
1044 }),
1045 num_threads: Some(24),
1046 namespace_capabilities: Some(vec![
1047 fdecl::Capability::Protocol(fdecl::Protocol {
1048 name: Some("foo_svc".into()),
1049 source_path: Some("/svc/foo".into()),
1050 ..Default::default()
1051 }),
1052 fdecl::Capability::Directory(fdecl::Directory {
1053 name: Some("bar_dir".into()),
1054 source_path: Some("/bar".into()),
1055 rights: Some(fio::Operations::CONNECT),
1056 ..Default::default()
1057 }),
1058 ]),
1059 builtin_capabilities: Some(vec![
1060 fdecl::Capability::Protocol(fdecl::Protocol {
1061 name: Some("foo_protocol".into()),
1062 source_path: None,
1063 ..Default::default()
1064 }),
1065 ]),
1066 root_component_url: Some(FOO_PKG_URL.to_string()),
1067 component_id_index_path: Some("/boot/config/component_id_index".to_string()),
1068 log_destination: Some(component_internal::LogDestination::Klog),
1069 log_all_events: Some(true),
1070 builtin_boot_resolver: Some(component_internal::BuiltinBootResolver::None),
1071 realm_builder_resolver_and_runner: Some(component_internal::RealmBuilderResolverAndRunner::None),
1072 abi_revision_policy: Some(component_internal::AbiRevisionPolicy{
1073 allowlist: Some(vec!["/baz".to_string(), "/qux/**".to_string()]),
1074 ..Default::default()
1075 }),
1076 vmex_source: Some(component_internal::VmexSource::Namespace),
1077 health_check: Some(component_internal::HealthCheck{ monikers: Some(vec!()), ..Default::default()}),
1078 ..Default::default()
1079 },
1080 RuntimeConfig {
1081 abi_revision_policy: AbiRevisionPolicy::new(vec![
1082 AllowlistEntryBuilder::new().exact("baz").build(),
1083 AllowlistEntryBuilder::new().exact("qux").any_descendant(),
1084 ]),
1085 debug: true,
1086 trace_provider: TraceProvider::RootExposed,
1087 enable_introspection: true,
1088 list_children_batch_size: 42,
1089 maintain_utc_clock: true,
1090 use_builtin_process_launcher: false,
1091 security_policy: Arc::new(SecurityPolicy {
1092 job_policy: JobPolicyAllowlists {
1093 ambient_mark_vmo_exec: vec![
1094 AllowlistEntryBuilder::new().build(),
1095 AllowlistEntryBuilder::new().exact("foo").exact("bar").build(),
1096 ],
1097 main_process_critical: vec![
1098 AllowlistEntryBuilder::new().exact("something").exact("important").build(),
1099 ],
1100 create_raw_processes: vec![
1101 AllowlistEntryBuilder::new().exact("another").exact("thing").build(),
1102 ],
1103 },
1104 capability_policy: HashMap::from_iter(vec![
1105 (CapabilityAllowlistKey {
1106 source_moniker: ExtendedMoniker::ComponentManager,
1107 source_name: "fuchsia.kernel.MmioResource".parse().unwrap(),
1108 source: CapabilityAllowlistSource::Self_,
1109 capability: CapabilityTypeName::Protocol,
1110 },
1111 HashSet::from_iter(vec![
1112 AllowlistEntryBuilder::new().exact("bootstrap").build(),
1113 AllowlistEntryBuilder::new().exact("core").any_descendant(),
1114 AllowlistEntryBuilder::new().exact("core").exact("test_manager").any_descendant_in_collection("tests"),
1115 ].iter().cloned())
1116 ),
1117 ].iter().cloned()),
1118 debug_capability_policy: HashMap::from_iter(vec![
1119 (
1120 DebugCapabilityKey {
1121 name: "fuchsia.foo.bar".parse().unwrap(),
1122 source: CapabilityAllowlistSource::Self_,
1123 capability: CapabilityTypeName::Protocol,
1124 env_name: "bar_env1".parse().unwrap(),
1125 },
1126 HashSet::from_iter(vec![
1127 DebugCapabilityAllowlistEntry::new(
1128 AllowlistEntryBuilder::new().exact("foo").exact("bar").build(),
1129 )
1130 ])
1131 ),
1132 (
1133 DebugCapabilityKey {
1134 name: "fuchsia.foo.bar".parse().unwrap(),
1135 source: CapabilityAllowlistSource::Self_,
1136 capability: CapabilityTypeName::Protocol,
1137 env_name: "foo_env1".parse().unwrap(),
1138 },
1139 HashSet::from_iter(vec![
1140 DebugCapabilityAllowlistEntry::new(
1141 AllowlistEntryBuilder::new().exact("foo").build(),
1142 )
1143 ])
1144 ),
1145 (
1146 DebugCapabilityKey {
1147 name: "fuchsia.foo.baz".parse().unwrap(),
1148 source: CapabilityAllowlistSource::Self_,
1149 capability: CapabilityTypeName::Protocol,
1150 env_name: "foo_env2".parse().unwrap(),
1151 },
1152 HashSet::from_iter(vec![
1153 DebugCapabilityAllowlistEntry::new(
1154 AllowlistEntryBuilder::new().exact("foo").any_descendant(),
1155 )
1156 ])
1157 ),
1158 (
1159 DebugCapabilityKey {
1160 name: "fuchsia.foo.baz".parse().unwrap(),
1161 source: CapabilityAllowlistSource::Self_,
1162 capability: CapabilityTypeName::Protocol,
1163 env_name: "root_env".parse().unwrap(),
1164 },
1165 HashSet::from_iter(vec![
1166 DebugCapabilityAllowlistEntry::new(
1167 AllowlistEntryBuilder::new().exact("root").build(),
1168 )
1169 ])
1170 ),
1171 ]),
1172 child_policy: ChildPolicyAllowlists {
1173 reboot_on_terminate: vec![
1174 AllowlistEntryBuilder::new().exact("something").exact("important").build(),
1175 ],
1176 },
1177 }),
1178 num_threads: 24,
1179 namespace_capabilities: vec![
1180 cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl {
1181 name: "foo_svc".parse().unwrap(),
1182 source_path: Some("/svc/foo".parse().unwrap()),
1183 delivery: Default::default(),
1184 }),
1185 cm_rust::CapabilityDecl::Directory(cm_rust::DirectoryDecl {
1186 name: "bar_dir".parse().unwrap(),
1187 source_path: Some("/bar".parse().unwrap()),
1188 rights: fio::Operations::CONNECT,
1189 }),
1190 ],
1191 builtin_capabilities: vec![
1192 cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl {
1193 name: "foo_protocol".parse().unwrap(),
1194 source_path: None,
1195 delivery: Default::default(),
1196 }),
1197 ],
1198 root_component_url: Some(Url::new(FOO_PKG_URL.to_string()).unwrap()),
1199 component_id_index_path: Some("/boot/config/component_id_index".into()),
1200 log_destination: LogDestination::Klog,
1201 log_all_events: true,
1202 builtin_boot_resolver: BuiltinBootResolver::None,
1203 realm_builder_resolver_and_runner: RealmBuilderResolverAndRunner::None,
1204 vmex_source: VmexSource::Namespace,
1205 health_check: HealthCheck{monikers: vec!()},
1206 }
1207 ),
1208 }
1209
1210 test_config_err! {
1211 invalid_job_policy => (
1212 component_internal::Config {
1213 debug: None,
1214 trace_provider: None,
1215 enable_introspection: None,
1216 list_children_batch_size: None,
1217 maintain_utc_clock: None,
1218 use_builtin_process_launcher: None,
1219 security_policy: Some(component_internal::SecurityPolicy {
1220 job_policy: Some(component_internal::JobPolicyAllowlists {
1221 main_process_critical: None,
1222 ambient_mark_vmo_exec: Some(vec!["/".to_string(), "bad".to_string()]),
1223 create_raw_processes: None,
1224 ..Default::default()
1225 }),
1226 capability_policy: None,
1227 ..Default::default()
1228 }),
1229 num_threads: None,
1230 namespace_capabilities: None,
1231 builtin_capabilities: None,
1232 root_component_url: None,
1233 component_id_index_path: None,
1234 ..Default::default()
1235 },
1236 AllowlistEntryParseError,
1237 AllowlistEntryParseError::NoLeadingSlash(
1238 "bad".into(),
1239 )
1240 ),
1241 invalid_capability_policy_empty_allowlist_cap => (
1242 component_internal::Config {
1243 debug: None,
1244 trace_provider: None,
1245 enable_introspection: None,
1246 list_children_batch_size: None,
1247 maintain_utc_clock: None,
1248 use_builtin_process_launcher: None,
1249 security_policy: Some(component_internal::SecurityPolicy {
1250 job_policy: None,
1251 capability_policy: Some(component_internal::CapabilityPolicyAllowlists {
1252 allowlist: Some(vec![
1253 component_internal::CapabilityAllowlistEntry {
1254 source_moniker: Some("<component_manager>".to_string()),
1255 source_name: Some("fuchsia.kernel.MmioResource".to_string()),
1256 source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
1257 capability: None,
1258 target_monikers: Some(vec!["/core".to_string()]),
1259 ..Default::default()
1260 }]),
1261 ..Default::default()
1262 }),
1263 ..Default::default()
1264 }),
1265 num_threads: None,
1266 namespace_capabilities: None,
1267 builtin_capabilities: None,
1268 root_component_url: None,
1269 component_id_index_path: None,
1270 ..Default::default()
1271 },
1272 PolicyConfigError,
1273 PolicyConfigError::EmptyAllowlistedCapability
1274 ),
1275 invalid_capability_policy_empty_source_moniker => (
1276 component_internal::Config {
1277 debug: None,
1278 trace_provider: None,
1279 enable_introspection: None,
1280 list_children_batch_size: None,
1281 maintain_utc_clock: None,
1282 use_builtin_process_launcher: None,
1283 security_policy: Some(component_internal::SecurityPolicy {
1284 job_policy: None,
1285 capability_policy: Some(component_internal::CapabilityPolicyAllowlists {
1286 allowlist: Some(vec![
1287 component_internal::CapabilityAllowlistEntry {
1288 source_moniker: None,
1289 source_name: Some("fuchsia.kernel.MmioResource".to_string()),
1290 capability: Some(component_internal::AllowlistedCapability::Protocol(component_internal::AllowlistedProtocol::default())),
1291 target_monikers: Some(vec!["/core".to_string()]),
1292 ..Default::default()
1293 }]),
1294 ..Default::default()
1295 }),
1296 ..Default::default()
1297 }),
1298 num_threads: None,
1299 namespace_capabilities: None,
1300 builtin_capabilities: None,
1301 root_component_url: None,
1302 component_id_index_path: None,
1303 ..Default::default()
1304 },
1305 PolicyConfigError,
1306 PolicyConfigError::EmptySourceMoniker
1307 ),
1308 invalid_root_component_url => (
1309 component_internal::Config {
1310 debug: None,
1311 trace_provider: None,
1312 enable_introspection: None,
1313 list_children_batch_size: None,
1314 maintain_utc_clock: None,
1315 use_builtin_process_launcher: None,
1316 security_policy: None,
1317 num_threads: None,
1318 namespace_capabilities: None,
1319 builtin_capabilities: None,
1320 root_component_url: Some("invalid url".to_string()),
1321 component_id_index_path: None,
1322 ..Default::default()
1323 },
1324 ParseError,
1325 ParseError::InvalidComponentUrl {
1326 details: String::from("Relative URL has no resource fragment.")
1327 }
1328 ),
1329 }
1330
1331 #[test]
1332 fn new_from_bytes_valid() -> Result<(), Error> {
1333 let config = component_internal::Config {
1334 debug: None,
1335 trace_provider: None,
1336 enable_introspection: None,
1337 list_children_batch_size: Some(42),
1338 security_policy: None,
1339 namespace_capabilities: None,
1340 builtin_capabilities: None,
1341 maintain_utc_clock: None,
1342 use_builtin_process_launcher: None,
1343 num_threads: None,
1344 root_component_url: Some(FOO_PKG_URL.to_string()),
1345 ..Default::default()
1346 };
1347 let bytes = fidl::persist(&config)?;
1348 let expected = RuntimeConfig {
1349 list_children_batch_size: 42,
1350 root_component_url: Some(Url::new(FOO_PKG_URL.to_string())?),
1351 ..Default::default()
1352 };
1353
1354 assert_matches!(
1355 RuntimeConfig::new_from_bytes(&bytes)
1356 , Ok(v) if v == expected);
1357 Ok(())
1358 }
1359
1360 #[test]
1361 fn new_from_bytes_invalid() -> Result<(), Error> {
1362 let bytes = vec![0xfa, 0xde];
1363 assert_matches!(RuntimeConfig::new_from_bytes(&bytes), Err(_));
1364 Ok(())
1365 }
1366
1367 #[test]
1368 fn abi_revision_policy_check_compatibility_empty_allowlist() -> Result<(), Error> {
1369 const UNKNOWN_ABI: AbiRevision = AbiRevision::from_u64(0x404);
1370 const RETIRED_ABI: AbiRevision = AbiRevision::from_u64(0x15);
1371 const SUPPORTED_ABI: AbiRevision = AbiRevision::from_u64(0x16);
1372
1373 const VERSIONS: &[Version] = &[
1374 Version {
1375 api_level: ApiLevel::from_u32(5),
1376 abi_revision: RETIRED_ABI,
1377 status: version_history::Status::Unsupported,
1378 },
1379 Version {
1380 api_level: ApiLevel::from_u32(6),
1381 abi_revision: SUPPORTED_ABI,
1382 status: version_history::Status::Supported,
1383 },
1384 ];
1385 let version_history = VersionHistory::new(&VERSIONS);
1386
1387 let policy = AbiRevisionPolicy::new(vec![]);
1388
1389 assert_eq!(
1390 policy.check_compatibility(&version_history, &Moniker::parse_str("/foo")?, None),
1391 Err(CompatibilityCheckError::AbiRevisionAbsent)
1392 );
1393 assert_eq!(
1394 policy.check_compatibility(
1395 &version_history,
1396 &Moniker::parse_str("/foo")?,
1397 Some(UNKNOWN_ABI)
1398 ),
1399 Err(CompatibilityCheckError::AbiRevisionInvalid(AbiRevisionError::TooNew {
1400 abi_revision: UNKNOWN_ABI,
1401 supported_versions: VersionVec(vec![VERSIONS[1].clone()])
1402 }))
1403 );
1404 assert_eq!(
1405 policy.check_compatibility(
1406 &version_history,
1407 &Moniker::parse_str("/foo")?,
1408 Some(RETIRED_ABI)
1409 ),
1410 Err(CompatibilityCheckError::AbiRevisionInvalid(AbiRevisionError::Retired {
1411 version: VERSIONS[0].clone(),
1412 supported_versions: VersionVec(vec![VERSIONS[1].clone()]),
1413 })),
1414 );
1415 assert_eq!(
1416 policy.check_compatibility(
1417 &version_history,
1418 &Moniker::parse_str("/foo")?,
1419 Some(SUPPORTED_ABI)
1420 ),
1421 Ok(())
1422 );
1423
1424 Ok(())
1425 }
1426
1427 #[test]
1428 fn abi_revision_policy_check_compatibility_allowlist() -> Result<(), Error> {
1429 const UNKNOWN_ABI: AbiRevision = AbiRevision::from_u64(0x404);
1430 const RETIRED_ABI: AbiRevision = AbiRevision::from_u64(0x15);
1431 const SUPPORTED_ABI: AbiRevision = AbiRevision::from_u64(0x16);
1432
1433 const VERSIONS: &[Version] = &[
1434 Version {
1435 api_level: ApiLevel::from_u32(5),
1436 abi_revision: RETIRED_ABI,
1437 status: version_history::Status::Unsupported,
1438 },
1439 Version {
1440 api_level: ApiLevel::from_u32(6),
1441 abi_revision: SUPPORTED_ABI,
1442 status: version_history::Status::Supported,
1443 },
1444 ];
1445 let version_history = VersionHistory::new(&VERSIONS);
1446
1447 let policy = AbiRevisionPolicy::new(vec![AllowlistEntryBuilder::new()
1448 .exact("foo")
1449 .any_child()
1450 .build()]);
1451
1452 assert_eq!(
1454 policy.check_compatibility(&version_history, &Moniker::parse_str("/bar")?, None),
1455 Err(CompatibilityCheckError::AbiRevisionAbsent)
1456 );
1457 assert_eq!(
1458 policy.check_compatibility(
1459 &version_history,
1460 &Moniker::parse_str("/bar")?,
1461 Some(UNKNOWN_ABI)
1462 ),
1463 Err(CompatibilityCheckError::AbiRevisionInvalid(AbiRevisionError::TooNew {
1464 abi_revision: UNKNOWN_ABI,
1465 supported_versions: VersionVec(vec![VERSIONS[1].clone()])
1466 }))
1467 );
1468 assert_eq!(
1469 policy.check_compatibility(
1470 &version_history,
1471 &Moniker::parse_str("/bar")?,
1472 Some(RETIRED_ABI)
1473 ),
1474 Err(CompatibilityCheckError::AbiRevisionInvalid(AbiRevisionError::Retired {
1475 version: VERSIONS[0].clone(),
1476 supported_versions: VersionVec(vec![VERSIONS[1].clone()]),
1477 })),
1478 );
1479 assert_eq!(
1480 policy.check_compatibility(
1481 &version_history,
1482 &Moniker::parse_str("/bar")?,
1483 Some(SUPPORTED_ABI)
1484 ),
1485 Ok(())
1486 );
1487
1488 assert_eq!(
1490 policy.check_compatibility(&version_history, &Moniker::parse_str("/foo/baz")?, None),
1491 Ok(())
1492 );
1493 assert_eq!(
1494 policy.check_compatibility(
1495 &version_history,
1496 &Moniker::parse_str("/foo/baz")?,
1497 Some(UNKNOWN_ABI)
1498 ),
1499 Ok(())
1500 );
1501 assert_eq!(
1502 policy.check_compatibility(
1503 &version_history,
1504 &Moniker::parse_str("/foo/baz")?,
1505 Some(RETIRED_ABI)
1506 ),
1507 Ok(())
1508 );
1509 assert_eq!(
1510 policy.check_compatibility(
1511 &version_history,
1512 &Moniker::parse_str("/foo/baz")?,
1513 Some(SUPPORTED_ABI)
1514 ),
1515 Ok(())
1516 );
1517
1518 Ok(())
1519 }
1520
1521 macro_rules! test_entries_ok {
1522 ( $($test_name:ident => ($input:expr, $expected:expr)),+ $(,)? ) => {
1523 test_function_ok! { parse_allowlist_entries, $($test_name => ($input, $expected)),+ }
1524 };
1525 }
1526
1527 macro_rules! test_entries_err {
1528 ( $($test_name:ident => ($input:expr, $type:ty, $expected:expr)),+ $(,)? ) => {
1529 test_function_err! { parse_allowlist_entries, $($test_name => ($input, $type, $expected)),+ }
1530 };
1531 }
1532
1533 test_entries_ok! {
1534 missing_entries => (&None, vec![]),
1535 empty_entries => (&Some(vec![]), vec![]),
1536 all_entry_types => (&Some(vec![
1537 "/core".into(),
1538 "/**".into(),
1539 "/foo/**".into(),
1540 "/coll:**".into(),
1541 "/core/test_manager/tests:**".into(),
1542 "/core/ffx-laboratory:*/echo_client".into(),
1543 "/core/*/ffx-laboratory:*/**".into(),
1544 "/core/*/bar".into(),
1545 ]), vec![
1546 AllowlistEntryBuilder::new().exact("core").build(),
1547 AllowlistEntryBuilder::new().any_descendant(),
1548 AllowlistEntryBuilder::new().exact("foo").any_descendant(),
1549 AllowlistEntryBuilder::new().any_descendant_in_collection("coll"),
1550 AllowlistEntryBuilder::new().exact("core").exact("test_manager").any_descendant_in_collection("tests"),
1551 AllowlistEntryBuilder::new().exact("core").any_child_in_collection("ffx-laboratory").exact("echo_client").build(),
1552 AllowlistEntryBuilder::new().exact("core").any_child().any_child_in_collection("ffx-laboratory").any_descendant(),
1553 AllowlistEntryBuilder::new().exact("core").any_child().exact("bar").build(),
1554 ])
1555 }
1556
1557 test_entries_err! {
1558 invalid_realm_entry => (
1559 &Some(vec!["/foo/**".into(), "bar/**".into()]),
1560 AllowlistEntryParseError,
1561 AllowlistEntryParseError::NoLeadingSlash("bar/**".into())),
1562 invalid_realm_in_collection_entry => (
1563 &Some(vec!["/foo/coll:**".into(), "bar/coll:**".into()]),
1564 AllowlistEntryParseError,
1565 AllowlistEntryParseError::NoLeadingSlash("bar/coll:**".into())),
1566 missing_realm_in_collection_entry => (
1567 &Some(vec!["coll:**".into()]),
1568 AllowlistEntryParseError,
1569 AllowlistEntryParseError::NoLeadingSlash("coll:**".into())),
1570 missing_collection_name => (
1571 &Some(vec!["/foo/coll:**".into(), "/:**".into()]),
1572 AllowlistEntryParseError,
1573 AllowlistEntryParseError::InvalidCollectionName(
1574 "".into(),
1575 ParseError::Empty
1576 )),
1577 invalid_collection_name => (
1578 &Some(vec!["/foo/coll:**".into(), "/*:**".into()]),
1579 AllowlistEntryParseError,
1580 AllowlistEntryParseError::InvalidCollectionName(
1581 "*".into(),
1582 ParseError::InvalidValue
1583 )),
1584 invalid_exact_entry => (
1585 &Some(vec!["/foo/bar*".into()]),
1586 AllowlistEntryParseError,
1587 AllowlistEntryParseError::InvalidChildName(
1588 "bar*".into(),
1589 MonikerError::InvalidMonikerPart { 0: ParseError::InvalidValue }
1590 )),
1591 descendant_wildcard_in_between => (
1592 &Some(vec!["/foo/**/bar".into()]),
1593 AllowlistEntryParseError,
1594 AllowlistEntryParseError::DescendantWildcardOnlyAtEnd(
1595 "/foo/**/bar".into(),
1596 )),
1597 }
1598
1599 #[test]
1600 fn allowlist_entry_matches() {
1601 let root = Moniker::root();
1602 let allowed = Moniker::try_from(vec!["foo", "bar"]).unwrap();
1603 let disallowed_child_of_allowed = Moniker::try_from(vec!["foo", "bar", "baz"]).unwrap();
1604 let disallowed = Moniker::try_from(vec!["baz", "fiz"]).unwrap();
1605 let allowlist_exact = AllowlistEntryBuilder::new().exact_from_moniker(&allowed).build();
1606 assert!(allowlist_exact.matches(&allowed));
1607 assert!(!allowlist_exact.matches(&root));
1608 assert!(!allowlist_exact.matches(&disallowed));
1609 assert!(!allowlist_exact.matches(&disallowed_child_of_allowed));
1610
1611 let allowed_realm_root = Moniker::try_from(vec!["qux"]).unwrap();
1612 let allowed_child_of_realm = Moniker::try_from(vec!["qux", "quux"]).unwrap();
1613 let allowed_nested_child_of_realm = Moniker::try_from(vec!["qux", "quux", "foo"]).unwrap();
1614 let allowlist_realm =
1615 AllowlistEntryBuilder::new().exact_from_moniker(&allowed_realm_root).any_descendant();
1616 assert!(!allowlist_realm.matches(&allowed_realm_root));
1617 assert!(allowlist_realm.matches(&allowed_child_of_realm));
1618 assert!(allowlist_realm.matches(&allowed_nested_child_of_realm));
1619 assert!(!allowlist_realm.matches(&disallowed));
1620 assert!(!allowlist_realm.matches(&root));
1621
1622 let collection_holder = Moniker::try_from(vec!["corge"]).unwrap();
1623 let collection_child = Moniker::try_from(vec!["corge", "collection:child"]).unwrap();
1624 let collection_nested_child =
1625 Moniker::try_from(vec!["corge", "collection:child", "inner-child"]).unwrap();
1626 let non_collection_child = Moniker::try_from(vec!["corge", "grault"]).unwrap();
1627 let allowlist_collection = AllowlistEntryBuilder::new()
1628 .exact_from_moniker(&collection_holder)
1629 .any_descendant_in_collection("collection");
1630 assert!(!allowlist_collection.matches(&collection_holder));
1631 assert!(allowlist_collection.matches(&collection_child));
1632 assert!(allowlist_collection.matches(&collection_nested_child));
1633 assert!(!allowlist_collection.matches(&non_collection_child));
1634 assert!(!allowlist_collection.matches(&disallowed));
1635 assert!(!allowlist_collection.matches(&root));
1636
1637 let collection_a = Moniker::try_from(vec!["foo", "bar:a", "baz", "qux"]).unwrap();
1638 let collection_b = Moniker::try_from(vec!["foo", "bar:b", "baz", "qux"]).unwrap();
1639 let parent_not_allowed = Moniker::try_from(vec!["foo", "bar:b", "baz"]).unwrap();
1640 let collection_not_allowed = Moniker::try_from(vec!["foo", "bar:b", "baz"]).unwrap();
1641 let different_collection_not_allowed =
1642 Moniker::try_from(vec!["foo", "test:b", "baz", "qux"]).unwrap();
1643 let allowlist_exact_in_collection = AllowlistEntryBuilder::new()
1644 .exact("foo")
1645 .any_child_in_collection("bar")
1646 .exact("baz")
1647 .exact("qux")
1648 .build();
1649 assert!(allowlist_exact_in_collection.matches(&collection_a));
1650 assert!(allowlist_exact_in_collection.matches(&collection_b));
1651 assert!(!allowlist_exact_in_collection.matches(&parent_not_allowed));
1652 assert!(!allowlist_exact_in_collection.matches(&collection_not_allowed));
1653 assert!(!allowlist_exact_in_collection.matches(&different_collection_not_allowed));
1654
1655 let any_child_allowlist = AllowlistEntryBuilder::new().exact("core").any_child().build();
1656 let allowed = Moniker::try_from(vec!["core", "abc"]).unwrap();
1657 let disallowed_1 = Moniker::try_from(vec!["not_core", "abc"]).unwrap();
1658 let disallowed_2 = Moniker::try_from(vec!["core", "abc", "def"]).unwrap();
1659 assert!(any_child_allowlist.matches(&allowed));
1660 assert!(!any_child_allowlist.matches(&disallowed_1));
1661 assert!(!any_child_allowlist.matches(&disallowed_2));
1662
1663 let multiwildcard_allowlist = AllowlistEntryBuilder::new()
1664 .exact("core")
1665 .any_child()
1666 .any_child_in_collection("foo")
1667 .any_descendant();
1668 let allowed = Moniker::try_from(vec!["core", "abc", "foo:def", "ghi"]).unwrap();
1669 let disallowed_1 = Moniker::try_from(vec!["not_core", "abc", "foo:def", "ghi"]).unwrap();
1670 let disallowed_2 = Moniker::try_from(vec!["core", "abc", "not_foo:def", "ghi"]).unwrap();
1671 let disallowed_3 = Moniker::try_from(vec!["core", "abc", "foo:def"]).unwrap();
1672 assert!(multiwildcard_allowlist.matches(&allowed));
1673 assert!(!multiwildcard_allowlist.matches(&disallowed_1));
1674 assert!(!multiwildcard_allowlist.matches(&disallowed_2));
1675 assert!(!multiwildcard_allowlist.matches(&disallowed_3));
1676 }
1677}