Add notifyError/onError notification for VM errors
For now, microdroid_manager shuts down a VM instance when it fails to
verify the VM payload. This change adds a new "error" notification to
deliver the error to the VM app.
Fow now this doesn't change the workflow. In a follow-up change this
will be changed so that VM app can decide to shutdown or not "onError".
Bug: 205778374
Test: MicrodroidHostTestCases
Change-Id: If7fdac3996b69601be0eda17da3e4cf218b4d1d8
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index f666294..2e6fa36 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -19,7 +19,7 @@
mod payload;
use crate::instance::{ApkData, InstanceDisk, MicrodroidData, RootHash};
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{anyhow, bail, ensure, Context, Error, Result};
use apkverify::{get_public_key_der, verify};
use binder::unstable_api::{new_spibinder, AIBinder};
use binder::{FromIBinder, Strong};
@@ -39,7 +39,7 @@
use vsock::VsockStream;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
- VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
+ ERROR_PAYLOAD_CHANGED, ERROR_PAYLOAD_VERIFICATION_FAILED, ERROR_UNKNOWN, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
};
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
@@ -51,6 +51,27 @@
const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
const LOGD_ENABLED_PROP: &str = "ro.boot.logd.enabled";
+#[derive(thiserror::Error, Debug)]
+enum MicrodroidError {
+ #[error("Payload has changed: {0}")]
+ PayloadChanged(String),
+ #[error("Payload verification has failed: {0}")]
+ PayloadVerificationFailed(String),
+}
+
+fn translate_error(err: &Error) -> (i32, String) {
+ if let Some(e) = err.downcast_ref::<MicrodroidError>() {
+ match e {
+ MicrodroidError::PayloadChanged(msg) => (ERROR_PAYLOAD_CHANGED, msg.to_string()),
+ MicrodroidError::PayloadVerificationFailed(msg) => {
+ (ERROR_PAYLOAD_VERIFICATION_FAILED, msg.to_string())
+ }
+ }
+ } else {
+ (ERROR_UNKNOWN, err.to_string())
+ }
+}
+
fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
// SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
// safely taken by new_spibinder.
@@ -81,6 +102,17 @@
kernlog::init()?;
info!("started.");
+ let service = get_vms_rpc_binder().context("cannot connect to VirtualMachineService")?;
+ if let Err(err) = try_start_payload(&service) {
+ let (error_code, message) = translate_error(&err);
+ service.notifyError(error_code, &message)?;
+ Err(err)
+ } else {
+ Ok(())
+ }
+}
+
+fn try_start_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<()> {
let metadata = load_metadata().context("Failed to load payload metadata")?;
let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
@@ -90,11 +122,13 @@
let verified_data =
verify_payload(&metadata, saved_data.as_ref()).context("Payload verification failed")?;
if let Some(saved_data) = saved_data {
- if saved_data == verified_data {
- info!("Saved data is verified.");
- } else {
- bail!("Detected an update of the payload which isn't supported yet.");
- }
+ ensure!(
+ saved_data == verified_data,
+ MicrodroidError::PayloadChanged(String::from(
+ "Detected an update of the payload which isn't supported yet."
+ ))
+ );
+ info!("Saved data is verified.");
} else {
info!("Saving verified data.");
instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
@@ -103,7 +137,6 @@
// Before reading a file from the APK, start zipfuse
system_properties::write("ctl.start", "zipfuse")?;
- let service = get_vms_rpc_binder().expect("cannot connect to VirtualMachineService");
if !metadata.payload_config_path.is_empty() {
let config = load_config(Path::new(&metadata.payload_config_path))?;
@@ -117,7 +150,7 @@
wait_for_apex_config_done()?;
if let Some(main_task) = &config.task {
- exec_task(main_task, &service).map_err(|e| {
+ exec_task(main_task, service).map_err(|e| {
error!("failed to execute task: {}", e);
e
})?;
@@ -156,7 +189,10 @@
let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
if let Some(saved_data) = saved_data.map(|d| &d.apex_data) {
// We don't support APEX updates. (assuming that update will change root digest)
- ensure!(saved_data == &apex_data_from_payload, "APEX payloads has changed.");
+ ensure!(
+ saved_data == &apex_data_from_payload,
+ MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
+ );
let apex_metadata = to_metadata(&apex_data_from_payload);
// Pass metadata(with public keys and root digests) to apexd so that it uses the passed
// metadata instead of the default one (/dev/block/by-name/payload-metadata)
@@ -178,7 +214,10 @@
// of the VM or APK was updated in the host.
// TODO(jooyung): consider multithreading to make this faster
let apk_pubkey = if !root_hash_trustful {
- verify(DM_MOUNTED_APK_PATH).context(format!("failed to verify {}", DM_MOUNTED_APK_PATH))?
+ verify(DM_MOUNTED_APK_PATH).context(MicrodroidError::PayloadVerificationFailed(format!(
+ "failed to verify {}",
+ DM_MOUNTED_APK_PATH
+ )))?
} else {
get_public_key_der(DM_MOUNTED_APK_PATH)?
};