Merge "Fix potential uninitialized variables bug"
diff --git a/authfs/src/file/remote_file.rs b/authfs/src/file/remote_file.rs
index 903c143..039285f 100644
--- a/authfs/src/file/remote_file.rs
+++ b/authfs/src/file/remote_file.rs
@@ -17,6 +17,7 @@
use std::cmp::min;
use std::convert::TryFrom;
use std::io;
+use std::path::Path;
use super::{ChunkBuffer, RandomWrite, ReadByChunk, VirtFdService};
use crate::common::CHUNK_SIZE;
@@ -48,6 +49,29 @@
pub fn new(service: VirtFdService, file_fd: i32) -> Self {
RemoteFileReader { service, file_fd }
}
+
+ pub fn new_by_path(
+ service: VirtFdService,
+ dir_fd: i32,
+ related_path: &Path,
+ ) -> io::Result<Self> {
+ let file_fd =
+ service.openFileInDirectory(dir_fd, related_path.to_str().unwrap()).map_err(|e| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ format!(
+ "Failed to create a remote file reader by path {}: {}",
+ related_path.display(),
+ e.get_description()
+ ),
+ )
+ })?;
+ Ok(RemoteFileReader { service, file_fd })
+ }
+
+ pub fn get_remote_fd(&self) -> i32 {
+ self.file_fd
+ }
}
impl ReadByChunk for RemoteFileReader {
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index ca73174..b456f33 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-use anyhow::{bail, Result};
+use anyhow::{anyhow, bail, Result};
use log::{debug, warn};
use std::collections::{btree_map, BTreeMap};
use std::convert::TryFrom;
@@ -24,7 +24,7 @@
use std::mem::MaybeUninit;
use std::option::Option;
use std::os::unix::{ffi::OsStrExt, io::AsRawFd};
-use std::path::{Path, PathBuf};
+use std::path::{Component, Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use std::time::Duration;
@@ -37,8 +37,8 @@
use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
use crate::file::{
- InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
- RemoteMerkleTreeReader,
+ validate_basename, InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor,
+ RemoteFileReader, RemoteMerkleTreeReader,
};
use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
@@ -97,34 +97,79 @@
AuthFs { inode_table: Mutex::new(inode_table), next_inode: AtomicU64::new(ROOT_INODE + 1) }
}
+ /// Add an `AuthFsEntry` as `basename` to the filesystem root.
pub fn add_entry_at_root_dir(
&mut self,
basename: PathBuf,
entry: AuthFsEntry,
) -> Result<Inode> {
- if basename.is_absolute() {
- bail!("Invalid entry name: {:?}", basename);
- }
+ validate_basename(&basename)?;
+ self.add_entry_at_ro_dir_by_path(ROOT_INODE, &basename, entry)
+ }
- let inode_table = &mut *self.inode_table.get_mut().unwrap();
- match inode_table
- .get_mut(&ROOT_INODE)
- .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?
- {
+ /// Add an `AuthFsEntry` by path from the `ReadonlyDirectory` represented by `dir_inode`. The
+ /// path must be a related path. If some ancestor directories do not exist, they will be
+ /// created (also as `ReadonlyDirectory`) automatically.
+ pub fn add_entry_at_ro_dir_by_path(
+ &mut self,
+ dir_inode: Inode,
+ path: &Path,
+ entry: AuthFsEntry,
+ ) -> Result<Inode> {
+ // 1. Make sure the parent directories all exist. Derive the entry's parent inode.
+ let parent_path =
+ path.parent().ok_or_else(|| anyhow!("No parent directory: {:?}", path))?;
+ let parent_inode =
+ parent_path.components().try_fold(dir_inode, |current_dir_inode, path_component| {
+ match path_component {
+ Component::RootDir => bail!("Absolute path is not supported"),
+ Component::Normal(name) => {
+ let inode_table = self.inode_table.get_mut().unwrap();
+ // Locate the internal directory structure.
+ let current_dir_entry =
+ inode_table.get_mut(¤t_dir_inode).ok_or_else(|| {
+ anyhow!("Unknown directory inode {}", current_dir_inode)
+ })?;
+ let dir = match current_dir_entry {
+ AuthFsEntry::ReadonlyDirectory { dir } => dir,
+ _ => unreachable!("Not a ReadonlyDirectory"),
+ };
+ // Return directory inode. Create first if not exists.
+ if let Some(existing_inode) = dir.lookup_inode(name.as_ref()) {
+ Ok(existing_inode)
+ } else {
+ let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
+ let new_dir_entry =
+ AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() };
+
+ // Actually update the tables.
+ dir.add_entry(name.as_ref(), new_inode)?;
+ if inode_table.insert(new_inode, new_dir_entry).is_some() {
+ bail!("Unexpected to find a duplicated inode");
+ }
+ Ok(new_inode)
+ }
+ }
+ _ => Err(anyhow!("Path is not canonical: {:?}", path)),
+ }
+ })?;
+
+ // 2. Insert the entry to the parent directory, as well as the inode table.
+ let inode_table = self.inode_table.get_mut().unwrap();
+ match inode_table.get_mut(&parent_inode).expect("previously returned inode") {
AuthFsEntry::ReadonlyDirectory { dir } => {
+ let basename =
+ path.file_name().ok_or_else(|| anyhow!("Bad file name: {:?}", path))?;
let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
- dir.add_entry(&basename, new_inode)?;
+ // Actually update the tables.
+ dir.add_entry(basename.as_ref(), new_inode)?;
if inode_table.insert(new_inode, entry).is_some() {
- bail!(
- "Found duplicated inode {} when adding {}",
- new_inode,
- basename.display()
- );
+ bail!("Unexpected to find a duplicated inode");
}
Ok(new_inode)
}
- _ => bail!("Not a ReadonlyDirectory"),
+ _ => unreachable!("Not a ReadonlyDirectory"),
}
}
}
@@ -333,9 +378,8 @@
// Create the entry's stat if found.
let st = self.handle_inode(&inode, |entry| match entry {
- AuthFsEntry::ReadonlyDirectory { .. } => {
- unreachable!("FUSE shouldn't need to look up the root inode");
- //create_dir_stat(inode, dir.number_of_entries())
+ AuthFsEntry::ReadonlyDirectory { dir } => {
+ create_dir_stat(inode, dir.number_of_entries())
}
AuthFsEntry::UnverifiedReadonly { file_size, .. }
| AuthFsEntry::VerifiedReadonly { file_size, .. } => {
@@ -613,6 +657,9 @@
let new_dir = dir.mkdir(basename, new_inode)?;
Ok(AuthFsEntry::VerifiedNewDirectory { dir: new_dir })
}
+ AuthFsEntry::ReadonlyDirectory { .. } => {
+ Err(io::Error::from_raw_os_error(libc::EACCES))
+ }
_ => Err(io::Error::from_raw_os_error(libc::EBADF)),
}
})?;
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(())
}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 8a13ef3..70d48c2 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -407,6 +407,38 @@
assertFailedOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir/dir");
}
+ @Test
+ public void testInputDirectory_CanReadFile() throws Exception {
+ // Setup
+ String authfsInputDir = MOUNT_DIR + "/3";
+ runFdServerOnAndroid("--open-dir 3:/system", "--ro-dirs 3");
+ // TODO(203251769): Replace /dev/null with real manifest file when it's generated. We
+ // currently hard-coded the files for the test manually, and ignore the integrity check.
+ runAuthFsOnMicrodroid("--remote-ro-dir 3:/dev/null:/system --cid " + VMADDR_CID_HOST);
+
+ // Action
+ String actualHash =
+ computeFileHashOnMicrodroid(authfsInputDir + "/system/framework/framework.jar");
+
+ // Verify
+ String expectedHash = computeFileHashOnAndroid("/system/framework/framework.jar");
+ assertEquals("Expect consistent hash through /authfs/3: ", expectedHash, actualHash);
+ }
+
+ @Test
+ public void testInputDirectory_OnlyAllowlistedFilesExist() throws Exception {
+ // Setup
+ String authfsInputDir = MOUNT_DIR + "/3";
+ runFdServerOnAndroid("--open-dir 3:/system", "--ro-dirs 3");
+ // TODO(203251769): Replace /dev/null with real manifest file when it's generated. We
+ // currently hard-coded the files for the test manually, and ignore the integrity check.
+ runAuthFsOnMicrodroid("--remote-ro-dir 3:/dev/null:/system --cid " + VMADDR_CID_HOST);
+
+ // Verify
+ runOnMicrodroid("test -f " + authfsInputDir + "/system/framework/services.jar");
+ assertFailedOnMicrodroid("test -f " + authfsInputDir + "/system/bin/sh");
+ }
+
private void expectBackingFileConsistency(
String authFsPath, String backendPath, String expectedHash)
throws DeviceNotAvailableException {
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 1826524..cf9d16e 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -1,3 +1,7 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_binary {
name: "fs_benchmark",
static_executable: true,