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