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