blob: a90f95f3df87797d1b07fcb5ecf02def955d73d8 [file] [log] [blame]
// Copyright 2022 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 {
anyhow::{bail, Result},
argh::{from_env, FromArgs},
fidl::endpoints::{create_endpoints, create_proxy, ServerEnd},
fidl_fuchsia_io as fio,
fidl_fuchsia_pkg::PackageUrl,
fidl_fuchsia_sys2::{StorageAdminMarker, StorageIteratorMarker},
fidl_fuchsia_update_installer::{
Initiator, InstallerMarker, MonitorMarker, MonitorRequest, Options, RebootControllerMarker,
State,
},
fidl_test_security_pkg::PackageServer_Marker,
fuchsia_async::Task,
fuchsia_component::client::connect_to_protocol,
fuchsia_fs::directory::readdir,
fuchsia_hash::Hash,
fuchsia_merkle::MerkleTree,
fuchsia_syslog::fx_log_info,
futures::{channel::oneshot::channel, join, TryStreamExt},
security_pkg_test_util::load_config,
std::fs::File,
};
/// Flags for bad_signature_update.
#[derive(FromArgs, Debug, PartialEq)]
pub struct Args {
/// absolute path to v1 update package (update.far) file used for
/// designating its merkle root hash.
#[argh(option)]
v1_update_far_path: String,
/// absolute path to shared test configuration file understood by
/// security_pkg_test_util::load_config().
#[argh(option)]
test_config_path: String,
/// switch used by rust test runner.
#[argh(switch)]
// TODO(fxbug.dev/84729)
#[allow(unused)]
nocapture: bool,
}
async fn get_storage_for_component_instance(moniker_prefix: &str) -> fio::DirectoryProxy {
let storage_admin = connect_to_protocol::<StorageAdminMarker>().unwrap();
let (storage_user_iterator, storage_user_iterator_server_end) =
create_proxy::<StorageIteratorMarker>().unwrap();
storage_admin
.list_storage_in_realm(".", storage_user_iterator_server_end)
.await
.unwrap()
.unwrap();
let mut matching_storage_users = vec![];
loop {
let chunk = storage_user_iterator.next().await.unwrap();
if chunk.is_empty() {
break;
}
let mut matches: Vec<String> =
chunk.into_iter().filter(|moniker| moniker.starts_with(moniker_prefix)).collect();
matching_storage_users.append(&mut matches);
}
assert_eq!(1, matching_storage_users.len());
let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>().unwrap();
storage_admin
.open_component_storage(
matching_storage_users.first().unwrap(),
fio::OpenFlags::RIGHT_READABLE,
fio::MODE_TYPE_DIRECTORY,
ServerEnd::new(server_end.into_channel()),
)
.unwrap();
proxy
}
async fn get_local_package_server_url() -> String {
connect_to_protocol::<PackageServer_Marker>().unwrap().get_url().await.unwrap()
}
async fn get_hello_world_v1_update_merkle(v1_update_far_path: String) -> Hash {
let (sender, receiver) = channel::<Hash>();
Task::local(async move {
let mut hello_world_v1_update = File::open(&v1_update_far_path).unwrap();
let hello_world_v1_update_merkle =
MerkleTree::from_reader(&mut hello_world_v1_update).unwrap().root();
sender.send(hello_world_v1_update_merkle).unwrap();
})
.detach();
receiver.await.unwrap()
}
async fn attempt_update(update_url: &str) -> Result<State> {
let installer_proxy = connect_to_protocol::<InstallerMarker>().unwrap();
let (monitor_client_end, monitor_server_end) = create_endpoints::<MonitorMarker>().unwrap();
// Prevent reboot attempt by signalling that the client (this code) will
// manage reboot via the provided RebootController.
let (_reboot_controller_proxy, reboot_controller_server_end) =
create_proxy::<RebootControllerMarker>().unwrap();
installer_proxy
.start_update(
&mut PackageUrl { url: update_url.to_string() },
Options {
initiator: Some(Initiator::Service),
allow_attach_to_existing_attempt: Some(false),
should_write_recovery: Some(false),
..Options::EMPTY
},
monitor_client_end,
Some(reboot_controller_server_end),
)
.await
.unwrap()
.unwrap();
let mut monitor_stream = monitor_server_end.into_stream().unwrap();
while let Some(request) = monitor_stream.try_next().await.unwrap() {
match request {
MonitorRequest::OnState { state, responder } => {
fx_log_info!("Update state change: {:#?}", state);
responder.send().unwrap();
match state {
// All terminal states plus `WaitToReboot` (which can only
// lead to successful terminal states).
State::WaitToReboot(_)
| State::Reboot(_)
| State::DeferReboot(_)
| State::Complete(_)
| State::FailPrepare(_)
| State::FailFetch(_) => {
return Ok(state);
}
_ => {}
}
}
}
}
bail!("Unexpected exit from update monitor state machine loop");
}
#[fuchsia::test]
async fn bad_signature_update() {
fx_log_info!("Starting bad_signature_update test");
let args @ Args { v1_update_far_path, test_config_path, .. } = &from_env();
fx_log_info!("Initalizing bad_signature_update with {:?}", args);
// Load test environment configuration.
let config = load_config(test_config_path);
// Setup storage capabilities.
let pkg_resolver_storage_proxy = get_storage_for_component_instance("./pkg-resolver").await;
// TODO(fxbug.dev/88453): Need a test that confirms assumption: Production
// configuration is an empty mutable storage directory.
assert!(readdir(&pkg_resolver_storage_proxy).await.unwrap().is_empty());
fx_log_info!("Gathering data and connecting to package server");
// Setup package server and perform pre-update access check.
let (update_merkle, package_server_url) = join!(
get_hello_world_v1_update_merkle(v1_update_far_path.to_string()),
get_local_package_server_url()
);
fx_log_info!("Package server running on {}", package_server_url);
// Placeholder assertion for well-formed local URL. Test will eventually use
// URL to configure network connection for `pkg-resolver`.
assert!(package_server_url.starts_with("https://localhost"));
let update_url =
format!("fuchsia-pkg://{}/update/0?hash={}", config.update_domain, update_merkle);
fx_log_info!("Initiating update: {}", update_url);
let update_result = attempt_update(&update_url).await.unwrap();
// Must not end in an "update complete" state.
assert!(match update_result {
State::WaitToReboot(_) | State::Reboot(_) | State::DeferReboot(_) | State::Complete(_) => {
false
}
_ => {
true
}
});
}