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