blob: 5e0301479d228d1d3bd0bf1a10e1422a306e7c5d [file] [log] [blame]
// Copyright 2019 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::{
capability::{BuiltinCapability, CapabilityProvider},
model::component::WeakComponentInstance,
},
::routing::{capability_source::InternalCapability, policy::ScopedPolicyChecker},
async_trait::async_trait,
cm_config::SecurityPolicy,
cm_types::Name,
cm_util::TaskGroup,
errors::CapabilityProviderError,
fuchsia_zircon as zx,
std::sync::Arc,
vfs::directory::entry::OpenRequest,
};
/// Trait for built-in runner services. Wraps the generic Runner trait to provide a
/// ScopedPolicyChecker for the realm of the component being started, so that runners can enforce
/// security policy.
pub trait BuiltinRunnerFactory: Send + Sync {
/// Get a connection to a scoped runner by pipelining a
/// `fuchsia.component.runner/ComponentRunner` server endpoint.
fn get_scoped_runner(
self: Arc<Self>,
checker: ScopedPolicyChecker,
open_request: OpenRequest<'_>,
) -> Result<(), zx::Status>;
}
/// Provides a hook for routing built-in runners to realms.
pub struct BuiltinRunner {
name: Name,
factory: Arc<dyn BuiltinRunnerFactory>,
security_policy: Arc<SecurityPolicy>,
}
impl BuiltinRunner {
pub fn new(
name: Name,
factory: Arc<dyn BuiltinRunnerFactory>,
security_policy: Arc<SecurityPolicy>,
) -> Self {
Self { name, factory, security_policy }
}
}
impl BuiltinCapability for BuiltinRunner {
fn matches(&self, capability: &InternalCapability) -> bool {
matches!(capability, InternalCapability::Runner(n) if *n == self.name)
}
fn new_provider(&self, target: WeakComponentInstance) -> Box<dyn CapabilityProvider> {
let checker =
ScopedPolicyChecker::new(self.security_policy.clone(), target.moniker.clone());
let runner = self.factory.clone();
Box::new(RunnerCapabilityProvider::new(runner, checker))
}
}
/// Allows a Rust `Runner` object to be treated as a generic capability,
/// as is required by the capability routing code.
#[derive(Clone)]
struct RunnerCapabilityProvider {
factory: Arc<dyn BuiltinRunnerFactory>,
checker: ScopedPolicyChecker,
}
impl RunnerCapabilityProvider {
fn new(factory: Arc<dyn BuiltinRunnerFactory>, checker: ScopedPolicyChecker) -> Self {
RunnerCapabilityProvider { factory, checker }
}
}
#[async_trait]
impl CapabilityProvider for RunnerCapabilityProvider {
async fn open(
self: Box<Self>,
_task_group: TaskGroup,
open_request: OpenRequest<'_>,
) -> Result<(), CapabilityProviderError> {
self.factory
.clone()
.get_scoped_runner(self.checker, open_request)
.map_err(|err| CapabilityProviderError::VfsOpenError(err))
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::model::testing::{mocks::MockRunner, routing_test_helpers::*},
anyhow::Error,
assert_matches::assert_matches,
cm_rust::{CapabilityDecl, RunnerDecl},
cm_rust_testing::*,
fidl_fuchsia_component_runner as fcrunner, fidl_fuchsia_io as fio,
futures::prelude::*,
moniker::{Moniker, MonikerBase},
vfs::{execution_scope::ExecutionScope, path::Path, ToObjectRequest},
};
fn sample_start_info(name: &str) -> fcrunner::ComponentStartInfo {
fcrunner::ComponentStartInfo {
resolved_url: Some(name.to_string()),
program: None,
ns: Some(vec![]),
outgoing_dir: None,
runtime_dir: None,
component_instance: Some(zx::Event::create()),
..Default::default()
}
}
// Test sending a start command to a failing runner.
#[fuchsia::test]
async fn capability_provider_error_from_runner() -> Result<(), Error> {
// Set up a capability provider wrapping a runner that returns an error on our
// target URL.
let mock_runner = Arc::new(MockRunner::new());
mock_runner.add_failing_url("xxx://failing");
let policy = Arc::new(SecurityPolicy::default());
let moniker = Moniker::try_from(vec!["foo"]).unwrap();
let checker = ScopedPolicyChecker::new(policy, moniker);
let provider = Box::new(RunnerCapabilityProvider { factory: mock_runner, checker });
// Open a connection to the provider.
let (client, server) = fidl::endpoints::create_proxy::<fcrunner::ComponentRunnerMarker>()?;
let server = server.into_channel();
let task_group = TaskGroup::new();
let scope = ExecutionScope::new();
let mut object_request = fio::OpenFlags::empty().to_object_request(server);
provider
.open(
task_group.clone(),
OpenRequest::new(
scope.clone(),
fio::OpenFlags::empty(),
Path::dot(),
&mut object_request,
),
)
.await?;
// Ensure errors are propagated back to the caller.
//
// We make multiple calls over the same channel to ensure that the channel remains open
// even after errors.
for _ in 0..3i32 {
let (client_controller, server_controller) =
fidl::endpoints::create_endpoints::<fcrunner::ComponentControllerMarker>();
client.start(sample_start_info("xxx://failing"), server_controller)?;
let actual = client_controller
.into_proxy()?
.take_event_stream()
.next()
.await
.unwrap()
.err()
.unwrap();
assert_matches!(
actual,
fidl::Error::ClientChannelClosed { status: zx::Status::UNAVAILABLE, .. }
);
}
Ok(())
}
// (cm)
// |
// a
//
// a: uses runner "elf" offered from the component mananger.
#[fuchsia::test]
async fn use_runner_from_component_manager() {
let mock_runner = Arc::new(MockRunner::new());
let components = vec![(
"a",
ComponentDeclBuilder::new_empty_component().program_runner("my_runner").build(),
)];
// Set up the system.
let universe = RoutingTestBuilder::new("a", components)
.set_builtin_capabilities(vec![CapabilityDecl::Runner(RunnerDecl {
name: "my_runner".parse().unwrap(),
source_path: None,
})])
.add_builtin_runner("my_runner", mock_runner.clone())
.build()
.await;
// Bind the root component.
universe.start_instance(&Moniker::root()).await.expect("bind failed");
// Ensure the instance starts up.
mock_runner.wait_for_url("test:///a_resolved").await;
}
// (cm)
// |
// a
// |
// b
//
// (cm): registers runner "elf".
// b: uses runner "elf".
#[fuchsia::test]
async fn use_runner_from_component_manager_environment() {
let mock_runner = Arc::new(MockRunner::new());
let components = vec![
(
"a",
ComponentDeclBuilder::new_empty_component()
.child_default("b")
.program_runner("elf")
.build(),
),
("b", ComponentDeclBuilder::new_empty_component().program_runner("elf").build()),
];
// Set up the system.
let universe = RoutingTestBuilder::new("a", components)
.set_builtin_capabilities(vec![CapabilityDecl::Runner(RunnerDecl {
name: "elf".parse().unwrap(),
source_path: None,
})])
.add_builtin_runner("elf", mock_runner.clone())
.build()
.await;
// Bind the child component.
universe.start_instance(&vec!["b"].try_into().unwrap()).await.expect("bind failed");
// Ensure the instances started up.
mock_runner.wait_for_urls(&["test:///b_resolved"]).await;
}
}