Merge "Enable --extended-status for crosvm and return reason why VM died."
diff --git a/authfs/src/auth.rs b/authfs/src/auth.rs
deleted file mode 100644
index 729b4d2..0000000
--- a/authfs/src/auth.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2020 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::io;
-
-// TODO(b/170494765): Implement an authenticator to verify a PKCS#7 signature. We only need to
-// verify the signature, not the full certificate chain.
-
-pub trait Authenticator {
-    fn verify(&self, signature: Option<&[u8]>, signed_data: &[u8]) -> io::Result<bool>;
-}
-
-pub struct FakeAuthenticator {
-    should_allow: bool,
-}
-
-#[allow(dead_code)]
-impl FakeAuthenticator {
-    pub fn always_succeed() -> Self {
-        FakeAuthenticator { should_allow: true }
-    }
-
-    pub fn always_fail() -> Self {
-        FakeAuthenticator { should_allow: false }
-    }
-}
-
-impl Authenticator for FakeAuthenticator {
-    fn verify(&self, _signature_pem: Option<&[u8]>, _signed_data: &[u8]) -> io::Result<bool> {
-        Ok(self.should_allow)
-    }
-}
diff --git a/authfs/src/file.rs b/authfs/src/file.rs
index 9bbf3ef..44e60d8 100644
--- a/authfs/src/file.rs
+++ b/authfs/src/file.rs
@@ -8,10 +8,11 @@
 
 use binder::unstable_api::{new_spibinder, AIBinder};
 use binder::FromIBinder;
+use std::convert::TryFrom;
 use std::io;
 use std::path::{Path, MAIN_SEPARATOR};
 
-use crate::common::CHUNK_SIZE;
+use crate::common::{divide_roundup, CHUNK_SIZE};
 use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::IVirtFdService;
 use authfs_aidl_interface::binder::{Status, Strong};
 
@@ -83,3 +84,39 @@
         Err(io::Error::from_raw_os_error(libc::EINVAL))
     }
 }
+
+pub struct EagerChunkReader {
+    buffer: Vec<u8>,
+}
+
+impl EagerChunkReader {
+    pub fn new<F: ReadByChunk>(chunked_file: F, file_size: u64) -> io::Result<EagerChunkReader> {
+        let last_index = divide_roundup(file_size, CHUNK_SIZE);
+        let file_size = usize::try_from(file_size).unwrap();
+        let mut buffer = Vec::with_capacity(file_size);
+        let mut chunk_buffer = [0; CHUNK_SIZE as usize];
+        for index in 0..last_index {
+            let size = chunked_file.read_chunk(index, &mut chunk_buffer)?;
+            buffer.extend_from_slice(&chunk_buffer[..size]);
+        }
+        if buffer.len() < file_size {
+            Err(io::Error::new(
+                io::ErrorKind::InvalidData,
+                format!("Insufficient data size ({} < {})", buffer.len(), file_size),
+            ))
+        } else {
+            Ok(EagerChunkReader { buffer })
+        }
+    }
+}
+
+impl ReadByChunk for EagerChunkReader {
+    fn read_chunk(&self, chunk_index: u64, buf: &mut ChunkBuffer) -> io::Result<usize> {
+        if let Some(chunk) = &self.buffer.chunks(CHUNK_SIZE as usize).nth(chunk_index as usize) {
+            buf[..chunk.len()].copy_from_slice(chunk);
+            Ok(chunk.len())
+        } else {
+            Ok(0) // Read beyond EOF is normal
+        }
+    }
+}
diff --git a/authfs/src/fsverity.rs b/authfs/src/fsverity.rs
index 1515574..61ae928 100644
--- a/authfs/src/fsverity.rs
+++ b/authfs/src/fsverity.rs
@@ -20,5 +20,6 @@
 mod sys;
 mod verifier;
 
+pub use common::merkle_tree_size;
 pub use editor::VerifiedFileEditor;
 pub use verifier::VerifiedFileReader;
diff --git a/authfs/src/fsverity/common.rs b/authfs/src/fsverity/common.rs
index 8889f5c..eba379d 100644
--- a/authfs/src/fsverity/common.rs
+++ b/authfs/src/fsverity/common.rs
@@ -24,8 +24,8 @@
 
 #[derive(Error, Debug)]
 pub enum FsverityError {
-    #[error("Cannot verify a signature")]
-    BadSignature,
+    #[error("Invalid digest")]
+    InvalidDigest,
     #[error("Insufficient data, only got {0}")]
     InsufficientData(usize),
     #[error("Cannot verify a block")]
@@ -52,6 +52,18 @@
     log128_ceil(hash_pages)
 }
 
+/// Returns the size of Merkle tree for `data_size` bytes amount of data.
+pub fn merkle_tree_size(mut data_size: u64) -> u64 {
+    let mut total = 0;
+    while data_size > CHUNK_SIZE {
+        let hash_size = divide_roundup(data_size, CHUNK_SIZE) * Sha256Hasher::HASH_SIZE as u64;
+        let hash_storage_size = divide_roundup(hash_size, CHUNK_SIZE) * CHUNK_SIZE;
+        total += hash_storage_size;
+        data_size = hash_storage_size;
+    }
+    total
+}
+
 pub fn build_fsverity_digest(
     root_hash: &Sha256Hash,
     file_size: u64,
@@ -75,3 +87,22 @@
         .update(&[0u8; 16])? // reserved
         .finalize()
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_merkle_tree_size() {
+        // To produce groundtruth:
+        //   dd if=/dev/zero of=zeros bs=1 count=524289 && \
+        //   fsverity digest --out-merkle-tree=tree zeros && \
+        //   du -b tree
+        assert_eq!(merkle_tree_size(0), 0);
+        assert_eq!(merkle_tree_size(1), 0);
+        assert_eq!(merkle_tree_size(4096), 0);
+        assert_eq!(merkle_tree_size(4097), 4096);
+        assert_eq!(merkle_tree_size(524288), 4096);
+        assert_eq!(merkle_tree_size(524289), 12288);
+    }
+}
diff --git a/authfs/src/fsverity/metadata/Android.bp b/authfs/src/fsverity/metadata/Android.bp
index b155224..af3729f 100644
--- a/authfs/src/fsverity/metadata/Android.bp
+++ b/authfs/src/fsverity/metadata/Android.bp
@@ -18,6 +18,7 @@
     ],
     rustlibs: [
         "libauthfs_fsverity_metadata_bindgen",
+        "libring",
     ],
     edition: "2018",
     apex_available: ["com.android.virt"],
diff --git a/authfs/src/fsverity/metadata/metadata.hpp b/authfs/src/fsverity/metadata/metadata.hpp
index 7bbd3da..f05740e 100644
--- a/authfs/src/fsverity/metadata/metadata.hpp
+++ b/authfs/src/fsverity/metadata/metadata.hpp
@@ -47,6 +47,9 @@
 
 const uint64_t CHUNK_SIZE = 4096;
 
+// Give the macro value a name to export.
+const uint8_t FSVERITY_HASH_ALG_SHA256 = FS_VERITY_HASH_ALG_SHA256;
+
 enum class FSVERITY_SIGNATURE_TYPE : __le32 {
     NONE = 0,
     PKCS7 = 1,
diff --git a/authfs/src/fsverity/metadata/metadata.rs b/authfs/src/fsverity/metadata/metadata.rs
index 0092bee..8bc0617 100644
--- a/authfs/src/fsverity/metadata/metadata.rs
+++ b/authfs/src/fsverity/metadata/metadata.rs
@@ -16,18 +16,31 @@
 
 //! Rust bindgen interface for FSVerity Metadata file (.fsv_meta)
 use authfs_fsverity_metadata_bindgen::{
-    fsverity_metadata_header, FSVERITY_SIGNATURE_TYPE_NONE, FSVERITY_SIGNATURE_TYPE_PKCS7,
-    FSVERITY_SIGNATURE_TYPE_RAW,
+    fsverity_descriptor, fsverity_metadata_header, FSVERITY_HASH_ALG_SHA256,
+    FSVERITY_SIGNATURE_TYPE_NONE, FSVERITY_SIGNATURE_TYPE_PKCS7, FSVERITY_SIGNATURE_TYPE_RAW,
 };
 
+use ring::digest::{Context, SHA256};
 use std::cmp::min;
-use std::os::unix::fs::MetadataExt;
+use std::ffi::OsString;
+use std::fs::File;
+use std::io::{self, Read, Seek};
+use std::mem::{size_of, zeroed};
+use std::os::unix::fs::{FileExt, MetadataExt};
+use std::path::{Path, PathBuf};
+use std::slice::from_raw_parts_mut;
+
+/// Offset of `descriptor` in `struct fsverity_metadatata_header`.
+const DESCRIPTOR_OFFSET: usize = 4;
 
 /// Structure for parsed metadata.
 pub struct FSVerityMetadata {
     /// Header for the metadata.
     pub header: fsverity_metadata_header,
 
+    /// fs-verity digest of the file, with hash algorithm defined in the fs-verity descriptor.
+    pub digest: Vec<u8>,
+
     /// Optional signature for the metadata.
     pub signature: Option<Vec<u8>>,
 
@@ -40,23 +53,20 @@
     /// Read the raw Merkle tree from the metadata, if it exists. The API semantics is similar to a
     /// regular pread(2), and may not return full requested buffer.
     pub fn read_merkle_tree(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
+        let file_size = self.metadata_file.metadata()?.size();
         let start = self.merkle_tree_offset + offset;
-        let end = min(self.metadata_file.metadata()?.size(), start + buf.len() as u64);
+        let end = min(file_size, start + buf.len() as u64);
         let read_size = (end - start) as usize;
         debug_assert!(read_size <= buf.len());
-        self.metadata_file.read_exact_at(&mut buf[..read_size], start)?;
-        Ok(read_size)
+        if read_size == 0 {
+            Ok(0)
+        } else {
+            self.metadata_file.read_exact_at(&mut buf[..read_size], start)?;
+            Ok(read_size)
+        }
     }
 }
 
-use std::ffi::OsString;
-use std::fs::File;
-use std::io::{self, Read, Seek};
-use std::mem::{size_of, zeroed};
-use std::os::unix::fs::FileExt;
-use std::path::{Path, PathBuf};
-use std::slice::from_raw_parts_mut;
-
 /// Common block and page size in Linux.
 pub const CHUNK_SIZE: u64 = authfs_fsverity_metadata_bindgen::CHUNK_SIZE;
 
@@ -70,23 +80,42 @@
 
 /// Parse metadata from given file, and returns a structure for the metadata.
 pub fn parse_fsverity_metadata(mut metadata_file: File) -> io::Result<Box<FSVerityMetadata>> {
-    let header_size = size_of::<fsverity_metadata_header>();
+    let (header, digest) = {
+        // SAFETY: The header doesn't include any pointers.
+        let mut header: fsverity_metadata_header = unsafe { zeroed() };
 
-    // SAFETY: the header doesn't include any pointers
-    let header: fsverity_metadata_header = unsafe {
-        let mut header: fsverity_metadata_header = zeroed();
-        let buffer = from_raw_parts_mut(
-            &mut header as *mut fsverity_metadata_header as *mut u8,
-            header_size,
-        );
-        metadata_file.read_exact(buffer)?;
+        // SAFETY: fsverity_metadata_header is packed, so reading/write from/to the back_buffer
+        // won't overflow.
+        let back_buffer = unsafe {
+            from_raw_parts_mut(
+                &mut header as *mut fsverity_metadata_header as *mut u8,
+                size_of::<fsverity_metadata_header>(),
+            )
+        };
+        metadata_file.read_exact(back_buffer)?;
+
+        // Digest needs to be calculated with the raw value (without changing the endianness).
+        let digest = match header.descriptor.hash_algorithm {
+            FSVERITY_HASH_ALG_SHA256 => {
+                let mut context = Context::new(&SHA256);
+                context.update(
+                    &back_buffer
+                        [DESCRIPTOR_OFFSET..DESCRIPTOR_OFFSET + size_of::<fsverity_descriptor>()],
+                );
+                Ok(context.finish().as_ref().to_owned())
+            }
+            alg => Err(io::Error::new(
+                io::ErrorKind::Other,
+                format!("Unsupported hash algorithm {}, continue (likely failing soon)", alg),
+            )),
+        }?;
 
         // TODO(inseob): This doesn't seem ideal. Maybe we can consider nom?
         header.version = u32::from_le(header.version);
         header.descriptor.data_size = u64::from_le(header.descriptor.data_size);
         header.signature_type = u32::from_le(header.signature_type);
         header.signature_size = u32::from_le(header.signature_size);
-        header
+        (header, digest)
     };
 
     if header.version != 1 {
@@ -108,5 +137,5 @@
     let merkle_tree_offset =
         (metadata_file.stream_position()? + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE;
 
-    Ok(Box::new(FSVerityMetadata { header, signature, metadata_file, merkle_tree_offset }))
+    Ok(Box::new(FSVerityMetadata { header, digest, signature, metadata_file, merkle_tree_offset }))
 }
diff --git a/authfs/src/fsverity/sys.rs b/authfs/src/fsverity/sys.rs
index b3222db..51e10a5 100644
--- a/authfs/src/fsverity/sys.rs
+++ b/authfs/src/fsverity/sys.rs
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-/// Magic used in fs-verity digest
-pub const FS_VERITY_MAGIC: &[u8; 8] = b"FSVerity";
-
 /// fs-verity version that we are using
 pub const FS_VERITY_VERSION: u8 = 1;
 
diff --git a/authfs/src/fsverity/verifier.rs b/authfs/src/fsverity/verifier.rs
index 1add37a..61b8e13 100644
--- a/authfs/src/fsverity/verifier.rs
+++ b/authfs/src/fsverity/verifier.rs
@@ -18,17 +18,12 @@
 use std::io;
 
 use super::common::{build_fsverity_digest, merkle_tree_height, FsverityError};
-use super::sys::{FS_VERITY_HASH_ALG_SHA256, FS_VERITY_MAGIC};
-use crate::auth::Authenticator;
 use crate::common::{divide_roundup, CHUNK_SIZE};
 use crate::crypto::{CryptoError, Sha256Hasher};
 use crate::file::{ChunkBuffer, ReadByChunk};
 
 const ZEROS: [u8; CHUNK_SIZE as usize] = [0u8; CHUNK_SIZE as usize];
 
-// The size of `struct fsverity_formatted_digest` in Linux with SHA-256.
-const SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256: usize = 12 + Sha256Hasher::HASH_SIZE;
-
 type HashBuffer = [u8; Sha256Hasher::HASH_SIZE];
 
 fn hash_with_padding(chunk: &[u8], pad_to: usize) -> Result<HashBuffer, CryptoError> {
@@ -49,6 +44,12 @@
 
     let chunk_hash = hash_with_padding(chunk, CHUNK_SIZE as usize)?;
 
+    // When the file is smaller or equal to CHUNK_SIZE, the root of Merkle tree is defined as the
+    // hash of the file content, plus padding.
+    if file_size <= CHUNK_SIZE {
+        return Ok(chunk_hash);
+    }
+
     fsverity_walk(chunk_index, file_size, merkle_tree)?.try_fold(
         chunk_hash,
         |actual_hash, result| {
@@ -110,21 +111,6 @@
     }))
 }
 
-fn build_fsverity_formatted_digest(
-    root_hash: &HashBuffer,
-    file_size: u64,
-) -> Result<[u8; SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256], CryptoError> {
-    let digest = build_fsverity_digest(root_hash, file_size)?;
-    // Little-endian byte representation of fsverity_formatted_digest from linux/fsverity.h
-    // Not FFI-ed as it seems easier to deal with the raw bytes manually.
-    let mut formatted_digest = [0u8; SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256];
-    formatted_digest[0..8].copy_from_slice(FS_VERITY_MAGIC);
-    formatted_digest[8..10].copy_from_slice(&(FS_VERITY_HASH_ALG_SHA256 as u16).to_le_bytes());
-    formatted_digest[10..12].copy_from_slice(&(Sha256Hasher::HASH_SIZE as u16).to_le_bytes());
-    formatted_digest[12..].copy_from_slice(&digest);
-    Ok(formatted_digest)
-}
-
 pub struct VerifiedFileReader<F: ReadByChunk, M: ReadByChunk> {
     chunked_file: F,
     file_size: u64,
@@ -133,25 +119,28 @@
 }
 
 impl<F: ReadByChunk, M: ReadByChunk> VerifiedFileReader<F, M> {
-    pub fn new<A: Authenticator>(
-        authenticator: &A,
+    pub fn new(
         chunked_file: F,
         file_size: u64,
-        sig: Option<&[u8]>,
+        expected_digest: &[u8],
         merkle_tree: M,
     ) -> Result<VerifiedFileReader<F, M>, FsverityError> {
         let mut buf = [0u8; CHUNK_SIZE as usize];
-        let size = merkle_tree.read_chunk(0, &mut buf)?;
-        if buf.len() != size {
-            return Err(FsverityError::InsufficientData(size));
+        if file_size <= CHUNK_SIZE {
+            let _size = chunked_file.read_chunk(0, &mut buf)?;
+            // The rest of buffer is 0-padded.
+        } else {
+            let size = merkle_tree.read_chunk(0, &mut buf)?;
+            if buf.len() != size {
+                return Err(FsverityError::InsufficientData(size));
+            }
         }
         let root_hash = Sha256Hasher::new()?.update(&buf[..])?.finalize()?;
-        let formatted_digest = build_fsverity_formatted_digest(&root_hash, file_size)?;
-        let valid = authenticator.verify(sig, &formatted_digest)?;
-        if valid {
+        if expected_digest == build_fsverity_digest(&root_hash, file_size)? {
+            // Once verified, use the root_hash for verification going forward.
             Ok(VerifiedFileReader { chunked_file, file_size, merkle_tree, root_hash })
         } else {
-            Err(FsverityError::BadSignature)
+            Err(FsverityError::InvalidDigest)
         }
     }
 }
@@ -172,7 +161,6 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::auth::FakeAuthenticator;
     use crate::file::ReadByChunk;
     use anyhow::Result;
     use authfs_fsverity_metadata::{parse_fsverity_metadata, FSVerityMetadata};
@@ -234,13 +222,11 @@
         let file_reader = LocalFileReader::new(File::open(content_path)?)?;
         let file_size = file_reader.len();
         let metadata = parse_fsverity_metadata(File::open(metadata_path)?)?;
-        let authenticator = FakeAuthenticator::always_succeed();
         Ok((
             VerifiedFileReader::new(
-                &authenticator,
                 file_reader,
                 file_size,
-                metadata.signature.clone().as_deref(),
+                &metadata.digest.clone(),
                 MerkleTreeReader { metadata },
             )?,
             file_size,
@@ -301,21 +287,4 @@
         assert!(file_reader.read_chunk(last_index, &mut buf).is_ok());
         Ok(())
     }
-
-    #[test]
-    fn invalid_signature() -> Result<()> {
-        let authenticator = FakeAuthenticator::always_fail();
-        let file_reader = LocalFileReader::new(File::open("testdata/input.4m")?)?;
-        let file_size = file_reader.len();
-        let metadata = parse_fsverity_metadata(File::open("testdata/input.4m.fsv_meta")?)?;
-        assert!(VerifiedFileReader::new(
-            &authenticator,
-            file_reader,
-            file_size,
-            metadata.signature.clone().as_deref(),
-            MerkleTreeReader { metadata },
-        )
-        .is_err());
-        Ok(())
-    }
 }
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index cbd24a9..03f832d 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -37,8 +37,8 @@
 
 use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
 use crate::file::{
-    validate_basename, Attr, InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor,
-    RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader,
+    validate_basename, Attr, EagerChunkReader, InMemoryDir, RandomWrite, ReadByChunk,
+    RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
 };
 use crate::fsstat::RemoteFsStatsReader;
 use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
@@ -59,7 +59,7 @@
     /// A file type that is verified against fs-verity signature (thus read-only). The file is
     /// served from a remote server.
     VerifiedReadonly {
-        reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
+        reader: VerifiedFileReader<RemoteFileReader, EagerChunkReader>,
         file_size: u64,
     },
     /// A file type that is a read-only passthrough from a file on a remote server.
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 0fa3db7..f664ca2 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -17,15 +17,20 @@
 //! This crate implements AuthFS, a FUSE-based, non-generic filesystem where file access is
 //! authenticated. This filesystem assumes the underlying layer is not trusted, e.g. file may be
 //! provided by an untrusted host/VM, so that the content can't be simply trusted. However, with a
-//! public key from a trusted party, this filesystem can still verify a (read-only) file signed by
-//! the trusted party even if the host/VM as the blob provider is malicious. With the Merkle tree,
-//! each read of file block can be verified individually only when needed.
+//! known file hash from trusted party, this filesystem can still verify a (read-only) file even if
+//! the host/VM as the blob provider is malicious. With the Merkle tree, each read of file block can
+//! be verified individually only when needed.
 //!
-//! AuthFS only serve files that are specifically configured. A file configuration may include the
-//! source (e.g. remote file server), verification method (e.g. certificate for fs-verity
-//! verification, or no verification if expected to mount over dm-verity), and file ID. Regardless
-//! of the actual file name, the exposed file names through AuthFS are currently integer, e.g.
-//! /mountpoint/42.
+//! AuthFS only serve files that are specifically configured. Each remote file can be configured to
+//! appear as a local file at the mount point. A file configuration may include its remote file
+//! identifier and its verification method (e.g. by known digest).
+//!
+//! AuthFS also support remote directories. A remote directory may be defined by a manifest file,
+//! which contains file paths and their corresponding digests.
+//!
+//! AuthFS can also be configured for write, in which case the remote file server is treated as a
+//! (untrusted) storage. The file/directory integrity is maintained in memory in the VM. Currently,
+//! the state is not persistent, thus only new file/directory are supported.
 
 use anyhow::{anyhow, bail, Result};
 use log::error;
@@ -35,7 +40,6 @@
 use std::path::{Path, PathBuf};
 use structopt::StructOpt;
 
-mod auth;
 mod common;
 mod crypto;
 mod file;
@@ -43,12 +47,12 @@
 mod fsverity;
 mod fusefs;
 
-use auth::FakeAuthenticator;
 use file::{
-    Attr, InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader,
+    Attr, EagerChunkReader, InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
+    RemoteMerkleTreeReader,
 };
 use fsstat::RemoteFsStatsReader;
-use fsverity::{VerifiedFileEditor, VerifiedFileReader};
+use fsverity::{merkle_tree_size, VerifiedFileEditor, VerifiedFileReader};
 use fsverity_digests_proto::fsverity_digests::FSVerityDigests;
 use fusefs::{AuthFs, AuthFsEntry};
 
@@ -68,8 +72,9 @@
 
     /// A read-only remote file with integrity check. Can be multiple.
     ///
-    /// For example, `--remote-ro-file 5:/path/to/cert` tells the filesystem to associate the
-    /// file $MOUNTPOINT/5 with a remote FD 5, and need to be verified against the /path/to/cert.
+    /// For example, `--remote-ro-file 5:sha256-1234abcd` tells the filesystem to associate the
+    /// file $MOUNTPOINT/5 with a remote FD 5, and has a fs-verity digest with sha256 of the hex
+    /// value 1234abcd.
     #[structopt(long, parse(try_from_str = parse_remote_ro_file_option))]
     remote_ro_file: Vec<OptionRemoteRoFile>,
 
@@ -122,9 +127,8 @@
     /// ID to refer to the remote file.
     remote_fd: i32,
 
-    /// Certificate to verify the authenticity of the file's fs-verity signature.
-    /// TODO(170494765): Implement PKCS#7 signature verification.
-    _certificate_path: PathBuf,
+    /// Expected fs-verity digest (with sha256) for the remote file.
+    digest: String,
 }
 
 struct OptionRemoteRoDir {
@@ -144,10 +148,11 @@
     if strs.len() != 2 {
         bail!("Invalid option: {}", option);
     }
-    Ok(OptionRemoteRoFile {
-        remote_fd: strs[0].parse::<i32>()?,
-        _certificate_path: PathBuf::from(strs[1]),
-    })
+    if let Some(digest) = strs[1].strip_prefix("sha256-") {
+        Ok(OptionRemoteRoFile { remote_fd: strs[0].parse::<i32>()?, digest: String::from(digest) })
+    } else {
+        bail!("Unsupported hash algorithm or invalid format: {}", strs[1]);
+    }
 }
 
 fn parse_remote_new_ro_dir_option(option: &str) -> Result<OptionRemoteRoDir> {
@@ -162,21 +167,36 @@
     })
 }
 
+fn from_hex_string(s: &str) -> Result<Vec<u8>> {
+    if s.len() % 2 == 1 {
+        bail!("Incomplete hex string: {}", s);
+    } else {
+        let results = (0..s.len())
+            .step_by(2)
+            .map(|i| {
+                u8::from_str_radix(&s[i..i + 2], 16)
+                    .map_err(|e| anyhow!("Cannot parse hex {}: {}", &s[i..i + 2], e))
+            })
+            .collect::<Result<Vec<_>>>();
+        Ok(results?)
+    }
+}
+
 fn new_remote_verified_file_entry(
     service: file::VirtFdService,
     remote_fd: i32,
+    expected_digest: &str,
     file_size: u64,
 ) -> Result<AuthFsEntry> {
-    let signature = service.readFsveritySignature(remote_fd).ok();
-
-    let authenticator = FakeAuthenticator::always_succeed();
     Ok(AuthFsEntry::VerifiedReadonly {
         reader: VerifiedFileReader::new(
-            &authenticator,
             RemoteFileReader::new(service.clone(), remote_fd),
             file_size,
-            signature.as_deref(),
-            RemoteMerkleTreeReader::new(service.clone(), remote_fd),
+            &from_hex_string(expected_digest)?,
+            EagerChunkReader::new(
+                RemoteMerkleTreeReader::new(service.clone(), remote_fd),
+                merkle_tree_size(file_size),
+            )?,
         )?,
         file_size,
     })
@@ -222,6 +242,7 @@
             new_remote_verified_file_entry(
                 service.clone(),
                 config.remote_fd,
+                &config.digest,
                 service.getFileSize(config.remote_fd)?.try_into()?,
             )?,
         )?;
@@ -264,7 +285,11 @@
         // Build the directory tree based on the mapping file.
         let mut reader = File::open(&config.mapping_file_path)?;
         let proto = FSVerityDigests::parse_from_reader(&mut reader)?;
-        for path_str in proto.digests.keys() {
+        for (path_str, digest) in &proto.digests {
+            if digest.hash_alg != "sha256" {
+                bail!("Unsupported hash algorithm: {}", digest.hash_alg);
+            }
+
             let file_entry = {
                 let remote_path_str = path_str.strip_prefix(&config.prefix).ok_or_else(|| {
                     anyhow!("Expect path {} to match prefix {}", path_str, config.prefix)
@@ -275,9 +300,20 @@
                     config.remote_dir_fd,
                     Path::new(remote_path_str),
                 )?;
-                let file_size = service.getFileSize(remote_file.get_remote_fd())?.try_into()?;
-                // TODO(206869687): Switch to VerifiedReadonly
-                AuthFsEntry::UnverifiedReadonly { reader: remote_file, file_size }
+                let remote_fd = remote_file.get_remote_fd();
+                let file_size = service.getFileSize(remote_fd)?.try_into()?;
+                AuthFsEntry::VerifiedReadonly {
+                    reader: VerifiedFileReader::new(
+                        remote_file,
+                        file_size,
+                        &digest.digest,
+                        EagerChunkReader::new(
+                            RemoteMerkleTreeReader::new(service.clone(), remote_fd),
+                            merkle_tree_size(file_size),
+                        )?,
+                    )?,
+                    file_size,
+                }
             };
             authfs.add_entry_at_ro_dir_by_path(dir_root_inode, Path::new(path_str), file_entry)?;
         }
@@ -312,3 +348,18 @@
         std::process::exit(1);
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn parse_hex_string() {
+        assert_eq!(from_hex_string("deadbeef").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
+        assert_eq!(from_hex_string("DEADBEEF").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
+        assert_eq!(from_hex_string("").unwrap(), Vec::<u8>::new());
+
+        assert!(from_hex_string("deadbee").is_err());
+        assert!(from_hex_string("X").is_err());
+    }
+}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index ef544b2..7df3b3e 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -70,12 +70,12 @@
     /** Path to authfs on Microdroid */
     private static final String AUTHFS_BIN = "/system/bin/authfs";
 
-    /** Idsig paths to be created for each APK in the "extra_apks" of vm_config.json. */
+    /** Idsig paths to be created for each APK in the "extra_apks" of vm_config_extra_apk.json. */
     private static final String[] EXTRA_IDSIG_PATHS = new String[] {
         TEST_DIR + "BuildManifest.apk.idsig",
     };
 
-    /** Build manifest path in the VM. 0 is the index of extra_apks in vm_config.json. */
+    /** Build manifest path in the VM. 0 is the index of extra_apks in vm_config_extra_apk.json. */
     private static final String BUILD_MANIFEST_PATH = "/mnt/extra-apk/0/assets/build_manifest.pb";
 
     /** Plenty of time for authfs to get ready */
@@ -84,6 +84,14 @@
     /** FUSE's magic from statfs(2) */
     private static final String FUSE_SUPER_MAGIC_HEX = "65735546";
 
+    // fs-verity digest (sha256) of testdata/input.{4k, 4k1, 4m}
+    private static final String DIGEST_4K =
+            "sha256-9828cd65f4744d6adda216d3a63d8205375be485bfa261b3b8153d3358f5a576";
+    private static final String DIGEST_4K1 =
+            "sha256-3c70dcd4685ed256ebf1ef116c12e472f35b5017eaca422c0483dadd7d0b5a9f";
+    private static final String DIGEST_4M =
+            "sha256-f18a268d565348fb4bbf11f10480b198f98f2922eb711de149857b3cecf98a8d";
+
     private static final int VMADDR_CID_HOST = 2;
 
     private static CommandRunner sAndroid;
@@ -177,7 +185,7 @@
                 "--ro-fds 3:4 --ro-fds 6");
 
         runAuthFsOnMicrodroid(
-                "--remote-ro-file-unverified 6 --remote-ro-file 3:cert.der --cid "
+                "--remote-ro-file-unverified 6 --remote-ro-file 3:" + DIGEST_4M + " --cid "
                         + VMADDR_CID_HOST);
 
         // Action
@@ -201,7 +209,8 @@
                     + " 6:input.4k1 --open-ro 7:input.4k1.fsv_meta",
                 "--ro-fds 3:4 --ro-fds 6:7");
         runAuthFsOnMicrodroid(
-                "--remote-ro-file 3:cert.der --remote-ro-file 6:cert.der --cid " + VMADDR_CID_HOST);
+                "--remote-ro-file 3:" + DIGEST_4K + " --remote-ro-file 6:" + DIGEST_4K1 + " --cid "
+                + VMADDR_CID_HOST);
 
         // Action
         String actualHash4k = computeFileHashOnMicrodroid(MOUNT_DIR + "/3");
@@ -221,7 +230,7 @@
         runFdServerOnAndroid(
                 "--open-ro 3:input.4m --open-ro 4:input.4m.fsv_meta.bad_merkle",
                 "--ro-fds 3:4");
-        runAuthFsOnMicrodroid("--remote-ro-file 3:cert.der --cid " + VMADDR_CID_HOST);
+        runAuthFsOnMicrodroid("--remote-ro-file 3:" + DIGEST_4M + " --cid " + VMADDR_CID_HOST);
 
         // Verify
         assertFalse(copyFileOnMicrodroid(MOUNT_DIR + "/3", "/dev/null"));
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index 093e428..6cdcd85 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -71,8 +71,7 @@
         // TODO: Try to start the current instance with staged APEXes to see if it works?
         let comp_os = self.instance_manager.start_pending_instance().context("Starting CompOS")?;
 
-        // TODO: Write to compos-pending instead
-        let target_dir_name = "test-artifacts".to_owned();
+        let target_dir_name = "compos-pending".to_owned();
         let task = OdrefreshTask::start(comp_os, target_dir_name, callback)?;
 
         Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
index 6ecccd2..bd272a0 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
@@ -25,7 +25,6 @@
 import android.content.pm.StagedApexInfo;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.provider.DeviceConfig;
 import android.util.Log;
 
 import com.android.server.SystemService;
@@ -72,13 +71,6 @@
     }
 
     private static boolean isIsolatedCompilationSupported() {
-        // Check that the relevant experiment is enabled on this device
-        // TODO - Remove this once we are ready for wider use.
-        if (!DeviceConfig.getBoolean(
-                "virtualization_framework_native", "isolated_compilation_enabled", false)) {
-            return false;
-        }
-
         // Check that KVM is enabled on the device
         if (!new File("/dev/kvm").exists()) {
             return false;
diff --git a/microdroid/microdroid.json b/microdroid/microdroid.json
index 9e630f8..c6c743d 100644
--- a/microdroid/microdroid.json
+++ b/microdroid/microdroid.json
@@ -38,5 +38,5 @@
     }
   ],
   "memory_mib": 2048,
-  "protected": true
+  "protected": false
 }
diff --git a/statslog_virtualization/Android.bp b/statslog_virtualization/Android.bp
new file mode 100644
index 0000000..846d12b
--- /dev/null
+++ b/statslog_virtualization/Android.bp
@@ -0,0 +1,67 @@
+//
+// 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.
+
+// Autogenerate the class (and respective headers) with logging methods and constants
+genrule {
+    name: "statslog_virtualization_header.rs",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --module virtualizationservice --minApiLevel 34 --rustHeader $(genDir)/statslog_virtualization_header.rs --rustHeaderCrate statslog_virtualization_rust_header",
+    out: [
+        "statslog_virtualization_header.rs",
+    ],
+}
+
+rust_library {
+    name: "libstatslog_virtualization_rust_header",
+    crate_name: "statslog_virtualization_rust_header",
+    srcs: [
+        "statslog_header_wrapper.rs",
+        ":statslog_virtualization_header.rs",
+    ],
+    rustlibs: [
+        "libstatspull_bindgen",
+        "libthiserror",
+    ],
+    apex_available: [
+        "com.android.virt",
+    ],
+
+}
+
+genrule {
+    name: "statslog_virtualization.rs",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --module virtualizationservice --minApiLevel 34 --rustHeaderCrate statslog_virtualization_rust_header --rust $(genDir)/statslog_virtualization.rs",
+    out: [
+        "statslog_virtualization.rs",
+    ],
+}
+
+rust_library {
+    name: "libstatslog_virtualization_rust",
+    crate_name: "statslog_virtualization_rust",
+    srcs: [
+        "statslog_wrapper.rs",
+        ":statslog_virtualization.rs",
+    ],
+    rustlibs: [
+        "libstatslog_virtualization_rust_header",
+        "libstatspull_bindgen",
+    ],
+    apex_available: [
+        "com.android.virt",
+    ],
+
+}
diff --git a/statslog_virtualization/statslog_header_wrapper.rs b/statslog_virtualization/statslog_header_wrapper.rs
new file mode 100644
index 0000000..39ff51f
--- /dev/null
+++ b/statslog_virtualization/statslog_header_wrapper.rs
@@ -0,0 +1,4 @@
+#![allow(clippy::too_many_arguments)]
+#![allow(missing_docs)]
+
+include!(concat!(env!("OUT_DIR"), "/statslog_virtualization_header.rs"));
diff --git a/statslog_virtualization/statslog_wrapper.rs b/statslog_virtualization/statslog_wrapper.rs
new file mode 100644
index 0000000..4d1a0fa
--- /dev/null
+++ b/statslog_virtualization/statslog_wrapper.rs
@@ -0,0 +1,5 @@
+#![allow(clippy::too_many_arguments)]
+#![allow(missing_docs)]
+#![allow(unused)]
+
+include!(concat!(env!("OUT_DIR"), "/statslog_virtualization.rs"));
diff --git a/tests/benchmark/benchmark_example.sh b/tests/benchmark/benchmark_example.sh
index 49cf258..7ba0c6d 100755
--- a/tests/benchmark/benchmark_example.sh
+++ b/tests/benchmark/benchmark_example.sh
@@ -10,7 +10,7 @@
 # 1. Build needed artifacts, and install it to device
 source build/make/rbesetup.sh
 lunch $1
-m fs_benchmark MicrodroidFilesystemBenchmarkApp fsverity
+m fs_benchmark MicrodroidFilesystemBenchmarkApp fsverity fsverity_metadata_generator
 adb push $OUT/system/bin/fs_benchmark /data/local/tmp
 adb install $OUT/system/app/MicrodroidFilesystemBenchmarkApp/MicrodroidFilesystemBenchmarkApp.apk
 
@@ -20,13 +20,12 @@
 adb shell 'rm -rf /data/local/tmp/virt /data/local/tmp/testcase*'
 adb shell 'mkdir -p /data/local/tmp/virt'
 dd if=/dev/zero of=/tmp/testcase bs=1048576 count=256
-fsverity sign /tmp/testcase /tmp/testcase.fsv_sig --key=packages/modules/Virtualization/tests/benchmark/assets/benchmark.pem \
-    --out-merkle-tree=/tmp/testcase.merkle_dump --cert=packages/modules/Virtualization/tests/benchmark/assets/benchmark.x509.pem
+fsverity_metadata_generator --fsverity-path $(which fsverity) --signature none --hash-alg sha256 --out /tmp/testcase.fsv_meta /tmp/testcase
 adb shell 'dd if=/dev/zero of=/data/local/tmp/testcase bs=1048576 count=256'
-adb push /tmp/testcase.fsv_sig /tmp/testcase.merkle_dump /data/local/tmp
+adb push /tmp/testcase.fsv_meta /data/local/tmp
 
 # 3. Run fd_server from host
-adb shell 'exec 3</data/local/tmp/testcase 4</data/local/tmp/testcase.merkle_dump 5</data/local/tmp/testcase.fsv_sig 6</data/local/tmp/testcase 7<>/data/local/tmp/testcase2 /apex/com.android.virt/bin/fd_server --ro-fds 3:4:5 --ro-fds 6 --rw-fds 7' &
+adb shell 'exec 3</data/local/tmp/testcase 4</data/local/tmp/testcase.fsv_meta 6</data/local/tmp/testcase 7<>/data/local/tmp/testcase2 /apex/com.android.virt/bin/fd_server --ro-fds 3:4 --ro-fds 6 --rw-fds 7' &
 
 # 4. Run VM and get the CID
 result=$(adb shell "/apex/com.android.virt/bin/vm run-app --debug full --daemonize --log /data/local/tmp/virt/log.txt $(adb shell pm path com.android.microdroid.benchmark | cut -d':' -f2) /data/local/tmp/virt/MicrodroidFilesystemBenchmarkApp.apk.idsig /data/local/tmp/virt/instance.img assets/vm_config.json")
@@ -48,7 +47,7 @@
 # 7. Install artifacts and run authfs
 adb -s localhost:8000 push $OUT/system/bin/fs_benchmark /data/local/tmp
 adb -s localhost:8000 shell "mkdir -p /data/local/tmp/authfs"
-adb -s localhost:8000 shell "/system/bin/authfs /data/local/tmp/authfs --cid 2 --remote-ro-file 3:/mnt/apk/assets/benchmark.x509.der --remote-ro-file-unverified 6 --remote-new-rw-file 7" &
+adb -s localhost:8000 shell "/system/bin/authfs /data/local/tmp/authfs --cid 2 --remote-ro-file 3:sha256-$(fsverity digest /tmp/testcase --hash-alg sha256 --compact) --remote-ro-file-unverified 6 --remote-new-rw-file 7" &
 
 # 8. Run guest tests
 echo "Running guest block device read test..."
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index c3ab39a..653524e 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -43,6 +43,7 @@
         "libserde_json",
         "libserde_xml_rs",
         "libshared_child",
+        "libstatslog_virtualization_rust",
         "libvmconfig",
         "libzip",
         "libvsock",
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 5a7322b..42eb1e6 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -54,6 +54,7 @@
 use log::{debug, error, info, warn};
 use microdroid_payload_config::VmPayloadConfig;
 use rustutils::system_properties;
+use statslog_virtualization_rust::vm_creation_requested::{stats_write, Hypervisor};
 use std::convert::TryInto;
 use std::ffi::CStr;
 use std::fs::{create_dir, File, OpenOptions};
@@ -147,6 +148,9 @@
         // Make directory for temporary files.
         let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
         create_dir(&temporary_directory).map_err(|e| {
+            // At this point, we do not know the protected status of Vm
+            // setting it to false, though this may not be correct.
+            write_vm_creation_stats(false, false);
             error!(
                 "Failed to create temporary directory {:?} for VM files: {}",
                 temporary_directory, e
@@ -186,6 +190,9 @@
             VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
                 load_app_config(config, &temporary_directory).map_err(|e| {
                     error!("Failed to load app config from {}: {}", &config.configPath, e);
+                    // At this point, we do not know the protected status of Vm
+                    // setting it to false, though this may not be correct.
+                    write_vm_creation_stats(false, false);
                     new_binder_exception(
                         ExceptionCode::SERVICE_SPECIFIC,
                         format!("Failed to load app config from {}: {}", &config.configPath, e),
@@ -195,6 +202,7 @@
             VirtualMachineConfig::RawConfig(config) => BorrowedOrOwned::Borrowed(config),
         };
         let config = config.as_ref();
+        let protected_vm = config.protectedVm;
 
         // Check if partition images are labeled incorrectly. This is to prevent random images
         // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
@@ -218,6 +226,7 @@
         let zero_filler_path = temporary_directory.join("zero.img");
         write_zero_filler(&zero_filler_path).map_err(|e| {
             error!("Failed to make composite image: {}", e);
+            write_vm_creation_stats(protected_vm, false);
             new_binder_exception(
                 ExceptionCode::SERVICE_SPECIFIC,
                 format!("Failed to make composite image: {}", e),
@@ -283,6 +292,7 @@
             )
             .map_err(|e| {
                 error!("Failed to create VM with config {:?}: {}", config, e);
+                write_vm_creation_stats(protected_vm, false);
                 new_binder_exception(
                     ExceptionCode::SERVICE_SPECIFIC,
                     format!("Failed to create VM: {}", e),
@@ -290,6 +300,7 @@
             })?,
         );
         state.add_vm(Arc::downgrade(&instance));
+        write_vm_creation_stats(protected_vm, true);
         Ok(VirtualMachine::create(instance))
     }
 
@@ -438,6 +449,16 @@
     }
 }
 
+/// Write the stats of VMCreation to statsd
+fn write_vm_creation_stats(protected: bool, success: bool) {
+    match stats_write(Hypervisor::Pkvm, protected, success) {
+        Err(e) => {
+            info!("stastlog_rust fails with error: {}", e);
+        }
+        Ok(_) => info!("stastlog_rust succeeded for virtualization service"),
+    }
+}
+
 /// Waits for incoming connections from VM. If a new connection is made, stores the stream in the
 /// corresponding `VmInstance`.
 fn handle_stream_connection_from_vm(state: Arc<Mutex<State>>) -> Result<()> {