Merge "Add diced to microdroid"
diff --git a/authfs/Android.bp b/authfs/Android.bp
index 471b0cf..ef78d4e 100644
--- a/authfs/Android.bp
+++ b/authfs/Android.bp
@@ -18,10 +18,12 @@
"libbinder_rpc_unstable_bindgen",
"libbinder_rs",
"libcfg_if",
+ "libfsverity_digests_proto_rust",
"libfuse_rust",
"liblibc",
"liblog_rust",
"libnix",
+ "libprotobuf",
"libstructopt",
"libthiserror",
],
diff --git a/authfs/service/src/authfs.rs b/authfs/service/src/authfs.rs
index e1d820a..c941360 100644
--- a/authfs/service/src/authfs.rs
+++ b/authfs/service/src/authfs.rs
@@ -152,7 +152,6 @@
}
for conf in in_dir_fds {
args.push(OsString::from("--remote-ro-dir"));
- // TODO(206869687): Replace /dev/null with the real path when possible.
args.push(OsString::from(format!("{}:{}:{}", conf.fd, conf.manifestPath, conf.prefix)));
}
for conf in out_dir_fds {
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 18b7b51..f664ca2 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -17,23 +17,29 @@
//! 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::{bail, Result};
+use anyhow::{anyhow, bail, Result};
use log::error;
+use protobuf::Message;
use std::convert::TryInto;
+use std::fs::File;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
-mod auth;
mod common;
mod crypto;
mod file;
@@ -41,12 +47,13 @@
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};
#[derive(StructOpt)]
@@ -65,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>,
@@ -93,10 +101,10 @@
/// 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
+ /// 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
+ /// 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>,
@@ -119,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 {
@@ -131,11 +138,9 @@
/// 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(206869687): Really use the file when it's generated.
- #[allow(dead_code)]
mapping_file_path: PathBuf,
- prefix: PathBuf,
+ prefix: String,
}
fn parse_remote_ro_file_option(option: &str) -> Result<OptionRemoteRoFile> {
@@ -143,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> {
@@ -157,25 +163,40 @@
Ok(OptionRemoteRoDir {
remote_dir_fd: strs[0].parse::<i32>().unwrap(),
mapping_file_path: PathBuf::from(strs[1]),
- prefix: PathBuf::from(strs[2]),
+ prefix: String::from(strs[2]),
})
}
+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,
})
@@ -221,6 +242,7 @@
new_remote_verified_file_entry(
service.clone(),
config.remote_fd,
+ &config.digest,
service.getFileSize(config.remote_fd)?.try_into()?,
)?,
)?;
@@ -260,42 +282,40 @@
AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() },
)?;
- // TODO(206869687): Read actual path from config.mapping_file_path when it's generated.
- let paths = vec![
- Path::new("/system/framework/com.android.location.provider.jar"),
- Path::new("/system/framework/ethernet-service.jar"),
- Path::new("/system/framework/ext.jar"),
- Path::new("/system/framework/framework-graphics.jar"),
- Path::new("/system/framework/framework.jar"),
- Path::new("/system/framework/ims-common.jar"),
- Path::new("/system/framework/services.jar"),
- Path::new("/system/framework/services.jar.prof"),
- Path::new("/system/framework/telephony-common.jar"),
- Path::new("/system/framework/voip-common.jar"),
- Path::new("/system/etc/boot-image.prof"),
- Path::new("/system/etc/classpaths/bootclasspath.pb"),
- Path::new("/system/etc/classpaths/systemserverclasspath.pb"),
- Path::new("/system/etc/dirty-image-objects"),
- ];
+ // 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, digest) in &proto.digests {
+ if digest.hash_alg != "sha256" {
+ bail!("Unsupported hash algorithm: {}", digest.hash_alg);
+ }
- for path in &paths {
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)
+ })?;
// 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,
+ 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.strip_prefix("/")?,
- file_entry,
- )?;
+ authfs.add_entry_at_ro_dir_by_path(dir_root_inode, Path::new(path_str), file_entry)?;
}
}
@@ -328,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 819061b..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,28 @@
/** 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_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_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 */
private static final int AUTHFS_INIT_TIMEOUT_MS = 3000;
/** 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;
@@ -111,13 +127,14 @@
CLog.i("Starting the shared VM");
final String apkName = "MicrodroidTestApp.apk";
final String packageName = "com.android.microdroid.test";
- final String configPath = "assets/vm_config.json"; // path inside the APK
+ final String configPath = "assets/vm_config_extra_apk.json"; // path inside the APK
sCid =
startMicrodroid(
androidDevice,
testInfo.getBuildInfo(),
apkName,
packageName,
+ EXTRA_IDSIG_PATHS,
configPath,
/* debug */ true,
/* use default memoryMib */ 0,
@@ -168,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
@@ -192,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");
@@ -212,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"));
@@ -491,9 +509,8 @@
// Setup
String authfsInputDir = MOUNT_DIR + "/3";
runFdServerOnAndroid("--open-dir 3:/system", "--ro-dirs 3");
- // TODO(206869687): 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);
+ runAuthFsOnMicrodroid("--remote-ro-dir 3:" + BUILD_MANIFEST_PATH + ":system/ --cid "
+ + VMADDR_CID_HOST);
// Action
String actualHash =
@@ -509,9 +526,8 @@
// Setup
String authfsInputDir = MOUNT_DIR + "/3";
runFdServerOnAndroid("--open-dir 3:/system", "--ro-dirs 3");
- // TODO(206869687): 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);
+ runAuthFsOnMicrodroid("--remote-ro-dir 3:" + BUILD_MANIFEST_PATH + ":system/ --cid "
+ + VMADDR_CID_HOST);
// Verify
runOnMicrodroid("test -f " + authfsInputDir + "/system/framework/services.jar");
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 4216e1a..46f3020 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -19,6 +19,7 @@
use crate::timeouts::timeouts;
use crate::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT, DEFAULT_VM_CONFIG_PATH};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ DeathReason::DeathReason,
IVirtualMachine::IVirtualMachine,
IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
IVirtualizationService::IVirtualizationService,
@@ -289,9 +290,9 @@
impl Interface for VmCallback {}
impl IVirtualMachineCallback for VmCallback {
- fn onDied(&self, cid: i32) -> BinderResult<()> {
+ fn onDied(&self, cid: i32, reason: DeathReason) -> BinderResult<()> {
self.0.set_died();
- log::warn!("VM died, cid = {}", cid);
+ log::warn!("VM died, cid = {}, reason = {:?}", cid, reason);
Ok(())
}
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 0305cd4..d19b713 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -50,6 +50,7 @@
using namespace std::literals;
using aidl::android::system::virtualizationservice::BnVirtualMachineCallback;
+using aidl::android::system::virtualizationservice::DeathReason;
using aidl::android::system::virtualizationservice::IVirtualizationService;
using aidl::android::system::virtualizationservice::IVirtualMachine;
using aidl::android::system::virtualizationservice::IVirtualMachineCallback;
@@ -163,8 +164,8 @@
return ScopedAStatus::ok();
}
- ::ndk::ScopedAStatus onDied(int32_t in_cid) override {
- LOG(WARNING) << "VM died! cid = " << in_cid;
+ ::ndk::ScopedAStatus onDied(int32_t in_cid, DeathReason reason) override {
+ LOG(WARNING) << "VM died! cid = " << in_cid << " reason = " << static_cast<int>(reason);
{
std::unique_lock lock(mMutex);
mDied = true;
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/compos/src/compilation.rs b/compos/src/compilation.rs
index 4837029..9a23bf5 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -122,9 +122,9 @@
port: FD_SERVER_PORT,
inputDirFdAnnotations: vec![InputDirFdAnnotation {
fd: context.system_dir_fd,
- // TODO(206869687): Replace /dev/null with the real path when possible.
- manifestPath: "/dev/null".to_string(),
- prefix: "/system".to_string(),
+ // 0 is the index of extra_apks in vm_config_extra_apk.json
+ manifestPath: "/mnt/extra-apk/0/assets/build_manifest.pb".to_string(),
+ prefix: "system/".to_string(),
}],
outputDirFdAnnotations: vec![
OutputDirFdAnnotation { fd: context.output_dir_fd },
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
index d7c0058..d59d3d9 100644
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
@@ -41,9 +41,12 @@
/** Wait time for service to be ready on boot */
private static final int READY_LATENCY_MS = 10 * 1000; // 10 seconds
- // Path to compos_key_cmd tool
+ /** Path to compos_key_cmd tool */
private static final String COMPOS_KEY_CMD_BIN = "/apex/com.android.compos/bin/compos_key_cmd";
+ /** Config of the test VM. This is a path inside the APK. */
+ private static final String VM_TEST_CONFIG_PATH = "assets/vm_test_config.json";
+
private String mCid;
@Before
@@ -132,7 +135,7 @@
getBuild(),
/* apkName, no need to install */ null,
packageName,
- "assets/vm_test_config.json",
+ VM_TEST_CONFIG_PATH,
/* debug */ true,
/* use default memoryMib */ 0,
Optional.empty(),
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 1a0e14d..e53f95d 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -274,7 +274,7 @@
}
@Override
- public void onDied(VirtualMachine vm) {
+ public void onDied(VirtualMachine vm, @DeathReason int reason) {
mService.shutdownNow();
mStatus.postValue(VirtualMachine.Status.STOPPED);
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index c2a897b..ecd4491 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -411,12 +411,12 @@
}
@Override
- public void onDied(int cid) {
+ public void onDied(int cid, int reason) {
final VirtualMachineCallback cb = mCallback;
if (cb == null) {
return;
}
- mCallbackExecutor.execute(() -> cb.onDied(VirtualMachine.this));
+ mCallbackExecutor.execute(() -> cb.onDied(VirtualMachine.this, reason));
}
});
service.asBinder()
@@ -426,7 +426,8 @@
public void binderDied() {
final VirtualMachineCallback cb = mCallback;
if (cb != null) {
- cb.onDied(VirtualMachine.this);
+ cb.onDied(VirtualMachine.this, VirtualMachineCallback
+ .DEATH_REASON_VIRTUALIZATIONSERVICE_DIED);
}
}
},
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index 2ddaf30..df3ad37 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -56,6 +56,39 @@
/** Error code indicating that the payload config is invalid. */
int ERROR_PAYLOAD_INVALID_CONFIG = 3;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DEATH_REASON_VIRTUALIZATIONSERVICE_DIED,
+ DEATH_REASON_SHUTDOWN,
+ DEATH_REASON_REBOOT,
+ DEATH_REASON_KILLED,
+ DEATH_REASON_UNKNOWN,
+ DEATH_REASON_INFRASTRUCTURE_ERROR
+ })
+ @interface DeathReason {}
+
+ /**
+ * virtualizationservice itself died, taking the VM down with it. This is a negative number to
+ * avoid conflicting with the other death reasons which match the ones in the AIDL interface.
+ */
+ int DEATH_REASON_VIRTUALIZATIONSERVICE_DIED = -1;
+
+ /** The VM requested to shut down. */
+ int DEATH_REASON_SHUTDOWN = 0;
+
+ /** The VM requested to reboot, possibly as the result of a kernel panic. */
+ int DEATH_REASON_REBOOT = 1;
+
+ /** The VM was killed. */
+ int DEATH_REASON_KILLED = 2;
+
+ /** The VM died for an unknown reason. */
+ int DEATH_REASON_UNKNOWN = 3;
+
+ /** There was an error waiting for the VM. */
+ int DEATH_REASON_INFRASTRUCTURE_ERROR = 4;
+
/** Called when the payload starts in the VM. */
void onPayloadStarted(@NonNull VirtualMachine vm, @Nullable ParcelFileDescriptor stream);
@@ -69,5 +102,5 @@
void onError(@NonNull VirtualMachine vm, @ErrorCode int errorCode, @NonNull String message);
/** Called when the VM died. */
- void onDied(@NonNull VirtualMachine vm);
+ void onDied(@NonNull VirtualMachine vm, @DeathReason int reason);
}
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/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 528f7c2..678fe84 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -33,6 +33,7 @@
import java.io.File;
import java.io.FileNotFoundException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
@@ -187,6 +188,22 @@
Optional<Integer> numCpus,
Optional<String> cpuAffinity)
throws DeviceNotAvailableException {
+ return startMicrodroid(androidDevice, buildInfo, apkName, packageName, null, configPath,
+ debug, memoryMib, numCpus, cpuAffinity);
+ }
+
+ public static String startMicrodroid(
+ ITestDevice androidDevice,
+ IBuildInfo buildInfo,
+ String apkName,
+ String packageName,
+ String[] extraIdsigPaths,
+ String configPath,
+ boolean debug,
+ int memoryMib,
+ Optional<Integer> numCpus,
+ Optional<String> cpuAffinity)
+ throws DeviceNotAvailableException {
CommandRunner android = new CommandRunner(androidDevice);
// Install APK if necessary
@@ -212,20 +229,26 @@
final String debugFlag = debug ? "--debug full" : "";
// Run the VM
- String ret =
- android.run(
- VIRT_APEX + "bin/vm",
- "run-app",
- "--daemonize",
- "--log " + logPath,
- "--mem " + memoryMib,
- numCpus.isPresent() ? "--cpus " + numCpus.get() : "",
- cpuAffinity.isPresent() ? "--cpu-affinity " + cpuAffinity.get() : "",
- debugFlag,
- apkPath,
- outApkIdsigPath,
- instanceImg,
- configPath);
+ ArrayList<String> args = new ArrayList<>(Arrays.asList(
+ VIRT_APEX + "bin/vm",
+ "run-app",
+ "--daemonize",
+ "--log " + logPath,
+ "--mem " + memoryMib,
+ numCpus.isPresent() ? "--cpus " + numCpus.get() : "",
+ cpuAffinity.isPresent() ? "--cpu-affinity " + cpuAffinity.get() : "",
+ debugFlag,
+ apkPath,
+ outApkIdsigPath,
+ instanceImg,
+ configPath));
+ if (extraIdsigPaths != null) {
+ for (String path : extraIdsigPaths) {
+ args.add("--extra-idsig");
+ args.add(path);
+ }
+ }
+ String ret = android.run(args.toArray(new String[0]));
// Redirect log.txt to logd using logwrapper
ExecutorService executor = Executors.newFixedThreadPool(1);
diff --git a/tests/testapk/assets/vm_config_extra_apk.json b/tests/testapk/assets/vm_config_extra_apk.json
new file mode 100644
index 0000000..a5bae63
--- /dev/null
+++ b/tests/testapk/assets/vm_config_extra_apk.json
@@ -0,0 +1,18 @@
+{
+ "os": {
+ "name": "microdroid"
+ },
+ "task": {
+ "type": "microdroid_launcher",
+ "command": "MicrodroidTestNativeLib.so",
+ "args": [
+ "hello",
+ "microdroid"
+ ]
+ },
+ "extra_apks": [
+ {
+ "path": "/system/etc/security/fsverity/BuildManifest.apk"
+ }
+ ]
+}
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 49575be..b03a915 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -105,7 +105,7 @@
void forceStop(VirtualMachine vm) {
try {
vm.stop();
- this.onDied(vm);
+ this.onDied(vm, VirtualMachineCallback.DEATH_REASON_KILLED);
mExecutorService.shutdown();
} catch (VirtualMachineException e) {
throw new RuntimeException(e);
@@ -125,7 +125,7 @@
public void onError(VirtualMachine vm, int errorCode, String message) {}
@Override
- public void onDied(VirtualMachine vm) {}
+ public void onDied(VirtualMachine vm, @DeathReason int reason) {}
}
private static final int MIN_MEM_ARM64 = 135;
@@ -166,7 +166,7 @@
}
@Override
- public void onDied(VirtualMachine vm) {
+ public void onDied(VirtualMachine vm, @DeathReason int reason) {
assertTrue(mPayloadReadyCalled);
assertTrue(mPayloadStartedCalled);
}
@@ -227,7 +227,7 @@
}
@Override
- public void onDied(VirtualMachine vm) {
+ public void onDied(VirtualMachine vm, @DeathReason int reason) {
assertFalse(mPayloadStarted);
assertTrue(mErrorOccurred);
}
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/aidl/android/system/virtualizationservice/DeathReason.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl
new file mode 100644
index 0000000..2f454a9
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2022 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.
+ */
+package android.system.virtualizationservice;
+
+/**
+ * The reason why a VM died.
+ */
+@Backing(type="int")
+enum DeathReason {
+ /** The VM requested to shut down. */
+ SHUTDOWN = 0,
+ /** The VM requested to reboot, possibly as the result of a kernel panic. */
+ REBOOT = 1,
+ /** The VM was killed. */
+ KILLED = 2,
+ /** The VM died for an unknown reason. */
+ UNKNOWN = 3,
+ /** There was an error waiting for the VM. */
+ INFRASTRUCTURE_ERROR = 4,
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index d7f90a1..12a056c 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -15,7 +15,7 @@
*/
package android.system.virtualizationservice;
-import android.system.virtualizationservice.IVirtualMachine;
+import android.system.virtualizationservice.DeathReason;
/**
* An object which a client may register with the VirtualizationService to get callbacks about the
@@ -52,5 +52,5 @@
* Note that this will not be called if the VirtualizationService itself dies, so you should
* also use `link_to_death` to handle that.
*/
- void onDied(int cid);
+ void onDied(int cid, in DeathReason reason);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index c264270..42eb1e6 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -22,6 +22,7 @@
use ::binder::unstable_api::AsNative;
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ DeathReason::DeathReason,
DiskImage::DiskImage,
IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
@@ -53,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};
@@ -146,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
@@ -185,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),
@@ -194,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
@@ -217,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),
@@ -282,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),
@@ -289,6 +300,7 @@
})?,
);
state.add_vm(Arc::downgrade(&instance));
+ write_vm_creation_stats(protected_vm, true);
Ok(VirtualMachine::create(instance))
}
@@ -437,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<()> {
@@ -790,10 +812,10 @@
}
/// Call all registered callbacks to say that the VM has died.
- pub fn callback_on_died(&self, cid: Cid) {
+ pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
let callbacks = &*self.0.lock().unwrap();
for callback in callbacks {
- if let Err(e) = callback.onDied(cid as i32) {
+ if let Err(e) = callback.onDied(cid as i32, reason) {
error!("Error notifying exit of VM CID {}: {}", cid, e);
}
}
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 0b1429c..76f4f47 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -21,19 +21,24 @@
use log::{debug, error, info};
use shared_child::SharedChild;
use std::fs::{remove_dir_all, File};
+use std::io;
use std::mem;
use std::num::NonZeroU32;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf;
-use std::process::Command;
+use std::process::{Command, ExitStatus};
use std::sync::{Arc, Mutex};
use std::thread;
use vsock::VsockStream;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DeathReason::DeathReason;
use android_system_virtualmachineservice::binder::Strong;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
+/// The exit status which crosvm returns when a VM requests a reboot.
+const CROSVM_REBOOT_STATUS: i32 = 32;
+
/// Configuration for a VM to run with crosvm.
#[derive(Debug)]
pub struct CrosvmConfig {
@@ -182,7 +187,8 @@
/// This takes a separate reference to the `SharedChild` rather than using the one in
/// `self.vm_state` to avoid holding the lock on `vm_state` while it is running.
fn monitor(&self, child: Arc<SharedChild>) {
- match child.wait() {
+ let result = child.wait();
+ match &result {
Err(e) => error!("Error waiting for crosvm({}) instance to die: {}", child.id(), e),
Ok(status) => info!("crosvm({}) exited with status {}", child.id(), status),
}
@@ -192,7 +198,7 @@
// Ensure that the mutex is released before calling the callbacks.
drop(vm_state);
- self.callbacks.callback_on_died(self.cid);
+ self.callbacks.callback_on_died(self.cid, death_reason(&result));
// Delete temporary files.
if let Err(e) = remove_dir_all(&self.temporary_directory) {
@@ -232,13 +238,31 @@
}
}
+fn death_reason(result: &Result<ExitStatus, io::Error>) -> DeathReason {
+ if let Ok(status) = result {
+ match status.code() {
+ None => DeathReason::KILLED,
+ Some(0) => DeathReason::SHUTDOWN,
+ Some(CROSVM_REBOOT_STATUS) => DeathReason::REBOOT,
+ Some(_) => DeathReason::UNKNOWN,
+ }
+ } else {
+ DeathReason::INFRASTRUCTURE_ERROR
+ }
+}
+
/// Starts an instance of `crosvm` to manage a new VM.
fn run_vm(config: CrosvmConfig) -> Result<SharedChild, Error> {
validate_config(&config)?;
let mut command = Command::new(CROSVM_PATH);
// TODO(qwandor): Remove --disable-sandbox.
- command.arg("run").arg("--disable-sandbox").arg("--cid").arg(config.cid.to_string());
+ command
+ .arg("--extended-status")
+ .arg("run")
+ .arg("--disable-sandbox")
+ .arg("--cid")
+ .arg(config.cid.to_string());
if config.protected {
command.arg("--protected-vm");
diff --git a/vm/src/main.rs b/vm/src/main.rs
index a466a4c..ad8c201 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -86,7 +86,7 @@
cpu_affinity: Option<String>,
/// Paths to extra idsig files.
- #[structopt(long)]
+ #[structopt(long = "extra-idsig")]
extra_idsigs: Vec<PathBuf>,
},
/// Run a virtual machine
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 19982ea..8583fe2 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -17,7 +17,8 @@
use crate::create_partition::command_create_partition;
use crate::sync::AtomicFlag;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
- IVirtualMachine::IVirtualMachine, IVirtualMachineCallback::BnVirtualMachineCallback,
+ DeathReason::DeathReason, IVirtualMachine::IVirtualMachine,
+ IVirtualMachineCallback::BnVirtualMachineCallback,
IVirtualMachineCallback::IVirtualMachineCallback,
IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
VirtualMachineAppConfig::DebugLevel::DebugLevel,
@@ -286,13 +287,17 @@
Ok(())
}
- fn onDied(&self, _cid: i32) -> BinderResult<()> {
- // No need to explicitly report the event to the user (e.g. via println!) because this
- // callback is registered only when the vm tool is invoked as interactive mode (e.g. not
- // --daemonize) in which case the tool will exit to the shell prompt upon VM shutdown.
- // Printing something will actually even confuse the user as the output from the app
- // payload is printed.
+ fn onDied(&self, _cid: i32, reason: DeathReason) -> BinderResult<()> {
self.dead.raise();
+
+ match reason {
+ DeathReason::SHUTDOWN => println!("VM shutdown cleanly."),
+ DeathReason::REBOOT => println!("VM tried to reboot, possibly due to a kernel panic."),
+ DeathReason::KILLED => println!("VM was killed."),
+ DeathReason::UNKNOWN => println!("VM died for an unknown reason."),
+ DeathReason::INFRASTRUCTURE_ERROR => println!("Error waiting for VM to finish."),
+ _ => println!("VM died for an unrecognised reason."),
+ }
Ok(())
}
}