cm_config/
lib.rs

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