authfs: Support binder-backed file source
This change adds remote file support to authfs. This allows a process to
read a remote file through a local path with transparent fs-verity
verification.
This is supposed to work across VM boundary, but before the remote
binder is ready, this change uses local binder.
Test: Shell #1
$ adb shell 'exec
9</system/bin/sh
8</data/local/tmp/input.4m
7</data/local/tmp/input.4m.merkle_dump
6</data/local/tmp/input.4m.fsv_sig
fd_server
--ro-fds 9
--ro-fds 8:7:6'`
Shell #2
$ adb push tools/device-test.sh /data/local/tmp/ && \
adb shell /data/local/tmp/device-test.sh
Change-Id: Ia69fae548b83ff3ba572f4a496a7cbcca518cbef
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 46e6fd8..74553f5 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -27,11 +27,12 @@
//! Regardless of the actual file name, the exposed file names through AuthFS are currently integer,
//! e.g. /mountpoint/42.
-use anyhow::{bail, Result};
+use anyhow::{anyhow, bail, Result};
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
+use std::sync::{Arc, Mutex};
use structopt::StructOpt;
mod auth;
@@ -40,18 +41,35 @@
mod fsverity;
mod fusefs;
mod reader;
+mod remote_file;
use auth::FakeAuthenticator;
use fsverity::FsverityChunkedFileReader;
use fusefs::{FileConfig, Inode};
use reader::ChunkedFileReader;
+use remote_file::{RemoteChunkedFileReader, RemoteFsverityMerkleTreeReader};
#[derive(StructOpt)]
-struct Options {
+struct Args {
/// Mount point of AuthFS.
#[structopt(parse(from_os_str))]
mount_point: PathBuf,
+ /// A verifiable read-only file. Can be multiple.
+ ///
+ /// For example, `--remote-verified-file 5:10:1234:/path/to/cert` tells the filesystem to
+ /// associate entry 5 with a remote file 10 of size 1234 bytes, and need to be verified against
+ /// the /path/to/cert.
+ #[structopt(long, parse(try_from_str = parse_remote_verified_file_option))]
+ remote_verified_file: Vec<RemoteVerifiedFileConfig>,
+
+ /// An unverifiable read-only file. Can be multiple.
+ ///
+ /// For example, `--remote-unverified-file 5:10:1234` tells the filesystem to associate entry 5
+ /// with a remote file 10 of size 1234 bytes.
+ #[structopt(long, parse(try_from_str = parse_remote_unverified_file_option))]
+ remote_unverified_file: Vec<RemoteUnverifiedFileConfig>,
+
/// Debug only. A readonly file to be protected by fs-verity. Can be multiple.
#[structopt(long, parse(try_from_str = parse_local_verified_file_option))]
local_verified_file: Vec<LocalVerifiedFileConfig>,
@@ -61,28 +79,91 @@
local_unverified_file: Vec<LocalUnverifiedFileConfig>,
}
+struct RemoteVerifiedFileConfig {
+ ino: Inode,
+
+ /// ID to refer to the remote file.
+ remote_id: i32,
+
+ /// Expected size of the remote file. Necessary for signature check and Merkle tree
+ /// verification.
+ file_size: u64,
+
+ /// Certificate to verify the authenticity of the file's fs-verity signature.
+ /// TODO(170494765): Implement PKCS#7 signature verification.
+ _certificate_path: PathBuf,
+}
+
+struct RemoteUnverifiedFileConfig {
+ ino: Inode,
+
+ /// ID to refer to the remote file.
+ remote_id: i32,
+
+ /// Expected size of the remote file.
+ file_size: u64,
+}
+
struct LocalVerifiedFileConfig {
ino: Inode,
+
+ /// Local path of the backing file.
file_path: PathBuf,
+
+ /// Local path of the backing file's fs-verity Merkle tree dump.
merkle_tree_dump_path: PathBuf,
+
+ /// Local path of fs-verity signature for the backing file.
signature_path: PathBuf,
+
+ /// Certificate to verify the authenticity of the file's fs-verity signature.
+ /// TODO(170494765): Implement PKCS#7 signature verification.
+ _certificate_path: PathBuf,
}
struct LocalUnverifiedFileConfig {
ino: Inode,
+
+ /// Local path of the backing file.
file_path: PathBuf,
}
-fn parse_local_verified_file_option(option: &str) -> Result<LocalVerifiedFileConfig> {
+fn parse_remote_verified_file_option(option: &str) -> Result<RemoteVerifiedFileConfig> {
let strs: Vec<&str> = option.split(':').collect();
if strs.len() != 4 {
bail!("Invalid option: {}", option);
}
+ Ok(RemoteVerifiedFileConfig {
+ ino: strs[0].parse::<Inode>()?,
+ remote_id: strs[1].parse::<i32>()?,
+ file_size: strs[2].parse::<u64>()?,
+ _certificate_path: PathBuf::from(strs[3]),
+ })
+}
+
+fn parse_remote_unverified_file_option(option: &str) -> Result<RemoteUnverifiedFileConfig> {
+ let strs: Vec<&str> = option.split(':').collect();
+ if strs.len() != 3 {
+ bail!("Invalid option: {}", option);
+ }
+ Ok(RemoteUnverifiedFileConfig {
+ ino: strs[0].parse::<Inode>()?,
+ remote_id: strs[1].parse::<i32>()?,
+ file_size: strs[2].parse::<u64>()?,
+ })
+}
+
+fn parse_local_verified_file_option(option: &str) -> Result<LocalVerifiedFileConfig> {
+ let strs: Vec<&str> = option.split(':').collect();
+ if strs.len() != 5 {
+ bail!("Invalid option: {}", option);
+ }
Ok(LocalVerifiedFileConfig {
- ino: strs[0].parse::<Inode>().unwrap(),
+ ino: strs[0].parse::<Inode>()?,
file_path: PathBuf::from(strs[1]),
merkle_tree_dump_path: PathBuf::from(strs[2]),
signature_path: PathBuf::from(strs[3]),
+ _certificate_path: PathBuf::from(strs[4]),
})
}
@@ -92,11 +173,39 @@
bail!("Invalid option: {}", option);
}
Ok(LocalUnverifiedFileConfig {
- ino: strs[0].parse::<Inode>().unwrap(),
+ ino: strs[0].parse::<Inode>()?,
file_path: PathBuf::from(strs[1]),
})
}
+fn new_config_remote_verified_file(remote_id: i32, file_size: u64) -> Result<FileConfig> {
+ let service = remote_file::server::get_local_service();
+ let signature = service
+ .readFsveritySignature(remote_id)
+ .map_err(|e| anyhow!("Failed to read signature: {}", e.get_description()))?;
+
+ let service = Arc::new(Mutex::new(service));
+ let authenticator = FakeAuthenticator::always_succeed();
+ Ok(FileConfig::RemoteVerifiedFile(
+ FsverityChunkedFileReader::new(
+ &authenticator,
+ RemoteChunkedFileReader::new(Arc::clone(&service), remote_id),
+ file_size,
+ signature,
+ RemoteFsverityMerkleTreeReader::new(Arc::clone(&service), remote_id),
+ )?,
+ file_size,
+ ))
+}
+
+fn new_config_remote_unverified_file(remote_id: i32, file_size: u64) -> Result<FileConfig> {
+ let file_reader = RemoteChunkedFileReader::new(
+ Arc::new(Mutex::new(remote_file::server::get_local_service())),
+ remote_id,
+ );
+ Ok(FileConfig::RemoteUnverifiedFile(file_reader, file_size))
+}
+
fn new_config_local_verified_file(
protected_file: &PathBuf,
merkle_tree_dump: &PathBuf,
@@ -125,9 +234,23 @@
Ok(FileConfig::LocalUnverifiedFile(file_reader, file_size))
}
-fn prepare_file_pool(args: &Options) -> Result<BTreeMap<Inode, FileConfig>> {
+fn prepare_file_pool(args: &Args) -> Result<BTreeMap<Inode, FileConfig>> {
let mut file_pool = BTreeMap::new();
+ for config in &args.remote_verified_file {
+ file_pool.insert(
+ config.ino,
+ new_config_remote_verified_file(config.remote_id, config.file_size)?,
+ );
+ }
+
+ for config in &args.remote_unverified_file {
+ file_pool.insert(
+ config.ino,
+ new_config_remote_unverified_file(config.remote_id, config.file_size)?,
+ );
+ }
+
for config in &args.local_verified_file {
file_pool.insert(
config.ino,
@@ -147,8 +270,8 @@
}
fn main() -> Result<()> {
- let args = Options::from_args();
+ let args = Args::from_args();
let file_pool = prepare_file_pool(&args)?;
fusefs::loop_forever(file_pool, &args.mount_point)?;
- Ok(())
+ bail!("Unexpected exit after the handler loop")
}