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