| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::startup, |
| anyhow::{format_err, Context, Error}, |
| cm_rust::{CapabilityName, CapabilityTypeName, FidlIntoNative}, |
| cm_types::Url, |
| fidl_fuchsia_component_internal::{ |
| self as component_internal, BuiltinPkgResolver, OutDirContents, |
| }, |
| fidl_fuchsia_sys2 as fsys, |
| moniker::{AbsoluteMoniker, ExtendedMoniker, MonikerError}, |
| std::{ |
| collections::{HashMap, HashSet}, |
| convert::TryFrom, |
| iter::FromIterator, |
| path::PathBuf, |
| }, |
| thiserror::Error, |
| }; |
| |
| /// Runtime configuration options. |
| /// This configuration intended to be "global", in that the same configuration |
| /// is applied throughout a given running instance of component_manager. |
| #[derive(Debug, PartialEq, Eq)] |
| pub struct RuntimeConfig { |
| /// How many children, maximum, are returned by a call to `ChildIterator.next()`. |
| pub list_children_batch_size: usize, |
| |
| /// Security policy configuration. |
| pub security_policy: SecurityPolicy, |
| |
| /// If true, component manager will be in debug mode. In this mode, component manager |
| /// provides the `BlockingEventSource` protocol and exposes this protocol. Component |
| /// manager will not start until it is resumed by a call to |
| /// `BlockingEventSource.StartComponentTree`. |
| /// |
| /// This is done so that an external component (say an integration test) can subscribe |
| /// to events before the root component has started. |
| pub debug: bool, |
| |
| /// If true, component_manager will serve an instance of fuchsia.process.Launcher and use this |
| /// launcher for the built-in ELF component runner. The root component can additionally |
| /// use and/or offer this service using '/builtin/fuchsia.process.Launcher' from realm. |
| // This flag exists because the built-in process launcher *only* works when |
| // component_manager runs under a job that has ZX_POL_NEW_PROCESS set to allow, like the root |
| // job. Otherwise, the component_manager process cannot directly create process through |
| // zx_process_create. When we run component_manager elsewhere, like in test environments, it |
| // has to use the fuchsia.process.Launcher service provided through its namespace instead. |
| pub use_builtin_process_launcher: bool, |
| |
| /// If true, component_manager will maintain a UTC kernel clock and vend write handles through |
| /// an instance of `fuchsia.time.Maintenance`. This flag should only be used with the top-level |
| /// component_manager. |
| pub maintain_utc_clock: bool, |
| |
| // The number of threads to use for running component_manager's executor. |
| // Value defaults to 1. |
| pub num_threads: usize, |
| |
| /// The list of capabilities offered from component manager's namespace. |
| pub namespace_capabilities: Vec<cm_rust::CapabilityDecl>, |
| |
| /// Which builtin resolver to use. If not supplied this defaults to the NONE option. |
| pub builtin_pkg_resolver: BuiltinPkgResolver, |
| |
| /// Determine what content to expose through the component manager's |
| /// outgoing directory. |
| pub out_dir_contents: OutDirContents, |
| |
| /// URL of the root component to launch. This field is used if no URL |
| /// is passed to component manager. If value is passed in both places, then |
| /// an error is raised. |
| pub root_component_url: Option<Url>, |
| |
| /// Path to the component ID index, parsed from |
| /// `fuchsia.component.internal.RuntimeConfig.component_id_index_path`. |
| pub component_id_index_path: Option<String>, |
| } |
| |
| /// Runtime security policy. |
| #[derive(Debug, Default, PartialEq, Eq)] |
| pub struct SecurityPolicy { |
| /// Allowlists for Zircon job policy. |
| pub job_policy: JobPolicyAllowlists, |
| /// Capability routing policies. The key contains all the information required |
| /// to uniquely identify any routable capability and the set of monikers |
| /// define the set of component paths that are allowed to access this specific |
| /// capability. |
| pub capability_policy: HashMap<CapabilityAllowlistKey, HashSet<AbsoluteMoniker>>, |
| } |
| |
| /// Allowlists for Zircon job policy. Part of runtime security policy. |
| #[derive(Debug, Default, PartialEq, Eq)] |
| pub struct JobPolicyAllowlists { |
| /// Absolute monikers for components allowed to be given the ZX_POL_AMBIENT_MARK_VMO_EXEC job |
| /// policy. |
| /// |
| /// Components must request this policy by including "job_policy_ambient_mark_vmo_exec: true" in |
| /// their manifest's program object and must be using the ELF runner. |
| /// This is equivalent to the v1 'deprecated-ambient-replace-as-executable' feature. |
| pub ambient_mark_vmo_exec: Vec<AbsoluteMoniker>, |
| |
| /// Absolute monikers for components allowed to have their original process marked as critical |
| /// to component_manager's job. |
| /// |
| /// Components must request this critical marking by including "main_process_critical: true" in |
| /// their manifest's program object and must be using the ELF runner. |
| pub main_process_critical: Vec<AbsoluteMoniker>, |
| } |
| |
| /// The available capability sources for capability allow lists. This is a strict |
| /// subset of all possible Ref types, with equality support. |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub enum CapabilityAllowlistSource { |
| Self_, |
| Framework, |
| Capability, |
| } |
| |
| /// Allowlist key for capability routing policy. Part of the runtime |
| /// security policy. This defines all the required keying information to lookup |
| /// whether a capability exists in the policy map or not. |
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] |
| pub struct CapabilityAllowlistKey { |
| pub source_moniker: ExtendedMoniker, |
| pub source_name: CapabilityName, |
| pub source: CapabilityAllowlistSource, |
| pub capability: CapabilityTypeName, |
| } |
| |
| impl Default for RuntimeConfig { |
| fn default() -> Self { |
| Self { |
| list_children_batch_size: 1000, |
| // security_policy must default to empty to ensure that it fails closed if no |
| // configuration is present or it fails to load. |
| security_policy: Default::default(), |
| debug: false, |
| use_builtin_process_launcher: false, |
| maintain_utc_clock: false, |
| num_threads: 1, |
| namespace_capabilities: vec![], |
| builtin_pkg_resolver: BuiltinPkgResolver::None, |
| out_dir_contents: OutDirContents::None, |
| root_component_url: Default::default(), |
| component_id_index_path: None, |
| } |
| } |
| } |
| |
| impl RuntimeConfig { |
| /// Load RuntimeConfig from the '--config' command line arg. Returns both the RuntimeConfig |
| /// and the path that the config was loaded from. Returns Err() if an an error occurs |
| /// loading it. |
| pub async fn load_from_file(args: &startup::Arguments) -> Result<(Self, PathBuf), Error> { |
| let config = |
| io_util::file::read_in_namespace_to_fidl::<component_internal::Config>(&args.config) |
| .await |
| .context(format!("Failed to read config file {}", &args.config))?; |
| |
| Self::try_from(config) |
| .map(|s| (s, PathBuf::from(&args.config))) |
| .context(format!("Failed to apply config file {}", &args.config)) |
| } |
| |
| fn translate_namespace_capabilities( |
| capabilities: Option<Vec<fsys::CapabilityDecl>>, |
| ) -> Result<Vec<cm_rust::CapabilityDecl>, Error> { |
| let capabilities = capabilities.unwrap_or(vec![]); |
| if let Some(c) = capabilities.iter().find(|c| { |
| !matches!(c, fsys::CapabilityDecl::Protocol(_) | fsys::CapabilityDecl::Directory(_)) |
| }) { |
| return Err(format_err!("Type unsupported for namespace capability: {:?}", c)); |
| } |
| cm_fidl_validator::validate_capabilities(&capabilities)?; |
| Ok(Some(capabilities).fidl_into_native()) |
| } |
| } |
| |
| fn parse_absolute_monikers_from_strings( |
| strs: &Option<Vec<String>>, |
| ) -> Result<Vec<AbsoluteMoniker>, Error> { |
| let result: Result<Vec<AbsoluteMoniker>, MonikerError> = if let Some(strs) = strs { |
| strs.iter().map(|s| AbsoluteMoniker::parse_string_without_instances(s)).collect() |
| } else { |
| Ok(Vec::new()) |
| }; |
| result.context(format!("Moniker parsing error for {:?}", strs)) |
| } |
| |
| fn as_usize_or_default(value: Option<u32>, default: usize) -> usize { |
| match value { |
| Some(value) => value as usize, |
| None => default, |
| } |
| } |
| |
| #[derive(Debug, Clone, Error, PartialEq, Eq)] |
| pub enum PolicyConfigError { |
| #[error("Capability source name was empty in a capability policy entry.")] |
| EmptyCapabilitySourceName, |
| #[error("Capability type was empty in a capability policy entry.")] |
| EmptyAllowlistedCapability, |
| #[error("Capability from type was empty in a capability policy entry.")] |
| EmptyFromType, |
| #[error("Capability source_moniker was empty in a capability policy entry.")] |
| EmptySourceMoniker, |
| #[error("Invalid source capability.")] |
| InvalidSourceCapability, |
| #[error("Unsupported allowlist capability type")] |
| UnsupportedAllowlistedCapability, |
| } |
| |
| impl TryFrom<component_internal::Config> for RuntimeConfig { |
| type Error = Error; |
| |
| fn try_from(config: component_internal::Config) -> Result<Self, Error> { |
| let default = RuntimeConfig::default(); |
| |
| let list_children_batch_size = |
| as_usize_or_default(config.list_children_batch_size, default.list_children_batch_size); |
| let num_threads = as_usize_or_default(config.num_threads, default.num_threads); |
| |
| let root_component_url = match config.root_component_url { |
| Some(url) => Some(Url::new(url)?), |
| None => None, |
| }; |
| |
| let security_policy = if let Some(security_policy) = config.security_policy { |
| SecurityPolicy::try_from(security_policy).context("Unable to parse security policy")? |
| } else { |
| SecurityPolicy::default() |
| }; |
| |
| Ok(RuntimeConfig { |
| list_children_batch_size, |
| security_policy, |
| namespace_capabilities: Self::translate_namespace_capabilities( |
| config.namespace_capabilities, |
| )?, |
| debug: config.debug.unwrap_or(default.debug), |
| use_builtin_process_launcher: config |
| .use_builtin_process_launcher |
| .unwrap_or(default.use_builtin_process_launcher), |
| maintain_utc_clock: config.maintain_utc_clock.unwrap_or(default.maintain_utc_clock), |
| num_threads, |
| builtin_pkg_resolver: config |
| .builtin_pkg_resolver |
| .unwrap_or(default.builtin_pkg_resolver), |
| out_dir_contents: config.out_dir_contents.unwrap_or(default.out_dir_contents), |
| root_component_url, |
| component_id_index_path: config.component_id_index_path, |
| }) |
| } |
| } |
| |
| impl TryFrom<component_internal::SecurityPolicy> for SecurityPolicy { |
| type Error = Error; |
| |
| fn try_from(security_policy: component_internal::SecurityPolicy) -> Result<Self, Error> { |
| let job_policy = if let Some(job_policy) = &security_policy.job_policy { |
| let ambient_mark_vmo_exec = |
| parse_absolute_monikers_from_strings(&job_policy.ambient_mark_vmo_exec)?; |
| let main_process_critical = |
| parse_absolute_monikers_from_strings(&job_policy.main_process_critical)?; |
| JobPolicyAllowlists { ambient_mark_vmo_exec, main_process_critical } |
| } else { |
| JobPolicyAllowlists::default() |
| }; |
| |
| let capability_policy = if let Some(capability_policy) = &security_policy.capability_policy |
| { |
| if let Some(allowlist) = &capability_policy.allowlist { |
| let mut policies = HashMap::new(); |
| for e in allowlist.iter() { |
| let source_moniker = ExtendedMoniker::parse_string_without_instances( |
| e.source_moniker |
| .as_ref() |
| .ok_or(Error::new(PolicyConfigError::EmptySourceMoniker))?, |
| )?; |
| let source_name = if let Some(source_name) = e.source_name.as_ref() { |
| Ok(CapabilityName(source_name.clone())) |
| } else { |
| Err(PolicyConfigError::EmptyCapabilitySourceName) |
| }?; |
| let source = match e.source { |
| Some(fsys::Ref::Self_(_)) => Ok(CapabilityAllowlistSource::Self_), |
| Some(fsys::Ref::Framework(_)) => Ok(CapabilityAllowlistSource::Framework), |
| Some(fsys::Ref::Capability(_)) => Ok(CapabilityAllowlistSource::Capability), |
| _ => Err(Error::new(PolicyConfigError::InvalidSourceCapability)), |
| }?; |
| |
| let capability = if let Some(capability) = e.capability.as_ref() { |
| match &capability { |
| component_internal::AllowlistedCapability::Directory(_) => { |
| Ok(CapabilityTypeName::Directory) |
| } |
| component_internal::AllowlistedCapability::Event(_) => { |
| Ok(CapabilityTypeName::Event) |
| } |
| component_internal::AllowlistedCapability::Protocol(_) => { |
| Ok(CapabilityTypeName::Protocol) |
| } |
| component_internal::AllowlistedCapability::Service(_) => { |
| Ok(CapabilityTypeName::Service) |
| } |
| component_internal::AllowlistedCapability::Storage(_) => { |
| Ok(CapabilityTypeName::Storage) |
| } |
| component_internal::AllowlistedCapability::Runner(_) => { |
| Ok(CapabilityTypeName::Runner) |
| } |
| component_internal::AllowlistedCapability::Resolver(_) => { |
| Ok(CapabilityTypeName::Resolver) |
| } |
| _ => Err(Error::new(PolicyConfigError::EmptyAllowlistedCapability)), |
| } |
| } else { |
| Err(Error::new(PolicyConfigError::EmptyAllowlistedCapability)) |
| }?; |
| |
| let target_monikers = HashSet::from_iter( |
| parse_absolute_monikers_from_strings(&e.target_monikers)?.iter().cloned(), |
| ); |
| |
| policies.insert( |
| CapabilityAllowlistKey { source_moniker, source_name, source, capability }, |
| target_monikers, |
| ); |
| } |
| policies |
| } else { |
| HashMap::new() |
| } |
| } else { |
| HashMap::new() |
| }; |
| |
| Ok(SecurityPolicy { job_policy, capability_policy }) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| cm_types::ParseError, |
| fidl::encoding::encode_persistent, |
| fidl::endpoints::ServerEnd, |
| fidl_fuchsia_io as fio, fidl_fuchsia_io2 as fio2, fuchsia_zircon as zx, |
| futures::future, |
| matches::assert_matches, |
| vfs::{ |
| directory::entry::DirectoryEntry, execution_scope::ExecutionScope, |
| file::pcb::asynchronous::read_only, path, pseudo_directory, |
| }, |
| }; |
| |
| const FOO_PKG_URL: &str = "fuchsia-pkg://fuchsia.com/foo#meta/foo.cmx"; |
| |
| macro_rules! test_config_ok { |
| ( |
| $( |
| $test_name:ident => ($input:expr, $expected:expr), |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| assert_matches!(RuntimeConfig::try_from($input), Ok(v) if v == $expected); |
| } |
| )+ |
| }; |
| } |
| |
| macro_rules! test_config_err { |
| ( |
| $( |
| $test_name:ident => ($input:expr, $type:ty, $expected:expr), |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| assert_eq!(*RuntimeConfig::try_from($input).unwrap_err().downcast_ref::<$type>().unwrap(), $expected); |
| } |
| )+ |
| }; |
| } |
| |
| test_config_ok! { |
| all_fields_none => (component_internal::Config { |
| debug: None, |
| list_children_batch_size: None, |
| security_policy: None, |
| maintain_utc_clock: None, |
| use_builtin_process_launcher: None, |
| num_threads: None, |
| namespace_capabilities: None, |
| builtin_pkg_resolver: None, |
| out_dir_contents: None, |
| root_component_url: None, |
| component_id_index_path: None, |
| ..component_internal::Config::EMPTY |
| }, RuntimeConfig::default()), |
| all_leaf_nodes_none => (component_internal::Config { |
| debug: Some(false), |
| list_children_batch_size: Some(5), |
| maintain_utc_clock: Some(false), |
| builtin_pkg_resolver: None, |
| use_builtin_process_launcher: Some(true), |
| security_policy: Some(component_internal::SecurityPolicy { |
| job_policy: Some(component_internal::JobPolicyAllowlists { |
| main_process_critical: None, |
| ambient_mark_vmo_exec: None, |
| ..component_internal::JobPolicyAllowlists::EMPTY |
| }), |
| capability_policy: None, |
| ..component_internal::SecurityPolicy::EMPTY |
| }), |
| num_threads: Some(10), |
| namespace_capabilities: None, |
| out_dir_contents: None, |
| root_component_url: None, |
| component_id_index_path: None, |
| ..component_internal::Config::EMPTY |
| }, RuntimeConfig { |
| debug:false, list_children_batch_size: 5, |
| maintain_utc_clock: false, use_builtin_process_launcher:true, |
| num_threads: 10, |
| builtin_pkg_resolver: BuiltinPkgResolver::None, |
| ..Default::default() }), |
| all_fields_some => ( |
| component_internal::Config { |
| debug: Some(true), |
| list_children_batch_size: Some(42), |
| maintain_utc_clock: Some(true), |
| use_builtin_process_launcher: Some(false), |
| builtin_pkg_resolver: Some(component_internal::BuiltinPkgResolver::None), |
| security_policy: Some(component_internal::SecurityPolicy { |
| job_policy: Some(component_internal::JobPolicyAllowlists { |
| main_process_critical: Some(vec!["/something/important".to_string()]), |
| ambient_mark_vmo_exec: Some(vec!["/".to_string(), "/foo/bar".to_string()]), |
| ..component_internal::JobPolicyAllowlists::EMPTY |
| }), |
| capability_policy: Some(component_internal::CapabilityPolicyAllowlists { |
| allowlist: Some(vec![ |
| component_internal::CapabilityAllowlistEntry { |
| source_moniker: Some("<component_manager>".to_string()), |
| source_name: Some("fuchsia.kernel.RootResource".to_string()), |
| source: Some(fsys::Ref::Self_(fsys::SelfRef {})), |
| capability: Some(component_internal::AllowlistedCapability::Protocol(component_internal::AllowlistedProtocol::EMPTY)), |
| target_monikers: Some(vec![ |
| "/root".to_string(), |
| "/root/bootstrap".to_string(), |
| "/root/core".to_string() |
| ]), |
| ..component_internal::CapabilityAllowlistEntry::EMPTY |
| }, |
| component_internal::CapabilityAllowlistEntry { |
| source_moniker: Some("/foo/bar".to_string()), |
| source_name: Some("running".to_string()), |
| source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})), |
| capability: Some(component_internal::AllowlistedCapability::Event(component_internal::AllowlistedEvent::EMPTY)), |
| target_monikers: Some(vec![ |
| "/foo/bar".to_string(), |
| "/foo/bar/baz".to_string() |
| ]), |
| ..component_internal::CapabilityAllowlistEntry::EMPTY |
| }, |
| ]), ..component_internal::CapabilityPolicyAllowlists::EMPTY}), |
| ..component_internal::SecurityPolicy::EMPTY |
| }), |
| num_threads: Some(24), |
| namespace_capabilities: Some(vec![ |
| fsys::CapabilityDecl::Protocol(fsys::ProtocolDecl { |
| name: Some("foo_svc".into()), |
| source_path: Some("/svc/foo".into()), |
| ..fsys::ProtocolDecl::EMPTY |
| }), |
| fsys::CapabilityDecl::Directory(fsys::DirectoryDecl { |
| name: Some("bar_dir".into()), |
| source_path: Some("/bar".into()), |
| rights: Some(fio2::Operations::Connect), |
| ..fsys::DirectoryDecl::EMPTY |
| }), |
| ]), |
| out_dir_contents: Some(component_internal::OutDirContents::Svc), |
| root_component_url: Some(FOO_PKG_URL.to_string()), |
| component_id_index_path: Some("/boot/config/component_id_index".to_string()), |
| ..component_internal::Config::EMPTY |
| }, |
| RuntimeConfig { |
| debug: true, |
| list_children_batch_size: 42, |
| maintain_utc_clock: true, |
| use_builtin_process_launcher: false, |
| security_policy: SecurityPolicy { |
| job_policy: JobPolicyAllowlists { |
| ambient_mark_vmo_exec: vec![ |
| AbsoluteMoniker::root(), |
| AbsoluteMoniker::from(vec!["foo:0", "bar:0"]), |
| ], |
| main_process_critical: vec![ |
| AbsoluteMoniker::from(vec!["something:0", "important:0"]), |
| ], |
| }, |
| capability_policy: HashMap::from_iter(vec![ |
| (CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentManager, |
| source_name: CapabilityName::from("fuchsia.kernel.RootResource"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| HashSet::from_iter(vec![ |
| AbsoluteMoniker::from(vec!["root:0"]), |
| AbsoluteMoniker::from(vec!["root:0", "bootstrap:0"]), |
| AbsoluteMoniker::from(vec!["root:0", "core:0"]), |
| ].iter().cloned()) |
| ), |
| (CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec!["foo:0", "bar:0"])), |
| source_name: CapabilityName::from("running"), |
| source: CapabilityAllowlistSource::Framework, |
| capability: CapabilityTypeName::Event, |
| }, |
| HashSet::from_iter(vec![ |
| AbsoluteMoniker::from(vec!["foo:0", "bar:0"]), |
| AbsoluteMoniker::from(vec!["foo:0", "bar:0", "baz:0"]), |
| ].iter().cloned()) |
| ), |
| ].iter().cloned()), |
| }, |
| num_threads: 24, |
| namespace_capabilities: vec![ |
| cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl { |
| name: "foo_svc".into(), |
| source_path: "/svc/foo".parse().unwrap(), |
| }), |
| cm_rust::CapabilityDecl::Directory(cm_rust::DirectoryDecl { |
| name: "bar_dir".into(), |
| source_path: "/bar".parse().unwrap(), |
| rights: fio2::Operations::Connect, |
| }), |
| ], |
| builtin_pkg_resolver: BuiltinPkgResolver::None, |
| out_dir_contents: OutDirContents::Svc, |
| root_component_url: Some(Url::new(FOO_PKG_URL.to_string()).unwrap()), |
| component_id_index_path: Some("/boot/config/component_id_index".to_string()), |
| } |
| ), |
| } |
| |
| test_config_err! { |
| invalid_job_policy => (component_internal::Config { |
| debug: None, |
| list_children_batch_size: None, |
| maintain_utc_clock: None, |
| use_builtin_process_launcher: None, |
| builtin_pkg_resolver: None, |
| security_policy: Some(component_internal::SecurityPolicy { |
| job_policy: Some(component_internal::JobPolicyAllowlists { |
| main_process_critical: None, |
| ambient_mark_vmo_exec: Some(vec!["/".to_string(), "bad".to_string()]), |
| ..component_internal::JobPolicyAllowlists::EMPTY |
| }), |
| capability_policy: None, |
| ..component_internal::SecurityPolicy::EMPTY |
| }), |
| num_threads: None, |
| namespace_capabilities: None, |
| out_dir_contents: None, |
| root_component_url: None, |
| component_id_index_path: None, |
| ..component_internal::Config::EMPTY |
| }, MonikerError, MonikerError::InvalidMoniker {rep: "bad".to_string()}), |
| invalid_capability_policy_empty_allowlist_cap => (component_internal::Config { |
| debug: None, |
| list_children_batch_size: None, |
| maintain_utc_clock: None, |
| use_builtin_process_launcher: None, |
| builtin_pkg_resolver: None, |
| security_policy: Some(component_internal::SecurityPolicy { |
| job_policy: None, |
| capability_policy: Some(component_internal::CapabilityPolicyAllowlists { |
| allowlist: Some(vec![ |
| component_internal::CapabilityAllowlistEntry { |
| source_moniker: Some("<component_manager>".to_string()), |
| source_name: Some("fuchsia.kernel.RootResource".to_string()), |
| source: Some(fsys::Ref::Self_(fsys::SelfRef{})), |
| capability: None, |
| target_monikers: Some(vec!["/root".to_string()]), |
| ..component_internal::CapabilityAllowlistEntry::EMPTY |
| }]), |
| ..component_internal::CapabilityPolicyAllowlists::EMPTY |
| }), |
| ..component_internal::SecurityPolicy::EMPTY |
| }), |
| num_threads: None, |
| namespace_capabilities: None, |
| out_dir_contents: None, |
| root_component_url: None, |
| component_id_index_path: None, |
| ..component_internal::Config::EMPTY |
| }, PolicyConfigError, PolicyConfigError::EmptyAllowlistedCapability), |
| invalid_capability_policy_empty_source_moniker => (component_internal::Config { |
| debug: None, |
| list_children_batch_size: None, |
| maintain_utc_clock: None, |
| use_builtin_process_launcher: None, |
| builtin_pkg_resolver: None, |
| security_policy: Some(component_internal::SecurityPolicy { |
| job_policy: None, |
| capability_policy: Some(component_internal::CapabilityPolicyAllowlists { |
| allowlist: Some(vec![ |
| component_internal::CapabilityAllowlistEntry { |
| source_moniker: None, |
| source_name: Some("fuchsia.kernel.RootResource".to_string()), |
| capability: Some(component_internal::AllowlistedCapability::Protocol(component_internal::AllowlistedProtocol::EMPTY)), |
| target_monikers: Some(vec!["/root".to_string()]), |
| ..component_internal::CapabilityAllowlistEntry::EMPTY |
| }]), |
| ..component_internal::CapabilityPolicyAllowlists::EMPTY |
| }), |
| ..component_internal::SecurityPolicy::EMPTY |
| }), |
| num_threads: None, |
| namespace_capabilities: None, |
| out_dir_contents: None, |
| root_component_url: None, |
| component_id_index_path: None, |
| ..component_internal::Config::EMPTY |
| }, PolicyConfigError, PolicyConfigError::EmptySourceMoniker), |
| invalid_root_component_url => (component_internal::Config { |
| debug: None, |
| list_children_batch_size: None, |
| maintain_utc_clock: None, |
| use_builtin_process_launcher: None, |
| builtin_pkg_resolver: None, |
| security_policy: None, |
| num_threads: None, |
| namespace_capabilities: None, |
| out_dir_contents: None, |
| root_component_url: Some("invalid url".to_string()), |
| component_id_index_path: None, |
| ..component_internal::Config::EMPTY |
| }, ParseError, ParseError::InvalidValue), |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn config_from_file_no_arg() -> Result<(), Error> { |
| let args = startup::Arguments::default(); |
| assert_matches!(RuntimeConfig::load_from_file(&args).await, Err(_)); |
| Ok(()) |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn config_from_file_missing() -> Result<(), Error> { |
| let args = startup::Arguments { config: "/foo/bar".to_string(), ..Default::default() }; |
| assert_matches!(RuntimeConfig::load_from_file(&args).await, Err(_)); |
| Ok(()) |
| } |
| |
| fn install_config_dir_in_namespace( |
| config_dir: &str, |
| config_file: &str, |
| content: Vec<u8>, |
| ) -> Result<(), Error> { |
| let dir = pseudo_directory!( |
| config_file => read_only(move || future::ready(Ok(content.clone()))), |
| ); |
| let (dir_server, dir_client) = zx::Channel::create().unwrap(); |
| dir.open( |
| ExecutionScope::new(), |
| fio::OPEN_RIGHT_READABLE, |
| fio::MODE_TYPE_DIRECTORY, |
| path::Path::empty(), |
| ServerEnd::new(dir_server), |
| ); |
| |
| let ns = fdio::Namespace::installed().expect("Failed to get installed namespace"); |
| ns.bind(config_dir, dir_client).expect("Failed to bind test directory"); |
| Ok(()) |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn config_from_file_valid() -> Result<(), Error> { |
| // Install a directory containing a test config file in the test process's namespace. |
| let config_dir = "/valid_config"; |
| let config_file = "test_config"; |
| let mut config = component_internal::Config { |
| debug: None, |
| list_children_batch_size: Some(42), |
| security_policy: None, |
| namespace_capabilities: None, |
| maintain_utc_clock: None, |
| use_builtin_process_launcher: None, |
| num_threads: None, |
| builtin_pkg_resolver: None, |
| out_dir_contents: None, |
| root_component_url: None, |
| ..component_internal::Config::EMPTY |
| }; |
| install_config_dir_in_namespace(config_dir, config_file, encode_persistent(&mut config)?)?; |
| |
| let config_path = [config_dir, "/", config_file].concat(); |
| let args = startup::Arguments { config: config_path.to_string(), ..Default::default() }; |
| let expected = ( |
| RuntimeConfig { list_children_batch_size: 42, ..Default::default() }, |
| PathBuf::from(config_path), |
| ); |
| assert_matches!(RuntimeConfig::load_from_file(&args).await, Ok(v) if v == expected); |
| Ok(()) |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn config_from_file_invalid() -> Result<(), Error> { |
| // Install a directory containing a test config file in the test process's namespace. |
| let config_dir = "/invalid_config"; |
| let config_file = "test_config"; |
| // Add config file containing garbage data. |
| install_config_dir_in_namespace(config_dir, config_file, vec![0xfa, 0xde])?; |
| |
| let config_path = [config_dir, "/", config_file].concat(); |
| let args = startup::Arguments { config: config_path.to_string(), ..Default::default() }; |
| assert_matches!(RuntimeConfig::load_from_file(&args).await, Err(_)); |
| Ok(()) |
| } |
| } |