Merge "bootconfig is part of VM identity"
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index f427966..c69d875 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -24,6 +24,7 @@
"libmicrodroid_metadata",
"libmicrodroid_payload_config",
"libnix",
+ "libonce_cell",
"libprotobuf",
"libring",
"librustutils",
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 47230e3..8ba6f51 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -315,6 +315,7 @@
pub struct MicrodroidData {
pub apk_data: ApkData,
pub apex_data: Vec<ApexData>,
+ pub bootconfig: Box<[u8]>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 93a0759..efe6126 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -27,6 +27,7 @@
use log::{error, info, warn};
use microdroid_metadata::{write_metadata, Metadata};
use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
+use once_cell::sync::OnceCell;
use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
use rustutils::system_properties;
use rustutils::system_properties::PropertyWatcher;
@@ -228,6 +229,13 @@
) -> Result<MicrodroidData> {
let start_time = SystemTime::now();
+ if let Some(saved_bootconfig) = saved_data.map(|d| &d.bootconfig) {
+ ensure!(
+ saved_bootconfig.as_ref() == get_bootconfig()?.as_slice(),
+ MicrodroidError::PayloadChanged(String::from("Bootconfig has changed."))
+ );
+ }
+
let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
let root_hash_from_idsig = get_apk_root_hash_from_idsig()?;
let root_hash_trustful = root_hash == Some(&root_hash_from_idsig);
@@ -288,6 +296,7 @@
Ok(MicrodroidData {
apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: apk_pubkey },
apex_data: apex_data_from_payload,
+ bootconfig: get_bootconfig()?.clone().into_boxed_slice(),
})
}
@@ -310,6 +319,13 @@
Ok(idsig.hashing_info.raw_root_hash)
}
+fn get_bootconfig() -> Result<&'static Vec<u8>> {
+ static VAL: OnceCell<Vec<u8>> = OnceCell::new();
+ VAL.get_or_try_init(|| {
+ fs::read("/proc/bootconfig").context("Failed to read bootconfig")
+ })
+}
+
fn load_config(path: &Path) -> Result<VmPayloadConfig> {
info!("loading config from {:?}...", path);
let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
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 61c3edc..0e99745 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -15,14 +15,21 @@
*/
package com.android.microdroid.test;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeThat;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
import android.system.virtualmachine.VirtualMachineException;
import android.system.virtualmachine.VirtualMachineManager;
@@ -36,6 +43,9 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -148,4 +158,65 @@
};
listener.runToFinish(mInner.mVm);
}
+
+ @Test
+ public void changingDebugLevelInvalidatesVmIdentity()
+ throws VirtualMachineException, InterruptedException, IOException {
+ assumeThat("Skip on Cuttlefish. b/195765441",
+ android.os.Build.DEVICE, is(not("vsoc_x86_64")));
+
+ VirtualMachineConfig.Builder builder =
+ new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
+ VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+ mInner.mVm = mInner.mVmm.getOrCreate("test_vm", normalConfig);
+ VmEventListener listener =
+ new VmEventListener() {
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ // TODO(b/208639280): remove this sleep. For now, we need to wait for a few
+ // seconds so that crosvm can actually persist instance.img.
+ try {
+ Thread.sleep(30 * 1000);
+ } catch (InterruptedException e) { }
+ forceStop(vm);
+ }
+ };
+ listener.runToFinish(mInner.mVm);
+
+ // Launch the same VM with different debug level. The Java API prohibits this (thankfully).
+ // For testing, we do that by creating another VM with debug level, and copy the config file
+ // from the new VM directory to the old VM directory.
+ VirtualMachineConfig debugConfig = builder.debugLevel(DebugLevel.FULL).build();
+ VirtualMachine newVm = mInner.mVmm.getOrCreate("test_debug_vm", debugConfig);
+ File vmRoot = new File(mInner.mContext.getFilesDir(), "vm");
+ File newVmConfig = new File(new File(vmRoot, "test_debug_vm"), "config.xml");
+ File oldVmConfig = new File(new File(vmRoot, "test_vm"), "config.xml");
+ Files.copy(newVmConfig.toPath(), oldVmConfig.toPath(), REPLACE_EXISTING);
+ newVm.delete();
+ mInner.mVm = mInner.mVmm.get("test_vm"); // re-load with the copied-in config file.
+ listener =
+ new VmEventListener() {
+ private boolean mPayloadStarted = false;
+ private boolean mErrorOccurred = false;
+
+ @Override
+ public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+ mPayloadStarted = true;
+ forceStop(vm);
+ }
+
+ @Override
+ public void onError(VirtualMachine vm, int errorCode, String message) {
+ mErrorOccurred = true;
+ forceStop(vm);
+ }
+
+ @Override
+ public void onDied(VirtualMachine vm) {
+ assertFalse(mPayloadStarted);
+ assertTrue(mErrorOccurred);
+ }
+ };
+ listener.runToFinish(mInner.mVm);
+ }
}