Merge "Add compos_key_helper"
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index a8abf97..0f60384 100644
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "8149267"
+ build_id: "8160972"
target: "u-boot_pvmfw"
source_file: "pvmfw.img"
}
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 03f832d..84129b6 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -49,7 +49,10 @@
pub type Inode = u64;
type Handle = u64;
-const DEFAULT_METADATA_TIMEOUT: Duration = Duration::from_secs(5);
+/// Maximum time for a file's metadata to be cached by the kernel. Since any file and directory
+/// changes (if not read-only) has to go through AuthFS to be trusted, the timeout can be maximum.
+const DEFAULT_METADATA_TIMEOUT: Duration = Duration::MAX;
+
const ROOT_INODE: Inode = 1;
/// `AuthFsEntry` defines the filesystem entry type supported by AuthFS.
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index ae4a29d..6049991 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -22,12 +22,9 @@
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
-use std::fs::read_dir;
use std::path::{self, Path, PathBuf};
use std::process::Command;
-use crate::artifact_signer::ArtifactSigner;
-use crate::signing_key::DiceSigner;
use authfs_aidl_interface::aidl::com::android::virt::fs::{
AuthFsConfig::{
AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation,
@@ -105,12 +102,15 @@
system_properties::read_bool(name, false).unwrap_or(false)
}
-pub fn odrefresh(
+pub fn odrefresh<F>(
odrefresh_path: &Path,
context: OdrefreshContext,
authfs_service: Strong<dyn IAuthFsService>,
- signer: DiceSigner,
-) -> Result<ExitCode> {
+ success_fn: F,
+) -> Result<ExitCode>
+where
+ F: FnOnce(PathBuf) -> Result<()>,
+{
// Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
// is out of scope.
let authfs_config = AuthFsConfig {
@@ -183,13 +183,8 @@
info!("odrefresh exited with {:?}", exit_code);
if exit_code == ExitCode::CompilationSuccess {
- // authfs only shows us the files we created, so it's ok to just sign everything under
- // the target directory.
let target_dir = art_apex_data.join(context.target_dir_name);
- let mut artifact_signer = ArtifactSigner::new(&target_dir);
- add_artifacts(&target_dir, &mut artifact_signer)?;
-
- artifact_signer.write_info_and_signature(signer, &target_dir.join("compos.info"))?;
+ success_fn(target_dir)?;
}
Ok(exit_code)
@@ -245,24 +240,6 @@
Ok(())
}
-fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
- for entry in
- read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
- {
- let entry = entry?;
- let file_type = entry.file_type()?;
- if file_type.is_dir() {
- add_artifacts(&entry.path(), artifact_signer)?;
- } else if file_type.is_file() {
- artifact_signer.add_artifact(&entry.path())?;
- } else {
- // authfs shouldn't create anything else, but just in case
- bail!("Unexpected file type in artifacts: {:?}", entry);
- }
- }
- Ok(())
-}
-
fn spawn_jailed_task(executable: &Path, args: &[String], env_vars: &[String]) -> Result<Minijail> {
// TODO(b/185175567): Run in a more restricted sandbox.
let jail = Minijail::new()?;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 3ec15dd..5f3ee62 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -18,17 +18,19 @@
//! file descriptors backed by authfs (via authfs_service) and pass the file descriptors to the
//! actual compiler.
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use binder_common::new_binder_exception;
use compos_common::binder::to_binder_result;
use log::warn;
use std::default::Default;
-use std::path::PathBuf;
+use std::fs::read_dir;
+use std::path::{Path, PathBuf};
use std::sync::RwLock;
+use crate::artifact_signer::ArtifactSigner;
use crate::compilation::{odrefresh, OdrefreshContext};
use crate::dice::Dice;
-use crate::signing_key::{DiceSigner, DiceSigningKey};
+use crate::signing_key::DiceSigningKey;
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
use compos_aidl_interface::aidl::com::android::compos::{
CompOsKeyData::CompOsKeyData,
@@ -57,17 +59,6 @@
key_blob: RwLock<Vec<u8>>,
}
-impl CompOsService {
- fn new_signer(&self) -> BinderResult<DiceSigner> {
- let key = &*self.key_blob.read().unwrap();
- if key.is_empty() {
- Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
- } else {
- to_binder_result(self.signing_key.new_signer(key))
- }
- }
-}
-
impl Interface for CompOsService {}
impl ICompOsService for CompOsService {
@@ -91,6 +82,14 @@
zygote_arch: &str,
system_server_compiler_filter: &str,
) -> BinderResult<i8> {
+ let key = &*self.key_blob.read().unwrap();
+ if key.is_empty() {
+ return Err(new_binder_exception(
+ ExceptionCode::ILLEGAL_STATE,
+ "Key is not initialized",
+ ));
+ }
+
let context = to_binder_result(OdrefreshContext::new(
compilation_mode,
system_dir_fd,
@@ -103,8 +102,16 @@
let authfs_service = get_authfs_service()?;
let exit_code = to_binder_result(
- odrefresh(&self.odrefresh_path, context, authfs_service, self.new_signer()?)
- .context("odrefresh failed"),
+ odrefresh(&self.odrefresh_path, context, authfs_service, |output_dir| {
+ // authfs only shows us the files we created, so it's ok to just sign everything
+ // under the output directory.
+ let mut artifact_signer = ArtifactSigner::new(&output_dir);
+ add_artifacts(&output_dir, &mut artifact_signer)?;
+
+ let signer = to_binder_result(self.signing_key.new_signer(key))?;
+ artifact_signer.write_info_and_signature(signer, &output_dir.join("compos.info"))
+ })
+ .context("odrefresh failed"),
)?;
Ok(exit_code as i8)
}
@@ -126,3 +133,21 @@
fn get_authfs_service() -> BinderResult<Strong<dyn IAuthFsService>> {
Ok(authfs_aidl_interface::binder::get_interface(AUTHFS_SERVICE_NAME)?)
}
+
+fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
+ for entry in
+ read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
+ {
+ let entry = entry?;
+ let file_type = entry.file_type()?;
+ if file_type.is_dir() {
+ add_artifacts(&entry.path(), artifact_signer)?;
+ } else if file_type.is_file() {
+ artifact_signer.add_artifact(&entry.path())?;
+ } else {
+ // authfs shouldn't create anything else, but just in case
+ bail!("Unexpected file type in artifacts: {:?}", entry);
+ }
+ }
+ Ok(())
+}
diff --git a/microdroid/dice/android.hardware.security.dice-service.microdroid.rc b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
index 162081e..7d9d441 100644
--- a/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
+++ b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
@@ -1,3 +1,3 @@
service vendor.dice-microdroid /vendor/bin/hw/android.hardware.security.dice-service.microdroid
- class early_hal
- user nobody
+ user diced
+ group diced
diff --git a/microdroid/dice/service.rs b/microdroid/dice/service.rs
index 3401654..8cb5cc3 100644
--- a/microdroid/dice/service.rs
+++ b/microdroid/dice/service.rs
@@ -14,17 +14,103 @@
//! Main entry point for the microdroid IDiceDevice HAL implementation.
-use anyhow::Result;
+use anyhow::{bail, Error, Result};
+use byteorder::{NativeEndian, ReadBytesExt};
use diced::{
dice,
hal_node::{DiceArtifacts, DiceDevice, ResidentHal, UpdatableDiceArtifacts},
};
+use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
use serde::{Deserialize, Serialize};
+use std::fs;
+use std::os::unix::io::AsRawFd;
use std::panic;
+use std::path::{Path, PathBuf};
+use std::ptr::null_mut;
+use std::slice;
use std::sync::Arc;
const DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default";
+/// Artifacts that are mapped into the process address space from the driver.
+struct MappedDriverArtifacts<'a> {
+ mmap_addr: *mut c_void,
+ mmap_size: usize,
+ cdi_attest: &'a [u8; dice::CDI_SIZE],
+ cdi_seal: &'a [u8; dice::CDI_SIZE],
+ bcc: &'a [u8],
+}
+
+impl MappedDriverArtifacts<'_> {
+ fn new(driver_path: &Path) -> Result<Self> {
+ let mut file = fs::File::open(driver_path)
+ .map_err(|error| Error::new(error).context("Opening driver"))?;
+ let mmap_size =
+ file.read_u64::<NativeEndian>()
+ .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
+ // It's safe to map the driver as the service will only create a single
+ // mapping per process.
+ let mmap_addr = unsafe {
+ let fd = file.as_raw_fd();
+ mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
+ };
+ if mmap_addr == MAP_FAILED {
+ bail!("Failed to mmap {:?}", driver_path);
+ }
+ // The slice is created for the region of memory that was just
+ // successfully mapped into the process address space so it will be
+ // accessible and not referenced from anywhere else.
+ let mmap_buf =
+ unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
+ // Very inflexible parsing / validation of the BccHandover data. Assumes deterministically
+ // encoded CBOR.
+ //
+ // BccHandover = {
+ // 1 : bstr .size 32, ; CDI_Attest
+ // 2 : bstr .size 32, ; CDI_Seal
+ // 3 : Bcc, ; Certificate chain
+ // }
+ if mmap_buf[0..4] != [0xa3, 0x01, 0x58, 0x20]
+ || mmap_buf[36..39] != [0x02, 0x58, 0x20]
+ || mmap_buf[71] != 0x03
+ {
+ bail!("BccHandover format mismatch");
+ }
+ Ok(Self {
+ mmap_addr,
+ mmap_size,
+ cdi_attest: mmap_buf[4..36].try_into().unwrap(),
+ cdi_seal: mmap_buf[39..71].try_into().unwrap(),
+ bcc: &mmap_buf[72..],
+ })
+ }
+}
+
+impl Drop for MappedDriverArtifacts<'_> {
+ fn drop(&mut self) {
+ // All references to the mapped region have the same lifetime as self.
+ // Since self is being dropped, so are all the references to the mapped
+ // region meaning its safe to unmap.
+ let ret = unsafe { munmap(self.mmap_addr, self.mmap_size) };
+ if ret != 0 {
+ log::warn!("Failed to munmap ({})", ret);
+ }
+ }
+}
+
+impl DiceArtifacts for MappedDriverArtifacts<'_> {
+ fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] {
+ self.cdi_attest
+ }
+ fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] {
+ self.cdi_seal
+ }
+ fn bcc(&self) -> Vec<u8> {
+ // The BCC only contains public information so it's fine to copy.
+ self.bcc.to_vec()
+ }
+}
+
/// Artifacts that are kept in the process address space after the artifacts
/// from the driver have been consumed.
#[derive(Clone, Serialize, Deserialize)]
@@ -49,19 +135,26 @@
#[derive(Clone, Serialize, Deserialize)]
enum DriverArtifactManager {
+ Driver(PathBuf),
Updated(RawArtifacts),
}
impl DriverArtifactManager {
- fn new() -> Self {
- // TODO(214231981): replace with true values passed by bootloader
- let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
- .expect("Failed to create sample dice artifacts.");
- Self::Updated(RawArtifacts {
- cdi_attest: cdi_attest[..].try_into().unwrap(),
- cdi_seal: cdi_seal[..].try_into().unwrap(),
- bcc,
- })
+ fn new(driver_path: &Path) -> Self {
+ // TODO(218793274): fail if driver is missing in protected mode
+ if driver_path.exists() {
+ log::info!("Using DICE values from driver");
+ Self::Driver(driver_path.to_path_buf())
+ } else {
+ log::warn!("Using sample DICE values");
+ let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+ .expect("Failed to create sample dice artifacts.");
+ Self::Updated(RawArtifacts {
+ cdi_attest: cdi_attest[..].try_into().unwrap(),
+ cdi_seal: cdi_seal[..].try_into().unwrap(),
+ bcc,
+ })
+ }
}
}
@@ -71,10 +164,17 @@
F: FnOnce(&dyn DiceArtifacts) -> Result<T>,
{
match self {
+ Self::Driver(driver_path) => f(&MappedDriverArtifacts::new(driver_path.as_path())?),
Self::Updated(raw_artifacts) => f(raw_artifacts),
}
}
fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> {
+ if let Self::Driver(driver_path) = self {
+ // Writing to the device wipes the artifcates. The string is ignored
+ // by the driver but included for documentation.
+ fs::write(driver_path, "wipe")
+ .map_err(|error| Error::new(error).context("Wiping driver"))?;
+ }
Ok(Self::Updated(RawArtifacts {
cdi_attest: *new_artifacts.cdi_attest(),
cdi_seal: *new_artifacts.cdi_seal(),
@@ -102,7 +202,7 @@
// Safety: ResidentHal cannot be used in multi threaded processes.
// This service does not start a thread pool. The main thread is the only thread
// joining the thread pool, thereby keeping the process single threaded.
- ResidentHal::new(DriverArtifactManager::new())
+ ResidentHal::new(DriverArtifactManager::new(Path::new("/dev/open-dice0")))
}
.expect("Failed to create ResidentHal implementation."),
);
diff --git a/microdroid/payload/mk_payload.cc b/microdroid/payload/mk_payload.cc
index 33e91b9..fd1ce78 100644
--- a/microdroid/payload/mk_payload.cc
+++ b/microdroid/payload/mk_payload.cc
@@ -171,6 +171,7 @@
auto* apex = metadata.add_apexes();
apex->set_name(apex_config.name);
apex->set_partition_name("microdroid-apex-" + std::to_string(apex_index++));
+ apex->set_is_factory(true);
}
if (config.apk.has_value()) {
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index 037b8fc..340a1f7 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -27,3 +27,5 @@
# Virtual console for logcat
/dev/hvc2 0660 logd logd
+
+/dev/open-dice0 0660 diced diced
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 4d65421..267a0e3 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -322,7 +322,6 @@
pub apk_data: ApkData,
pub extra_apks_data: Vec<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 827f9ff..005baf6 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -34,7 +34,6 @@
use log::{error, info};
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 rand::Fill;
use ring::digest;
@@ -42,7 +41,6 @@
use rustutils::system_properties::PropertyWatcher;
use std::convert::TryInto;
use std::fs::{self, create_dir, File, OpenOptions};
-use std::io::BufRead;
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::path::Path;
use std::process::{Child, Command, Stdio};
@@ -308,13 +306,6 @@
) -> 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."))
- );
- }
-
// Verify main APK
let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
@@ -455,7 +446,6 @@
apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
extra_apks_data,
apex_data: apex_data_from_payload,
- bootconfig: get_bootconfig()?.clone().into_boxed_slice(),
})
}
@@ -506,35 +496,6 @@
}
}
-fn get_bootconfig() -> Result<&'static Vec<u8>> {
- static VAL: OnceCell<Vec<u8>> = OnceCell::new();
- VAL.get_or_try_init(|| -> Result<Vec<u8>> {
- let f = File::open("/proc/bootconfig")?;
-
- // Filter-out androidboot.vbmeta.device which contains UUID of the vbmeta partition. That
- // UUID could change everytime when the same VM is started because the composite disk image
- // is ephemeral. A change in UUID is okay as long as other configs (e.g.
- // androidboot.vbmeta.digest) remain same.
- Ok(std::io::BufReader::new(f)
- .lines()
- // note: this try_fold is to early return when we fail to read a line from the file
- .try_fold(Vec::new(), |mut lines, line| {
- line.map(|s| {
- lines.push(s);
- lines
- })
- })?
- .into_iter()
- .filter(|line| {
- let tokens: Vec<&str> = line.splitn(2, '=').collect();
- // note: if `line` doesn't have =, tokens[0] is the entire line.
- tokens[0].trim() != "androidboot.vbmeta.device"
- })
- .flat_map(|line| (line + "\n").into_bytes())
- .collect())
- })
-}
-
fn load_config(path: &Path) -> Result<VmPayloadConfig> {
info!("loading config from {:?}...", path);
let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index c036c76..27c2d2b 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index bc8a4a5..10bcbf4 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -16,5 +16,9 @@
"VirtualizationTestHelper",
],
per_testcase_directory: true,
- data: [":MicrodroidTestApp"],
+ data: [
+ ":MicrodroidTestApp",
+ ":microdroid_general_sepolicy.conf",
+ ],
+ data_native_bins: ["sepolicy-analyze"],
}
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index e15f1ae..e3f1968 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -149,9 +149,26 @@
}
public static CommandResult runOnMicrodroidForResult(String... cmd) {
- final long timeout = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+ final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
return RunUtil.getDefault()
- .runTimedCmd(timeout, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+ .runTimedCmd(timeoutMs, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+ }
+
+ public static void pullMicrodroidFile(String path, File target) {
+ final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+ CommandResult result =
+ RunUtil.getDefault()
+ .runTimedCmd(
+ timeoutMs,
+ "adb",
+ "-s",
+ MICRODROID_SERIAL,
+ "pull",
+ path,
+ target.getPath());
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ fail("pulling " + path + " has failed: " + result);
+ }
}
// Asserts the command will fail on Microdroid.
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 6aa7566..6dacf23 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -17,18 +17,24 @@
package android.virt.test;
import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RunUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.util.Optional;
@RunWith(DeviceJUnit4ClassRunner.class)
@@ -43,6 +49,16 @@
private static final int NUM_VCPUS = 3;
private static final String CPU_AFFINITY = "0,1,2";
+ // TODO(b/176805428): remove this
+ private boolean isCuttlefish() throws Exception {
+ String productName = getDevice().getProperty("ro.product.name");
+ return (null != productName)
+ && (productName.startsWith("aosp_cf_x86")
+ || productName.startsWith("aosp_cf_arm")
+ || productName.startsWith("cf_x86")
+ || productName.startsWith("cf_arm"));
+ }
+
private int minMemorySize() throws DeviceNotAvailableException {
CommandRunner android = new CommandRunner(getDevice());
String abi = android.run("getprop", "ro.product.cpu.abi");
@@ -103,6 +119,34 @@
assertThat(runOnMicrodroid("cat /proc/cpuinfo | grep processor | wc -l"),
is(Integer.toString(NUM_VCPUS)));
+ // Check that selinux is enabled
+ assertThat(runOnMicrodroid("getenforce"), is("Enforcing"));
+
+ // TODO(b/176805428): adb is broken for nested VM
+ if (!isCuttlefish()) {
+ // Check neverallow rules on microdroid
+ File policyFile = FileUtil.createTempFile("microdroid_sepolicy", "");
+ pullMicrodroidFile("/sys/fs/selinux/policy", policyFile);
+
+ File generalPolicyConfFile = findTestFile("microdroid_general_sepolicy.conf");
+ File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze");
+
+ CommandResult result =
+ RunUtil.getDefault()
+ .runTimedCmd(
+ 10000,
+ sepolicyAnalyzeBin.getPath(),
+ policyFile.getPath(),
+ "neverallow",
+ "-w",
+ "-f",
+ generalPolicyConfFile.getPath());
+ assertEquals(
+ "neverallow check failed: " + result.getStderr().trim(),
+ result.getStatus(),
+ CommandStatus.SUCCESS);
+ }
+
shutdownMicrodroid(getDevice(), cid);
}
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 061e8cf..63fdca1 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -38,6 +38,7 @@
import android.system.virtualmachine.VirtualMachineException;
import android.system.virtualmachine.VirtualMachineManager;
+import androidx.annotation.CallSuper;
import androidx.test.core.app.ApplicationProvider;
import com.android.microdroid.testservice.ITestService;
@@ -52,6 +53,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -113,13 +115,7 @@
}
void forceStop(VirtualMachine vm) {
- try {
- vm.stop();
- this.onDied(vm, VirtualMachineCallback.DEATH_REASON_KILLED);
- mExecutorService.shutdown();
- } catch (VirtualMachineException e) {
- throw new RuntimeException(e);
- }
+ this.onDied(vm, VirtualMachineCallback.DEATH_REASON_KILLED);
}
@Override
@@ -135,7 +131,15 @@
public void onError(VirtualMachine vm, int errorCode, String message) {}
@Override
- public void onDied(VirtualMachine vm, @DeathReason int reason) {}
+ @CallSuper
+ public void onDied(VirtualMachine vm, @DeathReason int reason) {
+ try {
+ vm.stop();
+ mExecutorService.shutdown();
+ } catch (VirtualMachineException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
private static final int MIN_MEM_ARM64 = 135;
@@ -213,6 +217,7 @@
public void onDied(VirtualMachine vm, @DeathReason int reason) {
assertTrue(mPayloadReadyCalled);
assertTrue(mPayloadStartedCalled);
+ super.onDied(vm, reason);
}
};
listener.runToFinish(mInner.mVm);
@@ -276,6 +281,7 @@
public void onDied(VirtualMachine vm, @DeathReason int reason) {
assertFalse(mPayloadStarted);
assertTrue(mErrorOccurred);
+ super.onDied(vm, reason);
}
};
listener.runToFinish(mInner.mVm);
@@ -345,4 +351,84 @@
assertThat(vm_secret_second_boot).isNotNull();
assertThat(vm_secret_first_boot).isEqualTo(vm_secret_second_boot);
}
+
+ @Test
+ public void bootFailsWhenInstanceDiskIsCompromised()
+ throws VirtualMachineException, InterruptedException, IOException {
+ assume().withMessage("Skip on Cuttlefish. b/195765441")
+ .that(android.os.Build.DEVICE)
+ .isNotEqualTo("vsoc_x86_64");
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json")
+ .debugLevel(DebugLevel.NONE)
+ .build();
+
+ // Remove any existing VM so we can start from scratch
+ VirtualMachine oldVm = mInner.mVmm.getOrCreate("test_vm_integrity", config);
+ oldVm.delete();
+
+ mInner.mVm = mInner.mVmm.getOrCreate("test_vm_integrity", config);
+
+ VmEventListener listener =
+ new VmEventListener() {
+ private boolean mPayloadReadyCalled = false;
+
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ mPayloadReadyCalled = true;
+ forceStop(vm);
+ }
+
+ @Override
+ public void onDied(VirtualMachine vm, @DeathReason int reason) {
+ assertTrue(mPayloadReadyCalled);
+ super.onDied(vm, reason);
+ }
+ };
+ listener.runToFinish(mInner.mVm);
+
+ // Launch the same VM after flipping a bit of the instance image.
+ // Flip actual data, as flipping trivial bits like the magic string isn't interesting.
+ File vmRoot = new File(mInner.mContext.getFilesDir(), "vm");
+ File vmDir = new File(vmRoot, "test_vm_integrity");
+ File instanceImgPath = new File(vmDir, "instance.img");
+ RandomAccessFile instanceFile = new RandomAccessFile(instanceImgPath, "rw");
+
+ // microdroid data partition starts at 0x60200, actual data at 0x60400, based on experiment
+ // TODO: parse image file (QEMU qcow2) correctly?
+ long headerOffset = 0x60400;
+ instanceFile.seek(headerOffset);
+ int b = instanceFile.readByte();
+ instanceFile.seek(headerOffset);
+ instanceFile.writeByte(b ^ 1);
+ instanceFile.close();
+
+ mInner.mVm = mInner.mVmm.get("test_vm_integrity"); // re-load the vm with new instance disk
+ 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, @DeathReason int reason) {
+ assertFalse(mPayloadStarted);
+ assertTrue(mErrorOccurred);
+ super.onDied(vm, reason);
+ }
+ };
+ listener.runToFinish(mInner.mVm);
+ }
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 7e0c634..89c6e8a 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -29,7 +29,6 @@
IVirtualizationService::IVirtualizationService,
Partition::Partition,
PartitionType::PartitionType,
- VirtualMachineAppConfig::DebugLevel::DebugLevel,
VirtualMachineAppConfig::VirtualMachineAppConfig,
VirtualMachineConfig::VirtualMachineConfig,
VirtualMachineDebugInfo::VirtualMachineDebugInfo,
@@ -131,8 +130,8 @@
) -> binder::Result<Strong<dyn IVirtualMachine>> {
check_manage_access()?;
let state = &mut *self.state.lock().unwrap();
- let mut console_fd = console_fd.map(clone_file).transpose()?;
- let mut log_fd = log_fd.map(clone_file).transpose()?;
+ let console_fd = console_fd.map(clone_file).transpose()?;
+ let log_fd = log_fd.map(clone_file).transpose()?;
let requester_uid = ThreadState::get_calling_uid();
let requester_sid = get_calling_sid()?;
let requester_debug_pid = ThreadState::get_calling_pid();
@@ -163,27 +162,7 @@
)
})?;
- // Disable console logging if debug level != full. Note that kernel anyway doesn't use the
- // console output when debug level != full. So, users won't be able to see the kernel
- // output even without this overriding. This is to silence output from the bootloader which
- // doesn't understand the bootconfig parameters.
- if let VirtualMachineConfig::AppConfig(config) = config {
- if config.debugLevel != DebugLevel::FULL {
- console_fd = None;
- }
- if config.debugLevel == DebugLevel::NONE {
- log_fd = None;
- }
- }
-
let is_app_config = matches!(config, VirtualMachineConfig::AppConfig(_));
- let is_debug_level_full = matches!(
- config,
- VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
- debugLevel: DebugLevel::FULL,
- ..
- })
- );
let config = match config {
VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
@@ -201,14 +180,6 @@
let config = config.as_ref();
let protected = config.protectedVm;
- // Debug level FULL is only supported for non-protected VMs.
- if is_debug_level_full && protected {
- return Err(new_binder_exception(
- ExceptionCode::SERVICE_SPECIFIC,
- "FULL debug level not supported for protected VMs.",
- ));
- };
-
// 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
// being loaded in a pVM. Specifically, for images in the raw config, nothing is allowed