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/Android.bp b/authfs/Android.bp
index 3fc4504..9f7be93 100644
--- a/authfs/Android.bp
+++ b/authfs/Android.bp
@@ -10,6 +10,7 @@
     ],
     edition: "2018",
     rustlibs: [
+        "authfs_aidl_interface-rust",
         "libanyhow",
         "libauthfs_crypto_bindgen",
         "libcfg_if",
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 484aad4..0dfd0af 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -32,6 +32,7 @@
 use crate::common::{divide_roundup, COMMON_PAGE_SIZE};
 use crate::fsverity::FsverityChunkedFileReader;
 use crate::reader::{ChunkedFileReader, ReadOnlyDataByChunk};
+use crate::remote_file::{RemoteChunkedFileReader, RemoteFsverityMerkleTreeReader};
 
 // We're reading the backing file by chunk, so setting the block size to be the same.
 const BLOCK_SIZE: usize = COMMON_PAGE_SIZE as usize;
@@ -41,6 +42,9 @@
 pub type Inode = u64;
 type Handle = u64;
 
+type RemoteFsverityChunkedFileReader =
+    FsverityChunkedFileReader<RemoteChunkedFileReader, RemoteFsverityMerkleTreeReader>;
+
 // A debug only type where everything are stored as local files.
 type FileBackedFsverityChunkedFileReader =
     FsverityChunkedFileReader<ChunkedFileReader, ChunkedFileReader>;
@@ -48,6 +52,8 @@
 pub enum FileConfig {
     LocalVerifiedFile(FileBackedFsverityChunkedFileReader, u64),
     LocalUnverifiedFile(ChunkedFileReader, u64),
+    RemoteVerifiedFile(RemoteFsverityChunkedFileReader, u64),
+    RemoteUnverifiedFile(RemoteChunkedFileReader, u64),
 }
 
 struct AuthFs {
@@ -204,7 +210,9 @@
         let inode = num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
         let st = match self.get_file_config(&inode)? {
             FileConfig::LocalVerifiedFile(_, file_size)
-            | FileConfig::LocalUnverifiedFile(_, file_size) => create_stat(inode, *file_size)?,
+            | FileConfig::LocalUnverifiedFile(_, file_size)
+            | FileConfig::RemoteUnverifiedFile(_, file_size)
+            | FileConfig::RemoteVerifiedFile(_, file_size) => create_stat(inode, *file_size)?,
         };
         Ok(Entry {
             inode,
@@ -224,7 +232,9 @@
         Ok((
             match self.get_file_config(&inode)? {
                 FileConfig::LocalVerifiedFile(_, file_size)
-                | FileConfig::LocalUnverifiedFile(_, file_size) => create_stat(inode, *file_size)?,
+                | FileConfig::LocalUnverifiedFile(_, file_size)
+                | FileConfig::RemoteUnverifiedFile(_, file_size)
+                | FileConfig::RemoteVerifiedFile(_, file_size) => create_stat(inode, *file_size)?,
             },
             DEFAULT_METADATA_TIMEOUT,
         ))
@@ -239,13 +249,13 @@
         // Since file handle is not really used in later operations (which use Inode directly),
         // return None as the handle..
         match self.get_file_config(&inode)? {
-            FileConfig::LocalVerifiedFile(_, _) => {
+            FileConfig::LocalVerifiedFile(_, _) | FileConfig::RemoteVerifiedFile(_, _) => {
                 check_access_mode(flags, libc::O_RDONLY)?;
                 // Once verified, and only if verified, the file content can be cached. This is not
                 // really needed for a local file, but is the behavior of RemoteVerifiedFile later.
                 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
             }
-            FileConfig::LocalUnverifiedFile(_, _) => {
+            FileConfig::LocalUnverifiedFile(_, _) | FileConfig::RemoteUnverifiedFile(_, _) => {
                 check_access_mode(flags, libc::O_RDONLY)?;
                 // Do not cache the content. This type of file is supposed to be verified using
                 // dm-verity. The filesystem mount over dm-verity already is already cached, so use
@@ -273,6 +283,12 @@
             FileConfig::LocalUnverifiedFile(file, file_size) => {
                 read_chunks(w, file, *file_size, offset, size)
             }
+            FileConfig::RemoteVerifiedFile(file, file_size) => {
+                read_chunks(w, file, *file_size, offset, size)
+            }
+            FileConfig::RemoteUnverifiedFile(file, file_size) => {
+                read_chunks(w, file, *file_size, offset, size)
+            }
         }
     }
 }
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")
 }
diff --git a/authfs/src/remote_file.rs b/authfs/src/remote_file.rs
new file mode 100644
index 0000000..7c3d12e
--- /dev/null
+++ b/authfs/src/remote_file.rs
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use std::convert::TryFrom;
+use std::io;
+use std::io::Write;
+use std::sync::{Arc, Mutex};
+
+use crate::reader::ReadOnlyDataByChunk;
+
+use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService;
+use authfs_aidl_interface::binder::Strong;
+
+type VirtFdService = Strong<dyn IVirtFdService::IVirtFdService>;
+
+pub mod server {
+    // TODO(victorhsieh): use remote binder.
+    pub fn get_local_service() -> super::VirtFdService {
+        let service_name = "authfs_fd_server";
+        authfs_aidl_interface::binder::get_interface(&service_name)
+            .expect("Cannot reach authfs_fd_server binder service")
+    }
+}
+
+pub struct RemoteChunkedFileReader {
+    // This needs to have Sync trait to be used in fuse::worker::start_message_loop.
+    service: Arc<Mutex<VirtFdService>>,
+    file_fd: i32,
+}
+
+impl RemoteChunkedFileReader {
+    pub fn new(service: Arc<Mutex<VirtFdService>>, file_fd: i32) -> Self {
+        RemoteChunkedFileReader { service, file_fd }
+    }
+}
+
+impl ReadOnlyDataByChunk for RemoteChunkedFileReader {
+    fn read_chunk(&self, chunk_index: u64, mut buf: &mut [u8]) -> io::Result<usize> {
+        let offset = i64::try_from(chunk_index * Self::CHUNK_SIZE)
+            .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
+
+        let service = Arc::clone(&self.service);
+        let chunk = service
+            .lock()
+            .unwrap()
+            .readFile(self.file_fd, offset, buf.len() as i32)
+            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.get_description()))?;
+        buf.write(&chunk)
+    }
+}
+
+pub struct RemoteFsverityMerkleTreeReader {
+    // This needs to be a Sync to be used in fuse::worker::start_message_loop.
+    // TODO(victorhsieh): change to Strong<> once binder supports it.
+    service: Arc<Mutex<VirtFdService>>,
+    file_fd: i32,
+}
+
+impl RemoteFsverityMerkleTreeReader {
+    pub fn new(service: Arc<Mutex<VirtFdService>>, file_fd: i32) -> Self {
+        RemoteFsverityMerkleTreeReader { service, file_fd }
+    }
+}
+
+impl ReadOnlyDataByChunk for RemoteFsverityMerkleTreeReader {
+    fn read_chunk(&self, chunk_index: u64, mut buf: &mut [u8]) -> io::Result<usize> {
+        let offset = i64::try_from(chunk_index * Self::CHUNK_SIZE)
+            .map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
+
+        let service = Arc::clone(&self.service);
+        let chunk = service
+            .lock()
+            .unwrap()
+            .readFsverityMerkleTree(self.file_fd, offset, buf.len() as i32)
+            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.get_description()))?;
+        buf.write(&chunk)
+    }
+}
diff --git a/authfs/tools/device-test.sh b/authfs/tools/device-test.sh
new file mode 100755
index 0000000..5cf5f10
--- /dev/null
+++ b/authfs/tools/device-test.sh
@@ -0,0 +1,60 @@
+#!/system/bin/sh
+
+# TODO(victorhsieh): Create a standard Android test for continuous integration.
+#
+# How to run this test:
+#
+# Setup:
+# $ adb push testdata/input.4m* /data/local/tmp
+#
+# Shell 1:
+# $ adb shell 'cd /data/local/tmp && exec 9</system/bin/sh 8<input.4m 7<input.4m.merkle_dump 6<input.4m.fsv_sig 5<input.4m 4<input.4m.merkle_dump.bad 3<input.4m.fsv_sig fd_server --ro-fds 9 --ro-fds 8:7:6 --ro-fds 5:4:3'
+#
+# Shell 2:
+# $ adb push tools/device-test.sh /data/local/tmp/ && adb shell /data/local/tmp/device-test.sh
+
+# Run with -u to enter new namespace.
+if [[ $1 == "-u" ]]; then
+  exec unshare -mUr $0
+fi
+
+cd /data/local/tmp
+
+MOUNTPOINT=/data/local/tmp/authfs
+trap "umount ${MOUNTPOINT}" EXIT;
+mkdir -p ${MOUNTPOINT}
+
+size=$(du -b /system/bin/sh |awk '{print $1}')
+size2=$(du -b input.4m |awk '{print $1}')
+
+echo "Mounting authfs in background ..."
+
+# TODO(170494765): Replace /dev/null (currently not used) with a valid
+# certificate.
+authfs \
+  ${MOUNTPOINT} \
+  --local-verified-file 2:input.4m:input.4m.merkle_dump:input.4m.fsv_sig:/dev/null \
+  --local-verified-file 3:input.4k1:input.4k1.merkle_dump:input.4k1.fsv_sig:/dev/null \
+  --local-verified-file 4:input.4k:input.4k.merkle_dump:input.4k.fsv_sig:/dev/null \
+  --local-unverified-file 5:/system/bin/sh \
+  --remote-unverified-file 6:9:${size} \
+  --remote-verified-file 7:8:${size2}:/dev/null \
+  --remote-verified-file 8:5:${size2}:/dev/null \
+  &
+sleep 0.1
+
+echo "Accessing files in authfs ..."
+md5sum ${MOUNTPOINT}/2 input.4m
+echo
+md5sum ${MOUNTPOINT}/3 input.4k1
+echo
+md5sum ${MOUNTPOINT}/4 input.4k
+echo
+md5sum ${MOUNTPOINT}/5 /system/bin/sh
+md5sum ${MOUNTPOINT}/6
+echo
+md5sum ${MOUNTPOINT}/7 input.4m
+echo
+echo Checking error cases...
+cat /data/local/tmp/authfs/8 2>&1 |grep -q ": I/O error" || echo "Failed to catch the problem"
+echo "Done!"