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