Merge "Add notifyError/onError notification for VM errors"
This commit is contained in:
commit
f67031972d
|
@ -297,6 +297,12 @@ impl IVirtualMachineCallback for VmCallback {
|
|||
log::warn!("VM payload finished, cid = {}, exit code = {}", cid, exit_code);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn onError(&self, cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
|
||||
self.0.set_died();
|
||||
log::warn!("VM error, cid = {}, error code = {}, message = {}", cid, error_code, message,);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn start_logging(pfd: &ParcelFileDescriptor) -> Result<()> {
|
||||
|
|
|
@ -145,6 +145,14 @@ public:
|
|||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
::ndk::ScopedAStatus onError(int32_t in_cid, int32_t in_error_code,
|
||||
const std::string& in_message) override {
|
||||
// For now, just log the error as onDied() will follow.
|
||||
LOG(WARNING) << "VM error! cid = " << in_cid << ", error_code = " << in_error_code
|
||||
<< ", message = " << in_message;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
::ndk::ScopedAStatus onDied(int32_t in_cid) override {
|
||||
LOG(WARNING) << "VM died! cid = " << in_cid;
|
||||
{
|
||||
|
|
|
@ -262,6 +262,17 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(VirtualMachine vm, int errorCode, String message) {
|
||||
// This check doesn't 100% prevent race condition, but is fine for demo.
|
||||
if (!mService.isShutdown()) {
|
||||
mPayloadOutput.postValue(
|
||||
String.format(
|
||||
"(Error occurred. code: %d, message: %s)",
|
||||
errorCode, message));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDied(VirtualMachine vm) {
|
||||
mService.shutdownNow();
|
||||
|
|
|
@ -400,6 +400,16 @@ public class VirtualMachine {
|
|||
() -> cb.onPayloadFinished(VirtualMachine.this, exitCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int cid, int errorCode, String message) {
|
||||
final VirtualMachineCallback cb = mCallback;
|
||||
if (cb == null) {
|
||||
return;
|
||||
}
|
||||
mCallbackExecutor.execute(
|
||||
() -> cb.onError(VirtualMachine.this, errorCode, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDied(int cid) {
|
||||
final VirtualMachineCallback cb = mCallback;
|
||||
|
|
|
@ -16,10 +16,14 @@
|
|||
|
||||
package android.system.virtualmachine;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Callback interface to get notified with the events from the virtual machine. The methods are
|
||||
* executed on a binder thread. Implementations can make blocking calls in the methods.
|
||||
|
@ -27,6 +31,22 @@ import android.os.ParcelFileDescriptor;
|
|||
* @hide
|
||||
*/
|
||||
public interface VirtualMachineCallback {
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({ERROR_UNKNOWN, ERROR_PAYLOAD_VERIFICATION_FAILED, ERROR_PAYLOAD_CHANGED})
|
||||
@interface ErrorCode {}
|
||||
|
||||
/** Error code for all other errors not listed below. */
|
||||
int ERROR_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* Error code indicating that the payload can't be verified due to various reasons (e.g invalid
|
||||
* merkle tree, invalid formats, etc).
|
||||
*/
|
||||
int ERROR_PAYLOAD_VERIFICATION_FAILED = 1;
|
||||
|
||||
/** Error code indicating that the payload is verified, but has changed since the last boot. */
|
||||
int ERROR_PAYLOAD_CHANGED = 2;
|
||||
|
||||
/** Called when the payload starts in the VM. */
|
||||
void onPayloadStarted(@NonNull VirtualMachine vm, @Nullable ParcelFileDescriptor stream);
|
||||
|
@ -37,6 +57,9 @@ public interface VirtualMachineCallback {
|
|||
/** Called when the payload has finished in the VM. */
|
||||
void onPayloadFinished(@NonNull VirtualMachine vm, int exitCode);
|
||||
|
||||
/** Called when an error occurs in the VM. */
|
||||
void onError(@NonNull VirtualMachine vm, @ErrorCode int errorCode, @NonNull String message);
|
||||
|
||||
/** Called when the VM died. */
|
||||
void onDied(@NonNull VirtualMachine vm);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ rust_defaults {
|
|||
"libserde",
|
||||
"libserde_cbor",
|
||||
"libserde_json",
|
||||
"libthiserror",
|
||||
"libuuid",
|
||||
"libvsock",
|
||||
"librand",
|
||||
|
|
|
@ -19,7 +19,7 @@ mod ioutil;
|
|||
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 std::time::{Duration, SystemTime};
|
|||
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 VMADDR_CID_HOST: u32 = 2;
|
|||
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 @@ fn try_main() -> Result<()> {
|
|||
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 @@ fn try_main() -> Result<()> {
|
|||
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 @@ fn try_main() -> Result<()> {
|
|||
// 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 @@ fn try_main() -> Result<()> {
|
|||
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 @@ fn verify_payload(
|
|||
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 @@ fn verify_payload(
|
|||
// 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)?
|
||||
};
|
||||
|
|
|
@ -110,6 +110,9 @@ public class MicrodroidTests {
|
|||
@Override
|
||||
public void onPayloadFinished(VirtualMachine vm, int exitCode) {}
|
||||
|
||||
@Override
|
||||
public void onError(VirtualMachine vm, int errorCode, String message) {}
|
||||
|
||||
@Override
|
||||
public void onDied(VirtualMachine vm) {}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,11 @@ oneway interface IVirtualMachineCallback {
|
|||
*/
|
||||
void onPayloadFinished(int cid, int exitCode);
|
||||
|
||||
/**
|
||||
* Called when an error occurs in the VM.
|
||||
*/
|
||||
void onError(int cid, int errorCode, in String message);
|
||||
|
||||
/**
|
||||
* Called when the VM dies.
|
||||
*
|
||||
|
|
|
@ -44,5 +44,5 @@ enum VirtualMachineState {
|
|||
/**
|
||||
* The VM has died.
|
||||
*/
|
||||
DEAD = 5,
|
||||
DEAD = 6,
|
||||
}
|
||||
|
|
|
@ -43,4 +43,25 @@ interface IVirtualMachineService {
|
|||
* Notifies that the payload has finished.
|
||||
*/
|
||||
void notifyPayloadFinished(int exitCode);
|
||||
|
||||
/**
|
||||
* Notifies that an error has occurred. See the ERROR_* constants.
|
||||
*/
|
||||
void notifyError(int errorCode, in String message);
|
||||
|
||||
/**
|
||||
* Error code for all other errors not listed below.
|
||||
*/
|
||||
const int ERROR_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* Error code indicating that the payload can't be verified due to various reasons (e.g invalid
|
||||
* merkle tree, invalid formats, etc).
|
||||
*/
|
||||
const int ERROR_PAYLOAD_VERIFICATION_FAILED = 1;
|
||||
|
||||
/**
|
||||
* Error code indicating that the payload is verified, but has changed since the last boot.
|
||||
*/
|
||||
const int ERROR_PAYLOAD_CHANGED = 2;
|
||||
}
|
||||
|
|
|
@ -714,6 +714,16 @@ impl VirtualMachineCallbacks {
|
|||
}
|
||||
}
|
||||
|
||||
/// Call all registered callbacks to say that the VM encountered an error.
|
||||
pub fn notify_error(&self, cid: Cid, error_code: i32, message: &str) {
|
||||
let callbacks = &*self.0.lock().unwrap();
|
||||
for callback in callbacks {
|
||||
if let Err(e) = callback.onError(cid as i32, error_code, message) {
|
||||
error!("Error notifying error event from VM CID {}: {}", cid, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Call all registered callbacks to say that the VM has died.
|
||||
pub fn callback_on_died(&self, cid: Cid) {
|
||||
let callbacks = &*self.0.lock().unwrap();
|
||||
|
@ -879,10 +889,10 @@ impl IVirtualMachineService for VirtualMachineService {
|
|||
vm.callbacks.notify_payload_started(cid, stream);
|
||||
Ok(())
|
||||
} else {
|
||||
error!("notifyPayloadStarted is called from an unknown cid {}", cid);
|
||||
error!("notifyPayloadStarted is called from an unknown CID {}", cid);
|
||||
Err(new_binder_exception(
|
||||
ExceptionCode::SERVICE_SPECIFIC,
|
||||
format!("cannot find a VM with cid {}", cid),
|
||||
format!("cannot find a VM with CID {}", cid),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -896,10 +906,10 @@ impl IVirtualMachineService for VirtualMachineService {
|
|||
vm.callbacks.notify_payload_ready(cid);
|
||||
Ok(())
|
||||
} else {
|
||||
error!("notifyPayloadReady is called from an unknown cid {}", cid);
|
||||
error!("notifyPayloadReady is called from an unknown CID {}", cid);
|
||||
Err(new_binder_exception(
|
||||
ExceptionCode::SERVICE_SPECIFIC,
|
||||
format!("cannot find a VM with cid {}", cid),
|
||||
format!("cannot find a VM with CID {}", cid),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -913,10 +923,27 @@ impl IVirtualMachineService for VirtualMachineService {
|
|||
vm.callbacks.notify_payload_finished(cid, exit_code);
|
||||
Ok(())
|
||||
} else {
|
||||
error!("notifyPayloadFinished is called from an unknown cid {}", cid);
|
||||
error!("notifyPayloadFinished is called from an unknown CID {}", cid);
|
||||
Err(new_binder_exception(
|
||||
ExceptionCode::SERVICE_SPECIFIC,
|
||||
format!("cannot find a VM with cid {}", cid),
|
||||
format!("cannot find a VM with CID {}", cid),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn notifyError(&self, error_code: i32, message: &str) -> binder::Result<()> {
|
||||
let cid = self.cid;
|
||||
if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
|
||||
info!("VM having CID {} encountered an error", cid);
|
||||
vm.update_payload_state(PayloadState::Finished)
|
||||
.map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))?;
|
||||
vm.callbacks.notify_error(cid, error_code, message);
|
||||
Ok(())
|
||||
} else {
|
||||
error!("notifyPayloadStarted is called from an unknown CID {}", cid);
|
||||
Err(new_binder_exception(
|
||||
ExceptionCode::SERVICE_SPECIFIC,
|
||||
format!("cannot find a VM with CID {}", cid),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -271,6 +271,11 @@ impl IVirtualMachineCallback for VirtualMachineCallback {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn onError(&self, _cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
|
||||
eprintln!("VM encountered an error: code={}, message={}", error_code, message);
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue