Support remote readonly directory
A remote readonly directory allows a user process to open/read files at
the configured mountpoint sub-directory, e.g.
/authfs/42/system/framework/framework.jar. Only allowlisted files are
visible.
There will be transparent integrity checks for all files under such a
directory, but it is not done in this change yet (tracked by
b/203251769).
See doc of `Args::remote_ro_dir` in main.rs for more details.
Bug: 203251769
Test: atest AuthFsHostTest
Change-Id: I716d6820a047761159c79947504579677c0fdeec
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index ae446e3..f08d9ea 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -30,7 +30,7 @@
use anyhow::{bail, Context, Result};
use log::error;
use std::convert::TryInto;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use structopt::StructOpt;
mod auth;
@@ -41,7 +41,9 @@
mod fusefs;
use auth::FakeAuthenticator;
-use file::{RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader};
+use file::{
+ InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader,
+};
use fsverity::{VerifiedFileEditor, VerifiedFileReader};
use fusefs::{AuthFs, AuthFsEntry};
@@ -80,6 +82,23 @@
#[structopt(long)]
remote_new_rw_file: Vec<i32>,
+ /// A read-only directory that represents a remote directory. The directory view is constructed
+ /// and finalized during the filesystem initialization based on the provided mapping file
+ /// (which is a serialized protobuf of android.security.fsverity.FSVerityDigests, which
+ /// essentially provides <file path, fs-verity digest> mappings of exported files). The mapping
+ /// file is supposed to come from a trusted location in order to provide a trusted view as well
+ /// as verified access of included files with their fs-verity digest. Not all files on the
+ /// remote host may be included in the mapping file, so the directory view may be partial. The
+ /// directory structure won't change throughout the filesystem lifetime.
+ ///
+ /// For example, `--remote-ro-dir 5:/path/to/mapping:/prefix/` tells the filesystem to
+ /// construct a directory structure defined in the mapping file at $MOUNTPOINT/5, which may
+ /// include a file like /5/system/framework/framework.jar. "/prefix/" tells the filesystem to
+ /// strip the path (e.g. "/system/") from the mount point to match the expected location of the
+ /// remote FD (e.g. a directory FD of "/system" in the remote).
+ #[structopt(long, parse(try_from_str = parse_remote_new_ro_dir_option))]
+ remote_ro_dir: Vec<OptionRemoteRoDir>,
+
/// A new directory that is assumed empty in the backing filesystem. New files created in this
/// directory are integrity-protected in the same way as --remote-new-verified-file. Can be
/// multiple.
@@ -103,6 +122,20 @@
_certificate_path: PathBuf,
}
+struct OptionRemoteRoDir {
+ /// ID to refer to the remote dir.
+ remote_dir_fd: i32,
+
+ /// A mapping file that describes the expecting file/directory structure and integrity metadata
+ /// in the remote directory. The file contains serialized protobuf of
+ /// android.security.fsverity.FSVerityDigests.
+ /// TODO(203251769): Really use the file when it's generated.
+ #[allow(dead_code)]
+ mapping_file_path: PathBuf,
+
+ prefix: PathBuf,
+}
+
fn parse_remote_ro_file_option(option: &str) -> Result<OptionRemoteRoFile> {
let strs: Vec<&str> = option.split(':').collect();
if strs.len() != 2 {
@@ -114,6 +147,18 @@
})
}
+fn parse_remote_new_ro_dir_option(option: &str) -> Result<OptionRemoteRoDir> {
+ let strs: Vec<&str> = option.split(':').collect();
+ if strs.len() != 3 {
+ bail!("Invalid option: {}", option);
+ }
+ Ok(OptionRemoteRoDir {
+ remote_dir_fd: strs[0].parse::<i32>().unwrap(),
+ mapping_file_path: PathBuf::from(strs[1]),
+ prefix: PathBuf::from(strs[2]),
+ })
+}
+
fn new_remote_verified_file_entry(
service: file::VirtFdService,
remote_fd: i32,
@@ -201,6 +246,39 @@
)?;
}
+ for config in &args.remote_ro_dir {
+ let dir_root_inode = authfs.add_entry_at_root_dir(
+ remote_fd_to_path_buf(config.remote_dir_fd),
+ AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() },
+ )?;
+
+ // TODO(203251769): Read actual path from config.mapping_file_path when it's generated.
+ let paths = vec![
+ Path::new("/system/framework/framework.jar"),
+ Path::new("/system/framework/services.jar"),
+ ];
+
+ for path in &paths {
+ let file_entry = {
+ // TODO(205883847): Not all files will be used. Open the remote file lazily.
+ let related_path = path.strip_prefix(&config.prefix)?;
+ let remote_file = RemoteFileReader::new_by_path(
+ service.clone(),
+ config.remote_dir_fd,
+ related_path,
+ )?;
+ let file_size = service.getFileSize(remote_file.get_remote_fd())?.try_into()?;
+ // TODO(203251769): Switch to VerifiedReadonly
+ AuthFsEntry::UnverifiedReadonly { reader: remote_file, file_size }
+ };
+ authfs.add_entry_at_ro_dir_by_path(
+ dir_root_inode,
+ path.strip_prefix("/")?,
+ file_entry,
+ )?;
+ }
+ }
+
Ok(())
}