Merge "Use ro.board.api_level to check whether BSP is under VSR-15 or not" into main
diff --git a/apex/sign_virt_apex_test.sh b/apex/sign_virt_apex_test.sh
index e6a892b..e4ac615 100644
--- a/apex/sign_virt_apex_test.sh
+++ b/apex/sign_virt_apex_test.sh
@@ -25,12 +25,16 @@
DEBUGFS=$TEST_DIR/debugfs_static
FSCKEROFS=$TEST_DIR/fsck.erofs
+echo "Extracting the virt apex ..."
deapexer --debugfs_path $DEBUGFS --fsckerofs_path $FSCKEROFS \
extract $TEST_DIR/com.android.virt.apex $TMP_ROOT
if [ "$(ls -A $TMP_ROOT/etc/fs/)" ]; then
- sign_virt_apex $TEST_DIR/test.com.android.virt.pem $TMP_ROOT
- sign_virt_apex --verify $TEST_DIR/test.com.android.virt.pem $TMP_ROOT
+ echo "Re-signing the contents ..."
+ sign_virt_apex -v $TEST_DIR/test.com.android.virt.pem $TMP_ROOT
+ echo "Verifying the contents ..."
+ sign_virt_apex -v --verify $TEST_DIR/test.com.android.virt.pem $TMP_ROOT
+ echo "Done."
else
echo "No filesystem images. Skip."
fi
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 6914380..107f8d0 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -36,7 +36,6 @@
use glob::glob;
use log::{info, warn};
use platformproperties::hypervisorproperties;
-use rustutils::system_properties;
use std::fs::File;
use std::path::{Path, PathBuf};
use vmclient::{DeathReason, ErrorCode, VmInstance, VmWaitError};
@@ -80,7 +79,11 @@
idsig_manifest_ext_apk: &Path,
parameters: &VmParameters,
) -> Result<Self> {
- let protected_vm = want_protected_vm()?;
+ let have_protected_vm =
+ hypervisorproperties::hypervisor_protected_vm_supported()?.unwrap_or(false);
+ if !have_protected_vm {
+ bail!("Protected VM not supported, unable to start VM");
+ }
let instance_fd = ParcelFileDescriptor::new(instance_image);
@@ -133,7 +136,7 @@
payload: Payload::ConfigPath(config_path),
debugLevel: debug_level,
extraIdsigs: extra_idsigs,
- protectedVm: protected_vm,
+ protectedVm: true,
memoryMib: parameters.memory_mib.unwrap_or(0), // 0 means use the default
cpuTopology: cpu_topology,
customConfig: custom_config,
@@ -236,28 +239,6 @@
Ok(idsig_fd)
}
-fn want_protected_vm() -> Result<bool> {
- let have_protected_vm =
- hypervisorproperties::hypervisor_protected_vm_supported()?.unwrap_or(false);
- if have_protected_vm {
- info!("Starting protected VM");
- return Ok(true);
- }
-
- let is_debug_build = system_properties::read("ro.debuggable")?.as_deref().unwrap_or("0") == "1";
- if !is_debug_build {
- bail!("Protected VM not supported, unable to start VM");
- }
-
- let have_non_protected_vm = hypervisorproperties::hypervisor_vm_supported()?.unwrap_or(false);
- if have_non_protected_vm {
- warn!("Protected VM not supported, falling back to non-protected on debuggable build");
- return Ok(false);
- }
-
- bail!("No VM support available")
-}
-
struct Callback {}
impl vmclient::VmCallback for Callback {
fn on_payload_started(&self, cid: i32) {
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
index 66d0f4b..4d3bf2d 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -90,6 +90,7 @@
private static final String KEY_PROTECTED_VM = "protectedVm";
private static final String KEY_MEMORY_BYTES = "memoryBytes";
private static final String KEY_CPU_TOPOLOGY = "cpuTopology";
+ private static final String KEY_CONSOLE_INPUT_DEVICE = "consoleInputDevice";
private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes";
private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured";
private static final String KEY_VM_CONSOLE_INPUT_SUPPORTED = "vmConsoleInputSupported";
@@ -173,6 +174,9 @@
/** CPU topology configuration of the VM. */
@CpuTopology private final int mCpuTopology;
+ /** The serial device for VM console input. */
+ @Nullable private final String mConsoleInputDevice;
+
/**
* Path within the APK to the payload config file that defines software aspects of the VM.
*/
@@ -229,6 +233,7 @@
boolean protectedVm,
long memoryBytes,
@CpuTopology int cpuTopology,
+ @Nullable String consoleInputDevice,
long encryptedStorageBytes,
boolean vmOutputCaptured,
boolean vmConsoleInputSupported,
@@ -250,6 +255,7 @@
mProtectedVm = protectedVm;
mMemoryBytes = memoryBytes;
mCpuTopology = cpuTopology;
+ mConsoleInputDevice = consoleInputDevice;
mEncryptedStorageBytes = encryptedStorageBytes;
mVmOutputCaptured = vmOutputCaptured;
mVmConsoleInputSupported = vmConsoleInputSupported;
@@ -330,6 +336,10 @@
builder.setMemoryBytes(memoryBytes);
}
builder.setCpuTopology(b.getInt(KEY_CPU_TOPOLOGY));
+ String consoleInputDevice = b.getString(KEY_CONSOLE_INPUT_DEVICE);
+ if (consoleInputDevice != null) {
+ builder.setConsoleInputDevice(consoleInputDevice);
+ }
long encryptedStorageBytes = b.getLong(KEY_ENCRYPTED_STORAGE_BYTES);
if (encryptedStorageBytes != 0) {
builder.setEncryptedStorageBytes(encryptedStorageBytes);
@@ -382,6 +392,9 @@
b.putInt(KEY_DEBUGLEVEL, mDebugLevel);
b.putBoolean(KEY_PROTECTED_VM, mProtectedVm);
b.putInt(KEY_CPU_TOPOLOGY, mCpuTopology);
+ if (mConsoleInputDevice != null) {
+ b.putString(KEY_CONSOLE_INPUT_DEVICE, mConsoleInputDevice);
+ }
if (mMemoryBytes > 0) {
b.putLong(KEY_MEMORY_BYTES, mMemoryBytes);
}
@@ -595,6 +608,7 @@
&& this.mVmOutputCaptured == other.mVmOutputCaptured
&& this.mVmConsoleInputSupported == other.mVmConsoleInputSupported
&& this.mConnectVmConsole == other.mConnectVmConsole
+ && this.mConsoleInputDevice == other.mConsoleInputDevice
&& (this.mVendorDiskImage == null) == (other.mVendorDiskImage == null)
&& Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
&& Objects.equals(this.mPayloadBinaryName, other.mPayloadBinaryName)
@@ -666,6 +680,7 @@
config.protectedVm = this.mProtectedVm;
config.memoryMib = bytesToMebiBytes(mMemoryBytes);
config.cpuTopology = (byte) this.mCpuTopology;
+ config.consoleInputDevice = mConsoleInputDevice;
config.devices = EMPTY_STRING_ARRAY;
config.platformVersion = "~1.0";
return config;
@@ -804,6 +819,7 @@
private boolean mProtectedVmSet;
private long mMemoryBytes;
@CpuTopology private int mCpuTopology = CPU_TOPOLOGY_ONE_CPU;
+ @Nullable private String mConsoleInputDevice;
private long mEncryptedStorageBytes;
private boolean mVmOutputCaptured = false;
private boolean mVmConsoleInputSupported = false;
@@ -897,6 +913,7 @@
mProtectedVm,
mMemoryBytes,
mCpuTopology,
+ mConsoleInputDevice,
mEncryptedStorageBytes,
mVmOutputCaptured,
mVmConsoleInputSupported,
@@ -1080,6 +1097,17 @@
}
/**
+ * Sets the serial device for VM console input.
+ *
+ * @see android.system.virtualizationservice.ConsoleInputDevice
+ * @hide
+ */
+ public Builder setConsoleInputDevice(@Nullable String consoleInputDevice) {
+ mConsoleInputDevice = consoleInputDevice;
+ return this;
+ }
+
+ /**
* Sets the size (in bytes) of encrypted storage available to the VM. If not set, no
* encrypted storage is provided.
*
diff --git a/libs/vmconfig/src/lib.rs b/libs/vmconfig/src/lib.rs
index 7c917b0..1413b51 100644
--- a/libs/vmconfig/src/lib.rs
+++ b/libs/vmconfig/src/lib.rs
@@ -65,6 +65,8 @@
/// SysFS paths of devices assigned to the VM.
#[serde(default)]
pub devices: Vec<PathBuf>,
+ /// The serial device for VM console input.
+ pub console_input_device: Option<String>,
}
impl VmConfig {
@@ -124,6 +126,7 @@
x.to_str().map(String::from).ok_or(anyhow!("Failed to convert {x:?} to String"))
})
.collect::<Result<_>>()?,
+ consoleInputDevice: self.console_input_device.clone(),
..Default::default()
})
}
diff --git a/microdroid/microdroid.json b/microdroid/microdroid.json
index 00cedc8..e60c4ca 100644
--- a/microdroid/microdroid.json
+++ b/microdroid/microdroid.json
@@ -16,5 +16,6 @@
}
],
"memory_mib": 256,
+ "console_input_device": "hvc0",
"platform_version": "~1.0"
}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 769a955..144e81e 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -117,9 +117,10 @@
rustlibs: [
"libcbor_util",
"libciborium",
- "libdiced_open_dice_nostd",
+ "libdiced_open_dice",
"libpvmfw_avb_nostd",
"libzerocopy_nostd",
+ "libhex",
],
}
@@ -320,15 +321,22 @@
installable: false,
}
-prebuilt_etc {
+filegroup {
name: "pvmfw_embedded_key",
- src: ":avb_testkey_rsa4096_pub_bin",
- installable: false,
+ srcs: [":avb_testkey_rsa4096"],
+}
+
+genrule {
+ name: "pvmfw_embedded_key_pub_bin",
+ tools: ["avbtool"],
+ srcs: [":pvmfw_embedded_key"],
+ out: ["pvmfw_embedded_key_pub.bin"],
+ cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
}
genrule {
name: "pvmfw_embedded_key_rs",
- srcs: [":pvmfw_embedded_key"],
+ srcs: [":pvmfw_embedded_key_pub_bin"],
out: ["lib.rs"],
cmd: "(" +
" echo '#![no_std]';" +
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index aaf2691..da19931 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -71,6 +71,7 @@
Ok(hash(&digests)?)
}
+#[derive(Clone)]
pub struct PartialInputs {
pub code_hash: Hash,
pub auth_hash: Hash,
@@ -96,6 +97,7 @@
current_bcc_handover: &[u8],
salt: &[u8; HIDDEN_SIZE],
instance_hash: Option<Hash>,
+ deferred_rollback_protection: bool,
next_bcc: &mut [u8],
) -> Result<()> {
let config = self
@@ -107,16 +109,23 @@
Config::Descriptor(&config),
self.auth_hash,
self.mode,
- self.make_hidden(salt)?,
+ self.make_hidden(salt, deferred_rollback_protection)?,
);
let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc)?;
Ok(())
}
- fn make_hidden(&self, salt: &[u8; HIDDEN_SIZE]) -> Result<[u8; HIDDEN_SIZE]> {
+ fn make_hidden(
+ &self,
+ salt: &[u8; HIDDEN_SIZE],
+ deferred_rollback_protection: bool,
+ ) -> diced_open_dice::Result<[u8; HIDDEN_SIZE]> {
// We want to make sure we get a different sealing CDI for:
// - VMs with different salt values
// - An RKP VM and any other VM (regardless of salt)
+ // - depending on whether rollback protection has been deferred to payload. This ensures the
+ // adversary cannot leak the secrets by using old images & setting
+ // `deferred_rollback_protection` to true.
// The hidden input for DICE affects the sealing CDI (but the values in the config
// descriptor do not).
// Since the hidden input has to be a fixed size, create it as a hash of the values we
@@ -126,10 +135,16 @@
struct HiddenInput {
rkp_vm_marker: bool,
salt: [u8; HIDDEN_SIZE],
+ deferred_rollback_protection: bool,
}
- // TODO(b/291213394): Include `defer_rollback_protection` flag in the Hidden Input to
- // differentiate the secrets in both cases.
- Ok(hash(HiddenInput { rkp_vm_marker: self.rkp_vm_marker, salt: *salt }.as_bytes())?)
+ hash(
+ HiddenInput {
+ rkp_vm_marker: self.rkp_vm_marker,
+ salt: *salt,
+ deferred_rollback_protection,
+ }
+ .as_bytes(),
+ )
}
fn generate_config_descriptor(&self, instance_hash: Option<Hash>) -> Result<Vec<u8>> {
@@ -176,9 +191,20 @@
#[cfg(test)]
mod tests {
- use super::*;
+ use crate::{
+ Hash, PartialInputs, COMPONENT_NAME_KEY, INSTANCE_HASH_KEY, RKP_VM_MARKER_KEY,
+ SECURITY_VERSION_KEY,
+ };
use ciborium::Value;
+ use diced_open_dice::DiceArtifacts;
+ use diced_open_dice::DiceMode;
+ use diced_open_dice::HIDDEN_SIZE;
+ use pvmfw_avb::Capability;
+ use pvmfw_avb::DebugLevel;
+ use pvmfw_avb::Digest;
+ use pvmfw_avb::VerifiedBootData;
use std::collections::HashMap;
+ use std::mem::size_of;
use std::vec;
const COMPONENT_VERSION_KEY: i64 = -70003;
@@ -284,4 +310,67 @@
.map(|(k, v)| ((k.into_integer().unwrap().try_into().unwrap()), v))
.collect()
}
+
+ #[test]
+ fn changing_deferred_rpb_changes_secrets() {
+ let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+ let mut buffer_without_defer = [0; 4096];
+ let mut buffer_with_defer = [0; 4096];
+ let mut buffer_without_defer_retry = [0; 4096];
+
+ let sample_dice_input: &[u8] = &[
+ 0xa3, // CDI attest
+ 0x01, 0x58, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CDI seal
+ 0x02, 0x58, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DICE chain
+ 0x03, 0x82, 0xa6, 0x01, 0x02, 0x03, 0x27, 0x04, 0x02, 0x20, 0x01, 0x21, 0x40, 0x22,
+ 0x40, 0x84, 0x40, 0xa0, 0x40, 0x40,
+ // 8-bytes of trailing data that aren't part of the DICE chain.
+ 0x84, 0x41, 0x55, 0xa0, 0x42, 0x11, 0x22, 0x40,
+ ];
+
+ inputs
+ .clone()
+ .write_next_bcc(
+ sample_dice_input,
+ &[0u8; HIDDEN_SIZE],
+ Some([0u8; 64]),
+ false,
+ &mut buffer_without_defer,
+ )
+ .unwrap();
+ let bcc_handover1 = diced_open_dice::bcc_handover_parse(&buffer_without_defer).unwrap();
+
+ inputs
+ .clone()
+ .write_next_bcc(
+ sample_dice_input,
+ &[0u8; HIDDEN_SIZE],
+ Some([0u8; 64]),
+ true,
+ &mut buffer_with_defer,
+ )
+ .unwrap();
+ let bcc_handover2 = diced_open_dice::bcc_handover_parse(&buffer_with_defer).unwrap();
+
+ inputs
+ .clone()
+ .write_next_bcc(
+ sample_dice_input,
+ &[0u8; HIDDEN_SIZE],
+ Some([0u8; 64]),
+ false,
+ &mut buffer_without_defer_retry,
+ )
+ .unwrap();
+ let bcc_handover3 =
+ diced_open_dice::bcc_handover_parse(&buffer_without_defer_retry).unwrap();
+
+ assert_ne!(bcc_handover1.cdi_seal(), bcc_handover2.cdi_seal());
+ assert_eq!(bcc_handover1.cdi_seal(), bcc_handover3.cdi_seal());
+ }
}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 5893907..299d1c0 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -144,9 +144,9 @@
})?;
let instance_hash = if cfg!(llpvm_changes) { Some(salt_from_instance_id(fdt)?) } else { None };
- let (new_instance, salt) = if should_defer_rollback_protection(fdt)?
- && verified_boot_data.has_capability(Capability::SecretkeeperProtection)
- {
+ let defer_rollback_protection = should_defer_rollback_protection(fdt)?
+ && verified_boot_data.has_capability(Capability::SecretkeeperProtection);
+ let (new_instance, salt) = if defer_rollback_protection {
info!("Guest OS is capable of Secretkeeper protection, deferring rollback protection");
// rollback_index of the image is used as security_version and is expected to be > 0 to
// discourage implicit allocation.
@@ -201,12 +201,18 @@
Cow::Owned(truncated_bcc_handover)
};
- dice_inputs.write_next_bcc(new_bcc_handover.as_ref(), &salt, instance_hash, next_bcc).map_err(
- |e| {
+ dice_inputs
+ .write_next_bcc(
+ new_bcc_handover.as_ref(),
+ &salt,
+ instance_hash,
+ defer_rollback_protection,
+ next_bcc,
+ )
+ .map_err(|e| {
error!("Failed to derive next-stage DICE secrets: {e:?}");
RebootReason::SecretDerivationError
- },
- )?;
+ })?;
flush(next_bcc);
let kaslr_seed = u64::from_ne_bytes(rand::random_array().map_err(|e| {
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index b7ac827..1fc0f92 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -282,4 +282,8 @@
.map(os -> os.replaceFirst("^microdroid_gki-", ""))
.collect(Collectors.toList());
}
+
+ protected boolean isPkvmHypervisor() throws DeviceNotAvailableException {
+ return getDevice().getProperty("ro.boot.hypervisor.version").equals("kvm.arm-protected");
+ }
}
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index eb456f2..9d0b04b 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -809,8 +809,10 @@
// Check VmCreationRequested atom
AtomsProto.VmCreationRequested atomVmCreationRequested =
data.get(0).getAtom().getVmCreationRequested();
- assertThat(atomVmCreationRequested.getHypervisor())
- .isEqualTo(AtomsProto.VmCreationRequested.Hypervisor.PKVM);
+ if (isPkvmHypervisor()) {
+ assertThat(atomVmCreationRequested.getHypervisor())
+ .isEqualTo(AtomsProto.VmCreationRequested.Hypervisor.PKVM);
+ }
assertThat(atomVmCreationRequested.getIsProtected()).isEqualTo(mProtectedVm);
assertThat(atomVmCreationRequested.getCreationSucceeded()).isTrue();
assertThat(atomVmCreationRequested.getBinderExceptionCode()).isEqualTo(0);
@@ -832,7 +834,11 @@
assertThat(atomVmExited.getDeathReason()).isEqualTo(AtomsProto.VmExited.DeathReason.KILLED);
assertThat(atomVmExited.getExitSignal()).isEqualTo(9);
// In CPU & memory related fields, check whether positive values are collected or not.
- assertThat(atomVmExited.getGuestTimeMillis()).isGreaterThan(0);
+ if (isPkvmHypervisor()) {
+ // Guest Time may not be updated on other hypervisors.
+ // Checking only if the hypervisor is PKVM.
+ assertThat(atomVmExited.getGuestTimeMillis()).isGreaterThan(0);
+ }
assertThat(atomVmExited.getRssVmKb()).isGreaterThan(0);
assertThat(atomVmExited.getRssCrosvmKb()).isGreaterThan(0);
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 4ffef3c..12a46f7 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -2107,12 +2107,9 @@
IVmShareTestService service = connection.waitForService();
assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
+
try {
- // Send the VM descriptor to the other app. When received, it will reconstruct the VM
- // from the descriptor, start it, connect to the ITestService in it, creates a "proxy"
- // ITestService binder that delegates all the calls to the VM, and share it with this
- // app. It will allow us to verify assertions on the running VM in the other app.
- ITestService testServiceProxy = service.startVm(vmDesc);
+ ITestService testServiceProxy = transferAndStartVm(service, vmDesc, "vm_to_share");
int result = testServiceProxy.addInteger(37, 73);
assertThat(result).isEqualTo(110);
@@ -2163,12 +2160,7 @@
assertWithMessage("Timed out connecting to " + serviceIntent).that(service).isNotNull();
try {
- // Send the VM descriptor to the other app. When received, it will reconstruct the VM
- // from the descriptor, start it, connect to the ITestService in it, creates a "proxy"
- // ITestService binder that delegates all the calls to the VM, and share it with this
- // app. It will allow us to verify assertions on the running VM in the other app.
- ITestService testServiceProxy = service.startVm(vmDesc);
-
+ ITestService testServiceProxy = transferAndStartVm(service, vmDesc, "vm_to_share");
String result = testServiceProxy.readFromFile("/mnt/encryptedstore/private.key");
assertThat(result).isEqualTo(EXAMPLE_STRING);
} finally {
@@ -2176,6 +2168,25 @@
}
}
+ private ITestService transferAndStartVm(
+ IVmShareTestService service, VirtualMachineDescriptor vmDesc, String vmName)
+ throws Exception {
+ // Send the VM descriptor to the other app. When received, it will reconstruct the VM
+ // from the descriptor.
+ service.importVm(vmDesc);
+
+ // Now that the VM has been imported, we should be free to delete our copy (this is
+ // what we recommend for VM transfer).
+ getVirtualMachineManager().delete(vmName);
+
+ // Ask the other app to start the imported VM, connect to the ITestService in it, create
+ // a "proxy" ITestService binder that delegates all the calls to the VM, and share it
+ // with this app. It will allow us to verify assertions on the running VM in the other
+ // app.
+ ITestService testServiceProxy = service.startVm();
+ return testServiceProxy;
+ }
+
@Test
@CddTest(requirements = {"9.17/C-1-5"})
public void testFileUnderBinHasExecutePermission() throws Exception {
diff --git a/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl b/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl
index fe6ca43..ac59610 100644
--- a/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl
+++ b/tests/vmshareapp/aidl/com/android/microdroid/test/vmshare/IVmShareTestService.aidl
@@ -20,5 +20,7 @@
/** {@hide} */
interface IVmShareTestService {
- ITestService startVm(in VirtualMachineDescriptor vmDesc);
+ void importVm(in VirtualMachineDescriptor vmDesc);
+
+ ITestService startVm();
}
diff --git a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
index dc8908b..109486c 100644
--- a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
+++ b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
@@ -93,16 +93,19 @@
}
}
- public ITestService startVm(VirtualMachineDescriptor vmDesc) throws Exception {
+ public void importVm(VirtualMachineDescriptor vmDesc) throws Exception {
// Cleanup VM left from the previous test.
deleteVm();
- VirtualMachineManager vmm = getSystemService(VirtualMachineManager.class);
-
// Add random uuid to make sure that different tests that bind to this service don't trip
// over each other.
String vmName = "imported_vm" + UUID.randomUUID();
+ VirtualMachineManager vmm = getSystemService(VirtualMachineManager.class);
+ mVirtualMachine = vmm.importFromDescriptor(vmName, vmDesc);
+ }
+
+ public ITestService startVm() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
VirtualMachineCallback callback =
new VirtualMachineCallback() {
@@ -134,10 +137,9 @@
}
};
- mVirtualMachine = vmm.importFromDescriptor(vmName, vmDesc);
mVirtualMachine.setCallback(getMainExecutor(), callback);
- Log.i(TAG, "Starting VM " + vmName);
+ Log.i(TAG, "Starting VM " + mVirtualMachine.getName());
mVirtualMachine.run();
if (!latch.await(1, TimeUnit.MINUTES)) {
throw new TimeoutException("Timed out starting VM");
@@ -155,10 +157,21 @@
final class ServiceImpl extends IVmShareTestService.Stub {
@Override
- public ITestService startVm(VirtualMachineDescriptor vmDesc) {
+ public void importVm(VirtualMachineDescriptor vmDesc) {
+ Log.i(TAG, "importVm binder call received");
+ try {
+ VmShareServiceImpl.this.importVm(vmDesc);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to importVm", e);
+ throw new IllegalStateException("Failed to importVm", e);
+ }
+ }
+
+ @Override
+ public ITestService startVm() {
Log.i(TAG, "startVm binder call received");
try {
- return VmShareServiceImpl.this.startVm(vmDesc);
+ return VmShareServiceImpl.this.startVm();
} catch (Exception e) {
Log.e(TAG, "Failed to startVm", e);
throw new IllegalStateException("Failed to startVm", e);
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index d173b34..2df4fd7 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -624,6 +624,8 @@
} else {
None
};
+ let virtio_snd_backend =
+ if cfg!(paravirtualized_devices) { Some(String::from("aaudio")) } else { None };
// Actually start the VM.
let crosvm_config = CrosvmConfig {
@@ -654,6 +656,8 @@
input_device_options,
hugepages: config.hugePages,
tap,
+ virtio_snd_backend,
+ console_input_device: config.consoleInputDevice.clone(),
};
let instance = Arc::new(
VmInstance::new(
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index f73a977..6408b84 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -82,6 +82,12 @@
const SYSPROP_CUSTOM_PVMFW_PATH: &str = "hypervisor.pvmfw.path";
+/// Serial device for VM console input.
+/// Hypervisor (virtio-console)
+const CONSOLE_HVC0: &str = "hvc0";
+/// Serial (emulated uart)
+const CONSOLE_TTYS0: &str = "ttyS0";
+
lazy_static! {
/// If the VM doesn't move to the Started state within this amount time, a hang-up error is
/// triggered.
@@ -123,6 +129,8 @@
pub input_device_options: Vec<InputDeviceOption>,
pub hugepages: bool,
pub tap: Option<File>,
+ pub virtio_snd_backend: Option<String>,
+ pub console_input_device: Option<String>,
}
#[derive(Debug)]
@@ -918,19 +926,29 @@
let log_arg = format_serial_out_arg(&mut preserved_fds, &config.log_fd);
let failure_serial_path = add_preserved_fd(&mut preserved_fds, &failure_pipe_write);
let ramdump_arg = format_serial_out_arg(&mut preserved_fds, &config.ramdump);
+ let console_input_device = config.console_input_device.as_deref().unwrap_or(CONSOLE_HVC0);
+ match console_input_device {
+ CONSOLE_HVC0 | CONSOLE_TTYS0 => {}
+ _ => bail!("Unsupported serial device {console_input_device}"),
+ };
// Warning: Adding more serial devices requires you to shift the PCI device ID of the boot
// disks in bootconfig.x86_64. This is because x86 crosvm puts serial devices and the block
// devices in the same PCI bus and serial devices comes before the block devices. Arm crosvm
// doesn't have the issue.
// /dev/ttyS0
- command.arg(format!("--serial={},hardware=serial,num=1", &console_out_arg));
+ command.arg(format!(
+ "--serial={}{},hardware=serial,num=1",
+ &console_out_arg,
+ if console_input_device == CONSOLE_TTYS0 { &console_in_arg } else { "" }
+ ));
// /dev/ttyS1
command.arg(format!("--serial=type=file,path={},hardware=serial,num=2", &failure_serial_path));
// /dev/hvc0
command.arg(format!(
"--serial={}{},hardware=virtio-console,num=1",
- &console_out_arg, &console_in_arg
+ &console_out_arg,
+ if console_input_device == CONSOLE_HVC0 { &console_in_arg } else { "" }
));
// /dev/hvc1
command.arg(format!("--serial={},hardware=virtio-console,num=2", &ramdump_arg));
@@ -1029,6 +1047,12 @@
debug!("Preserving FDs {:?}", preserved_fds);
command.preserved_fds(preserved_fds);
+ if cfg!(paravirtualized_devices) {
+ if let Some(virtio_snd_backend) = &config.virtio_snd_backend {
+ command.arg("--virtio-snd").arg(format!("backend={}", virtio_snd_backend));
+ }
+ }
+
print_crosvm_args(&command);
let result = SharedChild::spawn(&mut command)?;
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index a5a849a..c927c9b 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -88,4 +88,7 @@
/** Whether the VM should have network feature. */
boolean networkSupported;
+
+ /** The serial device for VM console input. */
+ @nullable @utf8InCpp String consoleInputDevice;
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 5e71245..2fc9b4c 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -346,7 +346,7 @@
))
.with_log();
}
- if !remotely_provisioned_component_service_exists()? {
+ if !is_remote_provisioning_hal_declared()? {
return Err(Status::new_exception_str(
ExceptionCode::UNSUPPORTED_OPERATION,
Some("AVF remotely provisioned component service is not declared"),
@@ -403,7 +403,7 @@
}
fn isRemoteAttestationSupported(&self) -> binder::Result<bool> {
- remotely_provisioned_component_service_exists()
+ is_remote_provisioning_hal_declared()
}
fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
@@ -469,8 +469,16 @@
fn removeVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()> {
let state = &mut *self.state.lock().unwrap();
if let Some(sk_state) = &mut state.sk_state {
- info!("removeVmInstance(): delete secret");
- sk_state.delete_ids(&[*instance_id]);
+ let uid = get_calling_uid();
+ info!(
+ "Removing a VM's instance_id: {:?}, for uid: {:?}",
+ hex::encode(instance_id),
+ uid
+ );
+
+ let user_id = multiuser_get_user_id(uid);
+ let app_id = multiuser_get_app_id(uid);
+ sk_state.delete_id(instance_id, user_id, app_id);
} else {
info!("ignoring removeVmInstance() as no ISecretkeeper");
}
@@ -862,7 +870,9 @@
Ok(())
}
-fn remotely_provisioned_component_service_exists() -> binder::Result<bool> {
+/// Returns true if the AVF remotely provisioned component service is declared in the
+/// VINTF manifest.
+pub(crate) fn is_remote_provisioning_hal_declared() -> binder::Result<bool> {
Ok(binder::is_declared(REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME)?)
}
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 8acfdd3..55245f6 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -20,7 +20,10 @@
mod remote_provisioning;
mod rkpvm;
-use crate::aidl::{remove_temporary_dir, VirtualizationServiceInternal, TEMPORARY_DIRECTORY};
+use crate::aidl::{
+ is_remote_provisioning_hal_declared, remove_temporary_dir, VirtualizationServiceInternal,
+ TEMPORARY_DIRECTORY,
+};
use android_logger::{Config, FilterBuilder};
use android_system_virtualizationmaintenance::aidl::android::system::virtualizationmaintenance;
use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal;
@@ -81,7 +84,7 @@
BnVirtualizationServiceInternal::new_binder(service.clone(), BinderFeatures::default());
register(INTERNAL_SERVICE_NAME, internal_service)?;
- if cfg!(remote_attestation) {
+ if is_remote_provisioning_hal_declared().unwrap_or(false) {
// The IRemotelyProvisionedComponent service is only supposed to be triggered by rkpd for
// RKP VM attestation.
let remote_provisioning_service = remote_provisioning::new_binder();
diff --git a/virtualizationservice/src/maintenance.rs b/virtualizationservice/src/maintenance.rs
index 4732e1f..8e04075 100644
--- a/virtualizationservice/src/maintenance.rs
+++ b/virtualizationservice/src/maintenance.rs
@@ -90,14 +90,15 @@
self.get_inner()?.delete_ids_for_app(user_id, app_id)
}
- /// Delete the provided VM IDs from both Secretkeeper and the database.
- pub fn delete_ids(&mut self, vm_ids: &[VmId]) {
+ /// Delete the provided VM ID associated with `(user_id, app_id)` from both Secretkeeper and
+ /// the database.
+ pub fn delete_id(&mut self, vm_id: &VmId, user_id: u32, app_id: u32) {
let Ok(inner) = self.get_inner() else {
warn!("No Secretkeeper available, not deleting secrets");
return;
};
- inner.delete_ids(vm_ids)
+ inner.delete_id_for_app(vm_id, user_id, app_id)
}
/// Perform reconciliation to allow for possibly missed notifications of user or app removal.
@@ -157,6 +158,16 @@
self.vm_id_db.add_vm_id(vm_id, user_id, app_id)
}
+ fn delete_id_for_app(&mut self, vm_id: &VmId, user_id: u32, app_id: u32) {
+ if !self.vm_id_db.is_vm_id_for_app(vm_id, user_id, app_id).unwrap_or(false) {
+ info!(
+ "delete_id_for_app - VM id not associated with user_id={user_id}, app_id={app_id}"
+ );
+ return;
+ }
+ self.delete_ids(&[*vm_id])
+ }
+
fn delete_ids_for_user(&mut self, user_id: i32) -> Result<()> {
let vm_ids = self.vm_id_db.vm_ids_for_user(user_id)?;
info!(
@@ -371,8 +382,8 @@
#[test]
fn test_sk_state_batching() {
let history = Arc::new(Mutex::new(Vec::new()));
- let mut sk_state = new_test_state(history.clone(), 2);
- sk_state.delete_ids(&[VM_ID1, VM_ID2, VM_ID3, VM_ID4, VM_ID5]);
+ let sk_state = new_test_state(history.clone(), 2);
+ sk_state.inner.unwrap().delete_ids(&[VM_ID1, VM_ID2, VM_ID3, VM_ID4, VM_ID5]);
let got = (*history.lock().unwrap()).clone();
assert_eq!(
got,
@@ -387,8 +398,8 @@
#[test]
fn test_sk_state_no_batching() {
let history = Arc::new(Mutex::new(Vec::new()));
- let mut sk_state = new_test_state(history.clone(), 6);
- sk_state.delete_ids(&[VM_ID1, VM_ID2, VM_ID3, VM_ID4, VM_ID5]);
+ let sk_state = new_test_state(history.clone(), 6);
+ sk_state.inner.unwrap().delete_ids(&[VM_ID1, VM_ID2, VM_ID3, VM_ID4, VM_ID5]);
let got = (*history.lock().unwrap()).clone();
assert_eq!(got, vec![SkOp::DeleteIds(vec![VM_ID1, VM_ID2, VM_ID3, VM_ID4, VM_ID5])]);
}
@@ -402,7 +413,7 @@
get_db(&mut sk_state).add_vm_id(&VM_ID2, USER1, APP_A).unwrap();
get_db(&mut sk_state).add_vm_id(&VM_ID3, USER2, APP_B).unwrap();
get_db(&mut sk_state).add_vm_id(&VM_ID4, USER3, APP_A).unwrap();
- get_db(&mut sk_state).add_vm_id(&VM_ID5, USER3, APP_C).unwrap(); // Overwrites APP_A
+ get_db(&mut sk_state).add_vm_id(&VM_ID5, USER3, APP_C).unwrap();
assert_eq!((*history.lock().unwrap()).clone(), vec![]);
sk_state.delete_ids_for_app(USER2, APP_B).unwrap();
@@ -425,6 +436,36 @@
}
#[test]
+ fn test_sk_state_delete_id() {
+ let history = Arc::new(Mutex::new(Vec::new()));
+ let mut sk_state = new_test_state(history.clone(), 2);
+
+ get_db(&mut sk_state).add_vm_id(&VM_ID1, USER1, APP_A).unwrap();
+ get_db(&mut sk_state).add_vm_id(&VM_ID2, USER1, APP_A).unwrap();
+ get_db(&mut sk_state).add_vm_id(&VM_ID3, USER2, APP_B).unwrap();
+ assert_eq!((*history.lock().unwrap()).clone(), vec![]);
+
+ // A VM ID that doesn't exist anywhere - no delete
+ sk_state.delete_id(&VM_ID4, USER1 as u32, APP_A as u32);
+ assert_eq!((*history.lock().unwrap()).clone(), vec![]);
+
+ // Wrong app ID - no delete
+ sk_state.delete_id(&VM_ID1, USER1 as u32, APP_B as u32);
+ assert_eq!((*history.lock().unwrap()).clone(), vec![]);
+
+ // Wrong user ID - no delete
+ sk_state.delete_id(&VM_ID1, USER2 as u32, APP_A as u32);
+ assert_eq!((*history.lock().unwrap()).clone(), vec![]);
+
+ // This porridge is just right.
+ sk_state.delete_id(&VM_ID1, USER1 as u32, APP_A as u32);
+ assert_eq!((*history.lock().unwrap()).clone(), vec![SkOp::DeleteIds(vec![VM_ID1])]);
+
+ assert_eq!(vec![VM_ID2], get_db(&mut sk_state).vm_ids_for_user(USER1).unwrap());
+ assert_eq!(vec![VM_ID3], get_db(&mut sk_state).vm_ids_for_user(USER2).unwrap());
+ }
+
+ #[test]
fn test_sk_state_reconcile() {
let history = Arc::new(Mutex::new(Vec::new()));
let mut sk_state = new_test_state(history.clone(), 20);
diff --git a/virtualizationservice/src/maintenance/vmdb.rs b/virtualizationservice/src/maintenance/vmdb.rs
index 273f340..3519015 100644
--- a/virtualizationservice/src/maintenance/vmdb.rs
+++ b/virtualizationservice/src/maintenance/vmdb.rs
@@ -272,6 +272,21 @@
Ok(vm_ids)
}
+ /// Determine whether the specified VM ID is associated with `(user_id, app_id)`. Returns false
+ /// if there is no such VM ID, or it exists but is not associated.
+ pub fn is_vm_id_for_app(&mut self, vm_id: &VmId, user_id: u32, app_id: u32) -> Result<bool> {
+ let mut stmt = self
+ .conn
+ .prepare(
+ "SELECT COUNT(*) FROM main.vmids \
+ WHERE vm_id = ? AND user_id = ? AND app_id = ?;",
+ )
+ .context("failed to prepare SELECT stmt")?;
+ stmt.query_row(params![vm_id, user_id, app_id], |row| row.get(0))
+ .context("query failed")
+ .map(|n: usize| n != 0)
+ }
+
/// Determine the number of VM IDs associated with `(user_id, app_id)`.
pub fn count_vm_ids_for_app(&mut self, user_id: i32, app_id: i32) -> Result<usize> {
let mut stmt = self
@@ -350,6 +365,7 @@
const VM_ID3: VmId = [3u8; 64];
const VM_ID4: VmId = [4u8; 64];
const VM_ID5: VmId = [5u8; 64];
+ const VM_ID_UNKNOWN: VmId = [6u8; 64];
const USER1: i32 = 1;
const USER2: i32 = 2;
const USER3: i32 = 3;
@@ -506,6 +522,13 @@
assert_eq!(empty, db.vm_ids_for_app(USER1, APP_UNKNOWN).unwrap());
assert_eq!(0, db.count_vm_ids_for_app(USER1, APP_UNKNOWN).unwrap());
+ assert!(db.is_vm_id_for_app(&VM_ID1, USER1 as u32, APP_A as u32).unwrap());
+ assert!(!db.is_vm_id_for_app(&VM_ID1, USER2 as u32, APP_A as u32).unwrap());
+ assert!(!db.is_vm_id_for_app(&VM_ID1, USER1 as u32, APP_B as u32).unwrap());
+ assert!(!db.is_vm_id_for_app(&VM_ID_UNKNOWN, USER1 as u32, APP_A as u32).unwrap());
+ assert!(!db.is_vm_id_for_app(&VM_ID5, USER3 as u32, APP_A as u32).unwrap());
+ assert!(db.is_vm_id_for_app(&VM_ID5, USER3 as u32, APP_C as u32).unwrap());
+
db.delete_vm_ids(&[VM_ID2, VM_ID3]).unwrap();
assert_eq!(vec![VM_ID1], db.vm_ids_for_user(USER1).unwrap());
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 4d79235..5355313 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -47,13 +47,12 @@
import android.view.WindowManager;
import android.view.WindowMetrics;
+import libcore.io.IoBridge;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import libcore.io.IoBridge;
-
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
@@ -123,6 +122,9 @@
}
}
}
+ if (json.has("console_input_device")) {
+ configBuilder.setConsoleInputDevice(json.getString("console_input_device"));
+ }
configBuilder.setMemoryBytes(8L * 1024 * 1024 * 1024 /* 8 GB */);
WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics();