blob: c993acbdda0ab66c18acc44a4f8283f03559cf84 [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.
mod config;
mod validators;
use {
anyhow::{format_err, Error},
config::{Config, ConfigContext, FactoryConfig},
fidl::endpoints::{create_proxy, ProtocolMarker, Request, RequestStream, ServerEnd},
fidl_fuchsia_boot::FactoryItemsMarker,
fidl_fuchsia_factory::{
AlphaFactoryStoreProviderMarker, AlphaFactoryStoreProviderRequest,
AlphaFactoryStoreProviderRequestStream, CastCredentialsFactoryStoreProviderMarker,
CastCredentialsFactoryStoreProviderRequest,
CastCredentialsFactoryStoreProviderRequestStream, MiscFactoryStoreProviderMarker,
MiscFactoryStoreProviderRequest, MiscFactoryStoreProviderRequestStream,
PlayReadyFactoryStoreProviderMarker, PlayReadyFactoryStoreProviderRequest,
PlayReadyFactoryStoreProviderRequestStream, WeaveFactoryStoreProviderMarker,
WeaveFactoryStoreProviderRequest, WeaveFactoryStoreProviderRequestStream,
WidevineFactoryStoreProviderMarker, WidevineFactoryStoreProviderRequest,
WidevineFactoryStoreProviderRequestStream,
},
fidl_fuchsia_hardware_block as fhardware_block, fidl_fuchsia_io as fio,
fidl_fuchsia_storage_ext4::{MountVmoResult, Server_Marker},
fuchsia_bootfs::BootfsParser,
fuchsia_component::server::ServiceFs,
fuchsia_zircon as zx,
futures::{lock::Mutex, StreamExt as _, TryFutureExt as _, TryStreamExt as _},
remote_block_device::BlockClient as _,
std::{io, sync::Arc},
vfs::{
directory::{self, entry_container::Directory},
execution_scope::ExecutionScope,
file::vmo::read_only,
tree_builder::TreeBuilder,
},
};
const CONCURRENT_LIMIT: usize = 10_000;
const DEFAULT_BOOTFS_FACTORY_ITEM_EXTRA: u32 = 0;
const FACTORY_DEVICE_CONFIG: &'static str = "/config/data/factory.config";
enum IncomingServices {
AlphaFactoryStoreProvider(AlphaFactoryStoreProviderRequestStream),
CastCredentialsFactoryStoreProvider(CastCredentialsFactoryStoreProviderRequestStream),
MiscFactoryStoreProvider(MiscFactoryStoreProviderRequestStream),
PlayReadyFactoryStoreProvider(PlayReadyFactoryStoreProviderRequestStream),
WeaveFactoryStoreProvider(WeaveFactoryStoreProviderRequestStream),
WidevineFactoryStoreProvider(WidevineFactoryStoreProviderRequestStream),
}
async fn find_block_device_filepath(partition_path: &str) -> Result<String, Error> {
const DEV_CLASS_BLOCK: &str = "/dev/class/block";
let dir =
fuchsia_fs::directory::open_in_namespace(DEV_CLASS_BLOCK, fio::OpenFlags::RIGHT_READABLE)?;
device_watcher::wait_for_device_with(
&dir,
|device_watcher::DeviceInfo { filename, topological_path }| {
(topological_path == partition_path)
.then(|| format!("{}/{}", DEV_CLASS_BLOCK, filename))
},
)
.await
}
fn parse_bootfs<'a>(vmo: zx::Vmo) -> Arc<directory::immutable::Simple> {
let mut tree_builder = TreeBuilder::empty_dir();
match BootfsParser::create_from_vmo(vmo) {
Ok(parser) => parser.iter().for_each(|result| match result {
Ok(entry) => {
tracing::info!("Found {} in factory bootfs", &entry.name);
let name = entry.name;
let path_parts: Vec<&str> = name.split("/").collect();
let payload = entry.payload;
tree_builder
.add_entry(
&path_parts,
read_only(payload.unwrap_or_else(|| {
tracing::error!("Failed to buffer bootfs entry {}", name);
Vec::new()
})),
)
.unwrap_or_else(|err| {
tracing::error!(
"Failed to add bootfs entry {} to directory: {}",
name,
err
);
});
}
Err(err) => tracing::error!("BootfsParser: {}", err),
}),
Err(err) => tracing::error!("BootfsParser: {}", err),
};
tree_builder.build()
}
async fn fetch_new_factory_item() -> Result<zx::Vmo, Error> {
let factory_items = fuchsia_component::client::connect_to_protocol::<FactoryItemsMarker>()?;
let (vmo_opt, _) = factory_items.get(DEFAULT_BOOTFS_FACTORY_ITEM_EXTRA).await?;
vmo_opt.ok_or(format_err!("Failed to get a valid VMO from service"))
}
async fn read_file_from_proxy<'a>(
dir_proxy: &'a fio::DirectoryProxy,
file_path: &'a str,
) -> Result<Vec<u8>, Error> {
let file = fuchsia_fs::directory::open_file_no_describe(
&dir_proxy,
file_path,
fuchsia_fs::OpenFlags::RIGHT_READABLE,
)?;
fuchsia_fs::file::read(&file).await.map_err(Into::into)
}
fn load_config_file(path: &str) -> Result<FactoryConfig, Error> {
FactoryConfig::load(io::BufReader::new(std::fs::File::open(path)?))
}
async fn create_dir_from_context<'a>(
context: &'a ConfigContext,
dir: &'a fio::DirectoryProxy,
) -> Arc<directory::immutable::Simple> {
let mut tree_builder = TreeBuilder::empty_dir();
for (path, dest) in &context.file_path_map {
let contents = match read_file_from_proxy(dir, path).await {
Ok(contents) => contents,
Err(_) => {
tracing::error!("Failed to find {}, skipping", &path);
continue;
}
};
let mut failed_validation = false;
let mut validated = false;
for validator_context in &context.validator_contexts {
if validator_context.paths_to_validate.contains(path) {
tracing::info!("Validating {} with {} validator", &path, &validator_context.name);
if let Err(err) = validator_context.validator.validate(&path, &contents[..]) {
tracing::error!("{}", err);
failed_validation = true;
break;
}
validated = true;
}
}
// Do not allow files that failed validation or have not been validated at all.
if !failed_validation && validated {
let path_parts: Vec<&str> = dest.split("/").collect();
let file = read_only(contents);
tree_builder.add_entry(&path_parts, file).unwrap_or_else(|err| {
tracing::error!("Failed to add file {} to directory: {}", dest, err);
});
} else if !validated {
tracing::error!("{} was never validated, ignored", &path);
}
}
tree_builder.build()
}
async fn apply_config(config: Config, dir: Arc<Mutex<fio::DirectoryProxy>>) -> fio::DirectoryProxy {
let (directory_proxy, directory_server_end) = create_proxy::<fio::DirectoryMarker>().unwrap();
let dir_mtx = dir.clone();
// We only want to hold this lock to create `dir` so limit the scope of `dir_ref`.
let dir = {
let dir_ref = dir_mtx.lock().await;
let context = config.into_context().expect("Failed to convert config into context");
create_dir_from_context(&context, &*dir_ref).await
};
dir.open(
ExecutionScope::new(),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
vfs::path::Path::dot(),
ServerEnd::<fio::NodeMarker>::new(directory_server_end.into_channel()),
);
directory_proxy
}
async fn handle_request_stream<RS, G>(
mut stream: RS,
directory_mutex: Arc<Mutex<fio::DirectoryProxy>>,
mut get_directory_request_fn: G,
) -> Result<(), Error>
where
RS: RequestStream,
G: FnMut(Request<RS::Protocol>) -> Option<fidl::endpoints::ServerEnd<fio::DirectoryMarker>>,
{
while let Some(request) = stream.try_next().await? {
if let Some(directory_request) = get_directory_request_fn(request) {
if let Err(err) = directory_mutex.lock().await.clone(
fio::OpenFlags::RIGHT_READABLE,
ServerEnd::<fio::NodeMarker>::new(directory_request.into_channel()),
) {
tracing::error!(
"Failed to clone directory connection for {}: {:?}",
RS::Protocol::DEBUG_NAME,
err
);
}
}
}
Ok(())
}
async fn open_factory_source(factory_config: FactoryConfig) -> Result<fio::DirectoryProxy, Error> {
let (directory_proxy, directory_server_end) = create_proxy::<fio::DirectoryMarker>()?;
match factory_config {
FactoryConfig::FactoryItems => {
tracing::info!("{}", "Reading from FactoryItems service");
let factory_items_directory =
fetch_new_factory_item().await.map(|vmo| parse_bootfs(vmo)).unwrap_or_else(|err| {
tracing::error!(
"Failed to get factory item, returning empty item list: {}",
err
);
directory::immutable::simple()
});
factory_items_directory.open(
ExecutionScope::new(),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
vfs::path::Path::dot(),
ServerEnd::<fio::NodeMarker>::new(directory_server_end.into_channel()),
);
Ok(directory_proxy)
}
FactoryConfig::Ext4(partition_path) => {
tracing::info!("Reading from EXT4-formatted source: {}", partition_path);
let block_path = find_block_device_filepath(&partition_path).await?;
tracing::info!("found the block path {}", block_path);
let proxy = fuchsia_component::client::connect_to_protocol_at_path::<
fhardware_block::BlockMarker,
>(&block_path)?;
let block_client = remote_block_device::RemoteBlockClient::new(proxy).await?;
let block_count = block_client.block_count();
let block_size = block_client.block_size();
let size = block_count.checked_mul(block_size.into()).ok_or_else(|| {
format_err!("size overflows: block_count={} block_size={}", block_count, block_size)
})?;
let buf = async {
let size = size.try_into()?;
let mut buf = vec![0u8; size];
let () = block_client
.read_at(remote_block_device::MutableBufferSlice::Memory(buf.as_mut_slice()), 0)
.await?;
Ok::<_, Error>(buf)
}
.await?;
let vmo = zx::Vmo::create(size)?;
let () = vmo.write(&buf, 0)?;
let ext4_server = fuchsia_component::client::connect_to_protocol::<Server_Marker>()?;
tracing::info!("Mounting EXT4 VMO");
match ext4_server
.mount_vmo(vmo, fio::OpenFlags::RIGHT_READABLE, directory_server_end)
.await
{
Ok(MountVmoResult::Success(_)) => Ok(directory_proxy),
Ok(MountVmoResult::VmoReadFailure(status)) => {
Err(format_err!("Failed to read ext4 vmo: {}", status))
}
Ok(MountVmoResult::ParseError(parse_error)) => {
Err(format_err!("Failed to parse ext4 data: {:?}", parse_error))
}
Err(err) => Err(Error::from(err)),
_ => Err(format_err!("Unknown error while mounting ext4 vmo")),
}
}
FactoryConfig::FactoryVerity => {
tracing::info!("reading from factory verity");
fdio::open(
"/factory",
fio::OpenFlags::RIGHT_READABLE,
directory_server_end.into_channel(),
)?;
Ok(directory_proxy)
}
}
}
#[fuchsia::main(logging_tags = ["factory_store_providers"])]
async fn main() -> Result<(), Error> {
tracing::info!("{}", "Starting factory_store_providers");
let factory_config = load_config_file(FACTORY_DEVICE_CONFIG).unwrap_or_default();
let directory_proxy = open_factory_source(factory_config)
.await
.map_err(|e| {
tracing::error!("{:?}", e);
e
})
.unwrap();
let mut fs = ServiceFs::new();
fs.dir("svc")
.add_fidl_service(IncomingServices::AlphaFactoryStoreProvider)
.add_fidl_service(IncomingServices::CastCredentialsFactoryStoreProvider)
.add_fidl_service(IncomingServices::MiscFactoryStoreProvider)
.add_fidl_service(IncomingServices::PlayReadyFactoryStoreProvider)
.add_fidl_service(IncomingServices::WeaveFactoryStoreProvider)
.add_fidl_service(IncomingServices::WidevineFactoryStoreProvider);
fs.take_and_serve_directory_handle().expect("Failed to serve factory providers");
tracing::info!("{}", "Setting up factory directories");
let dir_mtx = Arc::new(Mutex::new(directory_proxy));
let alpha_config = Config::load::<AlphaFactoryStoreProviderMarker>().unwrap_or_default();
let alpha_directory = Arc::new(Mutex::new(apply_config(alpha_config, dir_mtx.clone()).await));
let cast_credentials_config = Config::load::<CastCredentialsFactoryStoreProviderMarker>()?;
let cast_directory =
Arc::new(Mutex::new(apply_config(cast_credentials_config, dir_mtx.clone()).await));
let misc_config = Config::load::<MiscFactoryStoreProviderMarker>()?;
let misc_directory = Arc::new(Mutex::new(apply_config(misc_config, dir_mtx.clone()).await));
let playready_config = Config::load::<PlayReadyFactoryStoreProviderMarker>()?;
let playready_directory =
Arc::new(Mutex::new(apply_config(playready_config, dir_mtx.clone()).await));
let widevine_config = Config::load::<WidevineFactoryStoreProviderMarker>()?;
let widevine_directory =
Arc::new(Mutex::new(apply_config(widevine_config, dir_mtx.clone()).await));
// The weave config may or may not be present.
let weave_config = Config::load::<WeaveFactoryStoreProviderMarker>().unwrap_or_default();
let weave_directory = Arc::new(Mutex::new(apply_config(weave_config, dir_mtx.clone()).await));
fs.for_each_concurrent(CONCURRENT_LIMIT, move |incoming_service| {
let alpha_directory_clone = alpha_directory.clone();
let cast_directory_clone = cast_directory.clone();
let misc_directory_clone = misc_directory.clone();
let playready_directory_clone = playready_directory.clone();
let weave_directory_clone = weave_directory.clone();
let widevine_directory_clone = widevine_directory.clone();
async move {
match incoming_service {
IncomingServices::AlphaFactoryStoreProvider(stream) => {
let alpha_directory_clone = alpha_directory_clone.clone();
handle_request_stream(
stream,
alpha_directory_clone,
|req: AlphaFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
IncomingServices::CastCredentialsFactoryStoreProvider(stream) => {
let cast_directory_clone = cast_directory_clone.clone();
handle_request_stream(
stream,
cast_directory_clone,
|req: CastCredentialsFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
IncomingServices::MiscFactoryStoreProvider(stream) => {
let misc_directory_clone = misc_directory_clone.clone();
handle_request_stream(
stream,
misc_directory_clone,
|req: MiscFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
IncomingServices::PlayReadyFactoryStoreProvider(stream) => {
let playready_directory_clone = playready_directory_clone.clone();
handle_request_stream(
stream,
playready_directory_clone,
|req: PlayReadyFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
IncomingServices::WeaveFactoryStoreProvider(stream) => {
let weave_directory_clone = weave_directory_clone.clone();
handle_request_stream(
stream,
weave_directory_clone,
|req: WeaveFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
IncomingServices::WidevineFactoryStoreProvider(stream) => {
let widevine_directory_clone = widevine_directory_clone.clone();
handle_request_stream(
stream,
widevine_directory_clone,
|req: WidevineFactoryStoreProviderRequest| {
req.into_get_factory_store().map(|item| item.0)
},
)
.await
}
}
}
.unwrap_or_else(|err| tracing::error!("Failed to handle incoming service: {}", err))
})
.await;
Ok(())
}
#[cfg(test)]
mod tests {
use {
super::*, fidl::endpoints::create_endpoints, fuchsia_async as fasync, vfs::pseudo_directory,
};
#[fasync::run_singlethreaded(test)]
async fn test_open_factory_verity() {
// Bind a vfs to /factory.
let dir = pseudo_directory! {
"a" => read_only("a content"),
"b" => pseudo_directory! {
"c" => read_only("c content"),
},
};
let (dir_client, dir_server) = create_endpoints();
let scope = ExecutionScope::new();
dir.open(
scope,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
vfs::path::Path::dot(),
ServerEnd::new(dir_server.into_channel()),
);
let ns = fdio::Namespace::installed().unwrap();
ns.bind("/factory", dir_client).unwrap();
let factory_proxy = open_factory_source(FactoryConfig::FactoryVerity).await.unwrap();
assert_eq!(
read_file_from_proxy(&factory_proxy, "a").await.unwrap(),
"a content".as_bytes()
);
assert_eq!(
read_file_from_proxy(&factory_proxy, "b/c").await.unwrap(),
"c content".as_bytes()
);
}
}