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