Merge "Update needed for Rust v1.77.1" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 58dcc06..1c4f5ca 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -36,15 +36,9 @@
"name": "initrd_bootconfig.test"
},
{
- "name": "libdice_policy.test"
- },
- {
"name": "libapkzip.test"
},
{
- "name": "libsecretkeeper_comm.test"
- },
- {
"name": "libdice_driver_test"
}
],
diff --git a/apex/Android.bp b/apex/Android.bp
index 3b5141e..e6c809c 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -42,6 +42,7 @@
"release_avf_enable_remote_attestation",
"release_avf_enable_vendor_modules",
"release_avf_enable_virt_cpufreq",
+ "release_avf_support_custom_vm_with_paravirtualized_devices",
],
properties: [
"androidManifest",
@@ -50,6 +51,7 @@
"prebuilts",
"systemserverclasspath_fragments",
"vintf_fragments",
+ "apps",
],
}
@@ -96,6 +98,11 @@
canned_fs_config: "canned_fs_config",
},
},
+ release_avf_support_custom_vm_with_paravirtualized_devices: {
+ apps: [
+ "VmLauncherApp",
+ ],
+ },
},
}
diff --git a/docs/vm_remote_attestation.md b/docs/vm_remote_attestation.md
index 093418b..ddb7adf 100644
--- a/docs/vm_remote_attestation.md
+++ b/docs/vm_remote_attestation.md
@@ -1,3 +1,98 @@
# VM Remote Attestation
-(To be filled)
+## Introduction
+
+In today's digital landscape, where security threats are ever-evolving, ensuring
+the authenticity and integrity of VMs is paramount. This is particularly crucial
+for sensitive applications, such as those running machine learning models, where
+guaranteeing a trusted and secure execution environment is essential.
+
+VM remote attestation provides a powerful mechanism for *protected VMs* (pVMs)
+to prove their trustworthiness to a third party. This process allows a pVM to
+demonstrate that:
+
+- All its components, including firmware, operating system, and software, are
+ valid and have not been tampered with.
+- It is running on a valid device trusted by the
+ [Remote Key Provisioning][rkp] (RKP) backend, such as Google.
+
+[rkp]: https://source.android.com/docs/core/ota/modular-system/remote-key-provisioning
+
+## Design
+
+The process of pVM remote attestation involves the use of a lightweight
+intermediate VM known as the [RKP VM][rkpvm]. It allows us to divide the
+attestation process into two parts:
+
+1. Attesting the RKP VM against the RKP server.
+2. Attesting the pVM against the RKP VM.
+
+[rkpvm]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/service_vm/README.md
+
+### RKP VM attestation
+
+The RKP VM is recognized and attested by the RKP server, which acts as a trusted
+entity responsible for verifying the [DICE chain][open-dice] of the RKP VM. This
+verification ensures that the RKP VM is operating on a genuine device.
+Additionally, the RKP VM is validated by the pVM Firmware, as part of the
+verified boot process.
+
+[open-dice]: https://android.googlesource.com/platform/external/open-dice/+/main/docs/android.md
+
+### pVM attestation
+
+Once the RKP VM is successfully attested, it acts as a trusted platform to
+attest pVMs. Leveraging its trusted status, the RKP VM validates the integrity
+of each pVM's DICE chain by comparing it against its own DICE chain. This
+validation process ensures that the pVMs are running in the expected VM
+environment and certifies the payload executed within each pVM. Currently, only
+Microdroid VMs are supported.
+
+## API
+
+To request remote attestation of a pVM, the [VM Payload API][api]
+`AVmPayload_requestAttestation(challenge)` can be invoked within the pVM
+payload.
+
+For detailed information and usage examples, please refer to the
+[demo app][demo].
+
+[api]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/vm_payload/README.md
+[demo]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/service_vm/demo_apk
+
+## Output
+
+Upon successful completion of the attestation process, a pVM receives an
+RKP-backed certificate chain and an attested private key that is exclusively
+known to the pVM. This certificate chain includes a leaf certificate covering
+the attested public key. Notably, the leaf certificate features a new extension
+with the OID `1.3.6.1.4.1.11129.2.1.29.1`, specifically designed to describe the
+pVM payload for third-party verification.
+
+The extension format is as follows:
+
+```
+AttestationExtension ::= SEQUENCE {
+ attestationChallenge OCTET_STRING,
+ isVmSecure BOOLEAN,
+ vmComponents SEQUENCE OF VmComponent,
+}
+
+VmComponent ::= SEQUENCE {
+ name UTF8String,
+ securityVersion INTEGER,
+ codeHash OCTET STRING,
+ authorityHash OCTET STRING,
+}
+```
+
+In `AttestationExtension`:
+
+- The `attestationChallenge` field represents a challenge provided by the
+ third party. It is passed to `AVmPayload_requestAttestation()` to ensure
+ the freshness of the certificate.
+- The `isVmSecure` field indicates whether the attested pVM is secure. It is
+ set to true only when all the DICE certificates in the pVM DICE chain are in
+ normal mode.
+- The `vmComponents` field contains a list of all the APKs and apexes loaded
+ by the pVM.
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index d746c7c..cc126eb 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -431,11 +431,6 @@
VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance());
config.serialize(vm.mConfigFilePath);
- if (vm.mInstanceIdPath != null) {
- vm.importInstanceIdFrom(vmDescriptor.getInstanceIdFd());
- vm.claimInstance();
- }
-
try {
vm.mInstanceFilePath.createNewFile();
} catch (IOException e) {
@@ -452,12 +447,16 @@
}
vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
}
+ if (vm.mInstanceIdPath != null) {
+ vm.importInstanceIdFrom(vmDescriptor.getInstanceIdFd());
+ vm.claimInstance();
+ }
}
return vm;
} catch (VirtualMachineException | RuntimeException e) {
// If anything goes wrong, delete any files created so far and the VM's directory
try {
- vmInstanceCleanup(context, name);
+ deleteRecursively(vmDir);
} catch (Exception innerException) {
e.addSuppressed(innerException);
}
diff --git a/libs/android_display_backend/Android.bp b/libs/android_display_backend/Android.bp
index 6ad5fab..f792a04 100644
--- a/libs/android_display_backend/Android.bp
+++ b/libs/android_display_backend/Android.bp
@@ -11,6 +11,9 @@
backend: {
java: {
enabled: true,
+ apex_available: [
+ "com.android.virt",
+ ],
},
cpp: {
enabled: false,
diff --git a/libs/dice/driver/src/lib.rs b/libs/dice/driver/src/lib.rs
index 79edb51..b5c1f12 100644
--- a/libs/dice/driver/src/lib.rs
+++ b/libs/dice/driver/src/lib.rs
@@ -65,6 +65,7 @@
/// Creates a new dice driver from the given driver_path.
pub fn new(driver_path: &Path, is_strict_boot: bool) -> Result<Self> {
+ log::info!("Creating DiceDriver backed by {driver_path:?} driver");
if driver_path.exists() {
log::info!("Using DICE values from driver");
} else if is_strict_boot {
@@ -107,6 +108,7 @@
/// Create a new dice driver that reads dice_artifacts from the given file.
pub fn from_file(file_path: &Path) -> Result<Self> {
+ log::info!("Creating DiceDriver backed by {file_path:?} file");
let file =
fs::File::open(file_path).map_err(|error| Error::new(error).context("open file"))?;
let dice_artifacts = serde_cbor::from_reader(file)
@@ -149,11 +151,18 @@
&input_values,
)
.context("DICE derive from driver")?;
- if let Self::Real { driver_path, .. } = &self {
- // Writing to the device wipes the artifacts. The string is ignored by the driver but
- // included for documentation.
- fs::write(driver_path, "wipe")
- .map_err(|err| Error::new(err).context("Wiping driver"))?;
+ match &self {
+ Self::Real { driver_path, .. } => {
+ // Writing to the device wipes the artifacts. The string is ignored by the driver
+ // but included for documentation.
+ fs::write(driver_path, "wipe")
+ .map_err(|err| Error::new(err).context("Wiping driver"))?;
+ }
+ Self::FromFile { file_path, .. } => {
+ fs::remove_file(file_path)
+ .map_err(|err| Error::new(err).context("Deleting file"))?;
+ }
+ Self::Fake { .. } => (),
}
Ok(next_dice_artifacts)
}
@@ -176,6 +185,11 @@
#[cfg(test)]
mod tests {
use super::*;
+ use core::ffi::CStr;
+ use diced_open_dice::{
+ hash, retry_bcc_format_config_descriptor, DiceConfigValues, HIDDEN_SIZE,
+ };
+ use std::fs::File;
fn assert_eq_bytes(expected: &[u8], actual: &[u8]) {
assert_eq!(
@@ -204,4 +218,34 @@
Ok(())
}
+
+ #[test]
+ fn test_dice_driver_from_file_deletes_file_after_derive() -> Result<()> {
+ let tmp_dir = tempfile::tempdir()?;
+
+ let file_path = tmp_dir.path().join("test-dice-chain.raw");
+
+ {
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+ let file = File::create(&file_path)?;
+ serde_cbor::to_writer(file, &dice_artifacts)?;
+ }
+
+ let dice = DiceDriver::from_file(&file_path)?;
+
+ let values = DiceConfigValues {
+ component_name: Some(CStr::from_bytes_with_nul(b"test\0")?),
+ ..Default::default()
+ };
+ let desc = retry_bcc_format_config_descriptor(&values)?;
+ let code_hash = hash(&String::from("test code hash").into_bytes())?;
+ let authority_hash = hash(&String::from("test authority hash").into_bytes())?;
+ let hidden = [0; HIDDEN_SIZE];
+
+ let _ = dice.derive(code_hash, &desc, authority_hash, false, hidden)?;
+
+ assert!(!file_path.exists());
+
+ Ok(())
+ }
}
diff --git a/libs/vbmeta/Android.bp b/libs/vbmeta/Android.bp
index 4fb6ae4..9a7375d 100644
--- a/libs/vbmeta/Android.bp
+++ b/libs/vbmeta/Android.bp
@@ -35,6 +35,8 @@
":avb_testkey_rsa2048",
":avb_testkey_rsa4096",
":avb_testkey_rsa8192",
+ ":test_microdroid_vendor_image",
+ ":test_microdroid_vendor_image_no_rollback_index",
],
required: ["avbtool"],
test_suites: ["general-tests"],
diff --git a/libs/vbmeta/src/lib.rs b/libs/vbmeta/src/lib.rs
index 1a40e45..a15f699 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -148,6 +148,11 @@
Descriptors::from_image(&self.data)
}
+ /// Returns the rollback_index of the VBMeta image.
+ pub fn rollback_index(&self) -> u64 {
+ self.header.rollback_index
+ }
+
/// Get the raw VBMeta image.
pub fn data(&self) -> &[u8] {
&self.data
@@ -283,4 +288,19 @@
fn test_rsa8192_signed_image() -> Result<()> {
signed_image_has_valid_vbmeta("SHA256_RSA8192", "data/testkey_rsa8192.pem")
}
+
+ #[test]
+ fn test_rollback_index() -> Result<()> {
+ let vbmeta = VbMetaImage::verify_path("test_microdroid_vendor_image.img")?;
+ assert_eq!(5, vbmeta.rollback_index());
+ Ok(())
+ }
+
+ #[test]
+ fn test_rollback_index_default_zero() -> Result<()> {
+ let vbmeta =
+ VbMetaImage::verify_path("test_microdroid_vendor_image_no_rollback_index.img")?;
+ assert_eq!(0, vbmeta.rollback_index());
+ Ok(())
+ }
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 169ecae..33d98dc 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -55,6 +55,7 @@
properties: [
"deps",
"dirs",
+ "multilib",
],
}
@@ -157,9 +158,11 @@
// Below are dependencies that are conditionally enabled depending on value of build flags.
soong_config_variables: {
release_avf_enable_dice_changes: {
- deps: [
- "derive_microdroid_vendor_dice_node",
- ],
+ multilib: {
+ lib64: {
+ deps: ["derive_microdroid_vendor_dice_node"],
+ },
+ },
dirs: [
"microdroid_resources",
],
diff --git a/microdroid/README.md b/microdroid/README.md
index baf41b0..c0cba97 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -41,7 +41,7 @@
## Building an app
A [vm
-payload](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/vm_payload/)
+payload](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/vm_payload/)
is a shared library file that gets executed in microdroid. It is packaged as
part of an Android application. The library should have an entry point
`AVmPayload_main` as shown below:
@@ -132,12 +132,12 @@
### Using the APIs
Use the [Android Virtualization Framework Java
-APIs](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/javalib/api/system-current.txt)
+APIs](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/java/framework/README.md)
in your app to create a microdroid VM and run payload in it. The APIs are currently
@SystemApi, and only available to preinstalled apps.
If you are looking for an example usage of the APIs, you may refer to the [demo
-app](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/demo/).
+app](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/demo/).
## Running Microdroid with vendor image
diff --git a/microdroid/derive_microdroid_vendor_dice_node/Android.bp b/microdroid/derive_microdroid_vendor_dice_node/Android.bp
index de1bef7..8b79aad 100644
--- a/microdroid/derive_microdroid_vendor_dice_node/Android.bp
+++ b/microdroid/derive_microdroid_vendor_dice_node/Android.bp
@@ -10,9 +10,20 @@
rustlibs: [
"libanyhow",
"libclap",
+ "libcstr",
+ "libdice_driver",
+ "libdiced_open_dice",
+ "libdm_rust",
+ "libserde_cbor",
+ "libvbmeta_rust",
],
bootstrap: true,
prefer_rlib: true,
+ multilib: {
+ lib32: {
+ enabled: false,
+ },
+ },
}
rust_binary {
diff --git a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
index 1d5db0d..c7bc3f5 100644
--- a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
+++ b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
@@ -14,9 +14,19 @@
//! Derives microdroid vendor dice node.
-use anyhow::Error;
+use anyhow::{bail, Context, Result};
use clap::Parser;
-use std::path::PathBuf;
+use cstr::cstr;
+use dice_driver::DiceDriver;
+use diced_open_dice::{
+ hash, retry_bcc_format_config_descriptor, DiceConfigValues, OwnedDiceArtifacts, HIDDEN_SIZE,
+};
+use dm::util::blkgetsize64;
+use std::fs::{read_link, File};
+use std::path::{Path, PathBuf};
+use vbmeta::VbMetaImage;
+
+const AVF_STRICT_BOOT: &str = "/proc/device-tree/chosen/avf,strict-boot";
#[derive(Parser)]
struct Args {
@@ -31,8 +41,74 @@
output: PathBuf,
}
-fn main() -> Result<(), Error> {
+// TODO(ioffe): move to a library to reuse same code here, in microdroid_manager and in
+// first_stage_init.
+fn is_strict_boot() -> bool {
+ Path::new(AVF_STRICT_BOOT).exists()
+}
+
+fn build_descriptor(vbmeta: &VbMetaImage) -> Result<Vec<u8>> {
+ let values = DiceConfigValues {
+ component_name: Some(cstr!("Microdroid vendor")),
+ security_version: Some(vbmeta.rollback_index()),
+ ..Default::default()
+ };
+ Ok(retry_bcc_format_config_descriptor(&values)?)
+}
+
+// TODO(ioffe): move to libvbmeta.
+fn find_root_digest(vbmeta: &VbMetaImage) -> Result<Option<Vec<u8>>> {
+ for descriptor in vbmeta.descriptors()?.iter() {
+ if let vbmeta::Descriptor::Hashtree(_) = descriptor {
+ return Ok(Some(descriptor.to_hashtree()?.root_digest().to_vec()));
+ }
+ }
+ Ok(None)
+}
+
+fn dice_derivation(dice: DiceDriver, vbmeta: &VbMetaImage) -> Result<OwnedDiceArtifacts> {
+ let authority_hash = if let Some(pubkey) = vbmeta.public_key() {
+ hash(pubkey).context("hash pubkey")?
+ } else {
+ bail!("no public key")
+ };
+ let code_hash = if let Some(root_digest) = find_root_digest(vbmeta)? {
+ hash(root_digest.as_ref()).context("hash root_digest")?
+ } else {
+ bail!("no hashtree")
+ };
+ let desc = build_descriptor(vbmeta).context("build descriptor")?;
+ let hidden = [0; HIDDEN_SIZE];
+ // The microdroid vendor partition doesn't contribute to the debuggability of the VM, and it is
+ // a bit tricky to propagate the info on whether the VM is debuggable to
+ // derive_microdroid_dice_node binary. Given these, we just always set `is_debuggable: false`
+ // for the "Microdroid vendor" dice node. The adjacent dice nodes (pvmfw & payload) provide the
+ // accurate information on whether VM is debuggable.
+ dice.derive(code_hash, &desc, authority_hash, /* debug= */ false, hidden)
+}
+
+fn extract_vbmeta(block_dev: &Path) -> Result<VbMetaImage> {
+ let size = blkgetsize64(block_dev).context("blkgetsize64 failed")?;
+ let file = File::open(block_dev).context("open failed")?;
+ let vbmeta = VbMetaImage::verify_reader_region(file, 0, size)?;
+ Ok(vbmeta)
+}
+
+fn try_main() -> Result<()> {
let args = Args::parse();
- eprintln!("{:?} {:?} {:?}", args.dice_driver, args.microdroid_vendor_disk_image, args.output);
+ let dice =
+ DiceDriver::new(&args.dice_driver, is_strict_boot()).context("Failed to load DICE")?;
+ let path = read_link(args.microdroid_vendor_disk_image).context("failed to read symlink")?;
+ let vbmeta = extract_vbmeta(&path).context("failed to extract vbmeta")?;
+ let dice_artifacts = dice_derivation(dice, &vbmeta).context("failed to derive dice chain")?;
+ let file = File::create(&args.output).context("failed to create output")?;
+ serde_cbor::to_writer(file, &dice_artifacts).context("failed to write dice artifacts")?;
Ok(())
}
+
+fn main() {
+ if let Err(e) = try_main() {
+ eprintln!("failed with {:?}", e);
+ std::process::exit(1);
+ }
+}
diff --git a/microdroid/kernel/README.md b/microdroid/kernel/README.md
index 92b7cfe..52df333 100644
--- a/microdroid/kernel/README.md
+++ b/microdroid/kernel/README.md
@@ -29,7 +29,7 @@
```
Note that
-[`--config=fast`](https://android.googlesource.com/kernel/build/+/refs/heads/master/kleaf/docs/fast.md)
+[`--config=fast`](https://android.googlesource.com/kernel/build/+/refs/heads/main/kleaf/docs/fast.md)
is not mandatory, but will make your build much faster.
The build may fail in case you are doing an incremental build and the config has changed (b/257288175). Until that issue
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 2386bd4..7da9ea4 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -43,6 +43,7 @@
use log::{error, info};
use microdroid_metadata::{Metadata, PayloadMetadata};
use microdroid_payload_config::{ApkConfig, OsConfig, Task, TaskType, VmPayloadConfig};
+use nix::mount::{umount2, MntFlags};
use nix::sys::signal::Signal;
use payload::load_metadata;
use rpcbinder::RpcSession;
@@ -86,6 +87,8 @@
const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
const ENCRYPTEDSTORE_KEYSIZE: usize = 32;
+const DICE_CHAIN_FILE: &str = "/microdroid_resources/dice_chain.raw";
+
#[derive(thiserror::Error, Debug)]
enum MicrodroidError {
#[error("Cannot connect to virtualization service: {0}")]
@@ -301,8 +304,13 @@
vm_payload_service_fd: OwnedFd,
) -> Result<i32> {
let metadata = load_metadata().context("Failed to load payload metadata")?;
- let dice = DiceDriver::new(Path::new("/dev/open-dice0"), is_strict_boot())
- .context("Failed to load DICE")?;
+ let dice = if Path::new(DICE_CHAIN_FILE).exists() {
+ DiceDriver::from_file(Path::new(DICE_CHAIN_FILE))
+ .context("Failed to load DICE from file")?
+ } else {
+ DiceDriver::new(Path::new("/dev/open-dice0"), is_strict_boot())
+ .context("Failed to load DICE from driver")?
+ };
// Microdroid skips checking payload against instance image iff the device supports
// secretkeeper. In that case Microdroid use VmSecret::V2, which provide protection against
@@ -328,6 +336,10 @@
// Start apexd to activate APEXes. This may allow code within them to run.
system_properties::write("ctl.start", "apexd-vm")?;
+
+ // Unmounting /microdroid_resources is a defence-in-depth effort to ensure that payload
+ // can't get hold of dice chain stored there.
+ umount2("/microdroid_resources", MntFlags::MNT_DETACH)?;
}
// Run encryptedstore binary to prepare the storage
diff --git a/service_vm/README.md b/service_vm/README.md
index 3d94f78..ca03c1d 100644
--- a/service_vm/README.md
+++ b/service_vm/README.md
@@ -18,28 +18,12 @@
## RKP VM (Remote Key Provisioning Virtual Machine)
-The RKP VM is a key dependency of the Service VM. It is a virtual machine that
-undergoes validation by the [RKP][rkp] Server and acts as a remotely provisioned
-component for verifying the integrity of other virtual machines.
+Currently, the Service VM only supports VM remote attestation, and in that
+context we refer to it as the RKP VM. The RKP VM undergoes validation by the
+[RKP][rkp] Server and functions as a remotely provisioned component responsible
+for verifying the integrity of other virtual machines. See
+[VM remote attestation][vm-attestation] for more details about the role of RKP
+VM in remote attestation.
[rkp]: https://source.android.com/docs/core/ota/modular-system/remote-key-provisioning
-
-### RKP VM attestation
-
-The RKP VM is recognized and attested by the RKP server, which acts as a trusted
-entity responsible for verifying the DICE chain of the RKP VM. This verification
-ensures that the RKP VM is operating on a genuine device.
-Additionally, the RKP VM is validated by the pVM Firmware, as part of the
-verified boot process.
-
-### Client VM attestation
-
-Once the RKP VM is successfully attested, it assumes the role of a trusted
-platform to attest client VMs. It leverages its trusted status to validate the
-integrity of the [DICE chain][open-dice] associated with each client VM. This
-validation process verifies that the client VMs are running in the expected
-[Microdroid][microdroid] VM environment, and certifies the payload executed
-within the VM. Currently, only Microdroid VMs are supported.
-
-[open-dice]: https://android.googlesource.com/platform/external/open-dice/+/main/docs/android.md
-[microdroid]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/microdroid/README.md
+[vm-attestation]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/docs/vm_remote_attestation.md
diff --git a/service_vm/requests/src/cert.rs b/service_vm/requests/src/cert.rs
index 91281e7..e31d870 100644
--- a/service_vm/requests/src/cert.rs
+++ b/service_vm/requests/src/cert.rs
@@ -43,7 +43,7 @@
/// Attestation extension contents
///
/// ```asn1
-/// AttestationDescription ::= SEQUENCE {
+/// AttestationExtension ::= SEQUENCE {
/// attestationChallenge OCTET_STRING,
/// isVmSecure BOOLEAN,
/// vmComponents SEQUENCE OF VmComponent,
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 6c82de8..f881909 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -155,6 +155,12 @@
public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)
throws VirtualMachineException {
final VirtualMachineManager vmm = getVirtualMachineManager();
+ deleteVirtualMachineIfExists(name);
+ return vmm.create(name, config);
+ }
+
+ protected void deleteVirtualMachineIfExists(String name) throws VirtualMachineException {
+ VirtualMachineManager vmm = getVirtualMachineManager();
boolean deleteExisting;
try {
deleteExisting = vmm.get(name) != null;
@@ -166,7 +172,6 @@
if (deleteExisting) {
vmm.delete(name);
}
- return vmm.create(name, config);
}
public void prepareTestSetup(boolean protectedVm, String gki) {
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 8f4df63..29e9014 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -37,6 +37,7 @@
import static org.junit.Assume.assumeTrue;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.util.stream.Collectors.toList;
import android.app.Instrumentation;
import android.app.UiAutomation;
@@ -110,6 +111,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.model.Array;
@@ -1055,6 +1057,51 @@
changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL);
}
+ // Copy the Vm directory, creating the target Vm directory if it does not already exist.
+ private void copyVmDirectory(String sourceVmName, String targetVmName) throws IOException {
+ Path sourceVm = getVmDirectory(sourceVmName);
+ Path targetVm = getVmDirectory(targetVmName);
+ if (!Files.exists(targetVm)) {
+ Files.createDirectories(targetVm);
+ }
+
+ try (Stream<Path> stream = Files.list(sourceVm)) {
+ for (Path f : stream.collect(toList())) {
+ Files.copy(f, targetVm.resolve(f.getFileName()), REPLACE_EXISTING);
+ }
+ }
+ }
+
+ private Path getVmDirectory(String vmName) {
+ Context context = getContext();
+ Path filePath = Paths.get(context.getDataDir().getPath(), "vm", vmName);
+ return filePath;
+ }
+
+ // Create a fresh VM with the given `vmName`, instance_id & instance.img. This function creates
+ // a Vm with a different temporary name & copies it to target VM directory. This ensures this
+ // VM is not in cache of `VirtualMachineManager` which makes it possible to modify underlying
+ // files.
+ private void createUncachedVmWithName(
+ String vmName, VirtualMachineConfig config, File vmIdBackup, File vmInstanceBackup)
+ throws Exception {
+ deleteVirtualMachineIfExists(vmName);
+ forceCreateNewVirtualMachine("test_vm_tmp", config);
+ copyVmDirectory("test_vm_tmp", vmName);
+ if (vmInstanceBackup != null) {
+ Files.copy(
+ vmInstanceBackup.toPath(),
+ getVmFile(vmName, "instance.img").toPath(),
+ REPLACE_EXISTING);
+ }
+ if (vmIdBackup != null) {
+ Files.copy(
+ vmIdBackup.toPath(),
+ getVmFile(vmName, "instance_id").toPath(),
+ REPLACE_EXISTING);
+ }
+ }
+
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
public void changingDebuggableVmNonDebuggableInvalidatesVmIdentity() throws Exception {
@@ -1089,29 +1136,17 @@
Files.copy(vmId.toPath(), vmIdBackup.toPath(), REPLACE_EXISTING);
}
- forceCreateNewVirtualMachine("test_vm", normalConfig);
-
- if (vmInstanceBackup != null) {
- Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
- }
- if (vmIdBackup != null) {
- Files.copy(vmIdBackup.toPath(), vmId.toPath(), REPLACE_EXISTING);
- }
- assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
+ createUncachedVmWithName("test_vm_rerun", normalConfig, vmIdBackup, vmInstanceBackup);
+ assertThat(tryBootVm(TAG, "test_vm_rerun").payloadStarted).isTrue();
// Launch the same VM with a different debug level. The Java API prohibits this
// (thankfully).
// For testing, we do that by creating a new VM with debug level, and overwriting the old
// instance data to the new VM instance data.
VirtualMachineConfig debugConfig = builder.setDebugLevel(toLevel).build();
- forceCreateNewVirtualMachine("test_vm", debugConfig);
- if (vmInstanceBackup != null) {
- Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
- }
- if (vmIdBackup != null) {
- Files.copy(vmIdBackup.toPath(), vmId.toPath(), REPLACE_EXISTING);
- }
- assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isFalse();
+ createUncachedVmWithName(
+ "test_vm_changed_debug_level", debugConfig, vmIdBackup, vmInstanceBackup);
+ assertThat(tryBootVm(TAG, "test_vm_changed_debug_level").payloadStarted).isFalse();
}
private static class VmCdis {
@@ -1555,7 +1590,6 @@
assertFileContentsAreEqualInTwoVms("storage.img", vmNameOrig, vmNameImport);
}
assertThat(vmImport).isNotEqualTo(vmOrig);
- vmm.delete(vmNameOrig);
assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
TestResults testResults =
runVmTestService(
diff --git a/tests/vendor_images/Android.bp b/tests/vendor_images/Android.bp
index 26dbc01..66f0219 100644
--- a/tests/vendor_images/Android.bp
+++ b/tests/vendor_images/Android.bp
@@ -15,6 +15,16 @@
file_contexts: ":microdroid_vendor_file_contexts.gen",
use_avb: true,
avb_private_key: ":vendor_sign_key",
+ rollback_index: 5,
+}
+
+android_filesystem {
+ name: "test_microdroid_vendor_image_no_rollback_index",
+ partition_name: "microdroid-vendor",
+ type: "ext4",
+ file_contexts: ":microdroid_vendor_file_contexts.gen",
+ use_avb: true,
+ avb_private_key: ":vendor_sign_key",
}
android_filesystem {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 9b0ec5f..86c9af3 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -440,15 +440,15 @@
/// Waits until payload is started, or timeout expires. When timeout occurs, kill
/// the VM to prevent indefinite hangup and update the payload_state accordingly.
- #[allow(let_underscore_lock)]
fn monitor_payload_hangup(&self, child: Arc<SharedChild>) {
debug!("Starting to monitor hangup for Microdroid({})", child.id());
- let (_, result) = self
+ let (state, result) = self
.payload_state_updated
.wait_timeout_while(self.payload_state.lock().unwrap(), *BOOT_HANGUP_TIMEOUT, |s| {
*s < PayloadState::Started
})
.unwrap();
+ drop(state); // we are not interested in state
let child_still_running = child.try_wait().ok() == Some(None);
if result.timed_out() && child_still_running {
error!(
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index c479691..fb89772 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -48,7 +48,7 @@
java: {
sdk_version: "module_current",
apex_available: [
- "//apex_available:platform",
+ "com.android.virt",
],
},
rust: {
diff --git a/virtualizationservice/src/maintenance.rs b/virtualizationservice/src/maintenance.rs
index f950db9..8efc58d 100644
--- a/virtualizationservice/src/maintenance.rs
+++ b/virtualizationservice/src/maintenance.rs
@@ -40,6 +40,9 @@
/// parcel fits within max AIDL message size.
const DELETE_MAX_BATCH_SIZE: usize = 100;
+/// Maximum number of VM IDs that a single app can have.
+const MAX_VM_IDS_PER_APP: usize = 400;
+
/// State related to VM secrets.
pub struct State {
sk: binder::Strong<dyn ISecretkeeper>,
@@ -101,6 +104,24 @@
pub fn add_id(&mut self, vm_id: &VmId, user_id: u32, app_id: u32) -> Result<()> {
let user_id: i32 = user_id.try_into().context(format!("user_id {user_id} out of range"))?;
let app_id: i32 = app_id.try_into().context(format!("app_id {app_id} out of range"))?;
+
+ // To prevent unbounded growth of VM IDs (and the associated state) for an app, limit the
+ // number of VM IDs per app.
+ let count = self
+ .vm_id_db
+ .count_vm_ids_for_app(user_id, app_id)
+ .context("failed to determine VM count")?;
+ if count >= MAX_VM_IDS_PER_APP {
+ // The owner has too many VM IDs, so delete the oldest IDs so that the new VM ID
+ // creation can progress/succeed.
+ let purge = 1 + count - MAX_VM_IDS_PER_APP;
+ let old_vm_ids = self
+ .vm_id_db
+ .oldest_vm_ids_for_app(user_id, app_id, purge)
+ .context("failed to find oldest VM IDs")?;
+ error!("Deleting {purge} of {count} VM IDs for user_id={user_id}, app_id={app_id}");
+ self.delete_ids(&old_vm_ids);
+ }
self.vm_id_db.add_vm_id(vm_id, user_id, app_id)
}
@@ -396,6 +417,39 @@
assert_eq!(vec![VM_ID5], sk_state.vm_id_db.vm_ids_for_user(USER3).unwrap());
}
+ #[test]
+ fn test_sk_state_too_many_vms() {
+ let history = Arc::new(Mutex::new(Vec::new()));
+ let mut sk_state = new_test_state(history.clone(), 20);
+
+ // Every VM ID added up to the limit is kept.
+ for idx in 0..MAX_VM_IDS_PER_APP {
+ let mut vm_id = [0u8; 64];
+ vm_id[0..8].copy_from_slice(&(idx as u64).to_be_bytes());
+ sk_state.add_id(&vm_id, USER1 as u32, APP_A as u32).unwrap();
+ assert_eq!(idx + 1, sk_state.vm_id_db.count_vm_ids_for_app(USER1, APP_A).unwrap());
+ }
+ assert_eq!(
+ MAX_VM_IDS_PER_APP,
+ sk_state.vm_id_db.count_vm_ids_for_app(USER1, APP_A).unwrap()
+ );
+
+ // Beyond the limit it's one in, one out.
+ for idx in MAX_VM_IDS_PER_APP..MAX_VM_IDS_PER_APP + 10 {
+ let mut vm_id = [0u8; 64];
+ vm_id[0..8].copy_from_slice(&(idx as u64).to_be_bytes());
+ sk_state.add_id(&vm_id, USER1 as u32, APP_A as u32).unwrap();
+ assert_eq!(
+ MAX_VM_IDS_PER_APP,
+ sk_state.vm_id_db.count_vm_ids_for_app(USER1, APP_A).unwrap()
+ );
+ }
+ assert_eq!(
+ MAX_VM_IDS_PER_APP,
+ sk_state.vm_id_db.count_vm_ids_for_app(USER1, APP_A).unwrap()
+ );
+ }
+
struct Irreconcilable;
impl IVirtualizationReconciliationCallback for Irreconcilable {
diff --git a/virtualizationservice/src/maintenance/vmdb.rs b/virtualizationservice/src/maintenance/vmdb.rs
index 47704bc..273f340 100644
--- a/virtualizationservice/src/maintenance/vmdb.rs
+++ b/virtualizationservice/src/maintenance/vmdb.rs
@@ -272,6 +272,34 @@
Ok(vm_ids)
}
+ /// 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
+ .conn
+ .prepare("SELECT COUNT(vm_id) FROM main.vmids WHERE user_id = ? AND app_id = ?;")
+ .context("failed to prepare SELECT stmt")?;
+ stmt.query_row(params![user_id, app_id], |row| row.get(0)).context("query failed")
+ }
+
+ /// Return the `count` oldest VM IDs associated with `(user_id, app_id)`.
+ pub fn oldest_vm_ids_for_app(
+ &mut self,
+ user_id: i32,
+ app_id: i32,
+ count: usize,
+ ) -> Result<Vec<VmId>> {
+ // SQLite considers NULL columns to be smaller than values, so rows left over from a v0
+ // database will be listed first.
+ let mut stmt = self
+ .conn
+ .prepare(
+ "SELECT vm_id FROM main.vmids WHERE user_id = ? AND app_id = ? ORDER BY created LIMIT ?;",
+ )
+ .context("failed to prepare SELECT stmt")?;
+ let rows = stmt.query(params![user_id, app_id, count]).context("query failed")?;
+ Self::vm_ids_from_rows(rows)
+ }
+
/// Return all of the `(user_id, app_id)` pairs present in the database.
pub fn get_all_owners(&mut self) -> Result<Vec<(i32, i32)>> {
let mut stmt = self
@@ -344,6 +372,19 @@
fn show_contents(db: &VmIdDb) {
let mut stmt = db.conn.prepare("SELECT * FROM main.vmids;").unwrap();
let mut rows = stmt.query(()).unwrap();
+ println!("DB contents:");
+ while let Some(row) = rows.next().unwrap() {
+ println!(" {row:?}");
+ }
+ }
+
+ fn show_contents_for_app(db: &VmIdDb, user_id: i32, app_id: i32, count: usize) {
+ let mut stmt = db
+ .conn
+ .prepare("SELECT vm_id, created FROM main.vmids WHERE user_id = ? AND app_id = ? ORDER BY created LIMIT ?;")
+ .unwrap();
+ let mut rows = stmt.query(params![user_id, app_id, count]).unwrap();
+ println!("First (by created) {count} rows for app_id={app_id}");
while let Some(row) = rows.next().unwrap() {
println!(" {row:?}");
}
@@ -457,31 +498,39 @@
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_app(USER1, APP_A).unwrap());
+ assert_eq!(3, db.count_vm_ids_for_app(USER1, APP_A).unwrap());
assert_eq!(vec![VM_ID4], db.vm_ids_for_app(USER2, APP_B).unwrap());
+ assert_eq!(1, db.count_vm_ids_for_app(USER2, APP_B).unwrap());
assert_eq!(vec![VM_ID5], db.vm_ids_for_user(USER3).unwrap());
assert_eq!(empty, db.vm_ids_for_user(USER_UNKNOWN).unwrap());
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());
db.delete_vm_ids(&[VM_ID2, VM_ID3]).unwrap();
assert_eq!(vec![VM_ID1], db.vm_ids_for_user(USER1).unwrap());
assert_eq!(vec![VM_ID1], db.vm_ids_for_app(USER1, APP_A).unwrap());
+ assert_eq!(1, db.count_vm_ids_for_app(USER1, APP_A).unwrap());
// OK to delete things that don't exist.
db.delete_vm_ids(&[VM_ID2, VM_ID3]).unwrap();
assert_eq!(vec![VM_ID1], db.vm_ids_for_user(USER1).unwrap());
assert_eq!(vec![VM_ID1], db.vm_ids_for_app(USER1, APP_A).unwrap());
+ assert_eq!(1, db.count_vm_ids_for_app(USER1, APP_A).unwrap());
db.add_vm_id(&VM_ID2, USER1, APP_A).unwrap();
db.add_vm_id(&VM_ID3, USER1, APP_A).unwrap();
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_app(USER1, APP_A).unwrap());
+ assert_eq!(3, db.count_vm_ids_for_app(USER1, APP_A).unwrap());
assert_eq!(vec![VM_ID4], db.vm_ids_for_app(USER2, APP_B).unwrap());
+ assert_eq!(1, db.count_vm_ids_for_app(USER2, APP_B).unwrap());
assert_eq!(vec![VM_ID5], db.vm_ids_for_user(USER3).unwrap());
assert_eq!(empty, db.vm_ids_for_user(USER_UNKNOWN).unwrap());
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_eq!(
vec![(USER1, APP_A), (USER2, APP_B), (USER3, APP_C)],
@@ -513,4 +562,47 @@
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
show_contents(&db);
}
+
+ #[test]
+ fn test_remove_oldest_with_upgrade() {
+ let mut db = new_test_db_version(0);
+ let version = db.schema_version().unwrap();
+ assert_eq!(0, version);
+
+ let remove_count = 10;
+ let mut want = vec![];
+
+ // Manually insert rows before upgrade.
+ const V0_COUNT: usize = 5;
+ for idx in 0..V0_COUNT {
+ let mut vm_id = [0u8; 64];
+ vm_id[0..8].copy_from_slice(&(idx as u64).to_be_bytes());
+ if want.len() < remove_count {
+ want.push(vm_id);
+ }
+ db.conn
+ .execute(
+ "REPLACE INTO main.vmids (vm_id, user_id, app_id) VALUES (?1, ?2, ?3);",
+ params![&vm_id, &USER1, APP_A],
+ )
+ .unwrap();
+ }
+
+ // Now move to v1.
+ db.upgrade_tables_v0_v1().unwrap();
+ let version = db.schema_version().unwrap();
+ assert_eq!(1, version);
+
+ for idx in V0_COUNT..40 {
+ let mut vm_id = [0u8; 64];
+ vm_id[0..8].copy_from_slice(&(idx as u64).to_be_bytes());
+ if want.len() < remove_count {
+ want.push(vm_id);
+ }
+ db.add_vm_id(&vm_id, USER1, APP_A).unwrap();
+ }
+ show_contents_for_app(&db, USER1, APP_A, 10);
+ let got = db.oldest_vm_ids_for_app(USER1, APP_A, 10).unwrap();
+ assert_eq!(got, want);
+ }
}
diff --git a/vm_payload/README.md b/vm_payload/README.md
index 419d854..4b1e6f3 100644
--- a/vm_payload/README.md
+++ b/vm_payload/README.md
@@ -2,7 +2,7 @@
This directory contains the definition of the VM Payload API. This is a native
API, exposed as a set of C functions, available to payload code running inside a
-[Microdroid](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/microdroid/README.md)
+[Microdroid](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/microdroid/README.md)
VM.
Note that only native code is supported in Microdroid, so no Java APIs are
@@ -17,7 +17,7 @@
under the `lib/<ABI>` directory, like other JNI code.
The primary .so, which is specified as part of the VM configuration via
-[VirtualMachineConfig.Builder#setPayloadBinaryPath](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java),
+[VirtualMachineConfig.Builder#setPayloadBinaryPath](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java),
must define the entry point for the payload.
This entry point is a C function called `AVmPayload_main()`, as declared in
@@ -36,7 +36,7 @@
runtime from the real `libvm_payload.so` hosted within the Microdroid VM.
See `MicrodroidTestNativeLib` in the [test
-APK](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/tests/testapk/Android.bp)
+APK](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/tests/testapk/Android.bp)
for an example.
In other build systems a similar stub `libvm_payload.so` can be built using
diff --git a/vmbase/src/bionic.rs b/vmbase/src/bionic.rs
index a049616..63a6894 100644
--- a/vmbase/src/bionic.rs
+++ b/vmbase/src/bionic.rs
@@ -14,17 +14,17 @@
//! Low-level compatibility layer between baremetal Rust and Bionic C functions.
-use core::ffi::c_char;
-use core::ffi::c_int;
-use core::ffi::c_void;
-use core::ffi::CStr;
-use core::slice;
-use core::str;
-
use crate::console;
use crate::eprintln;
use crate::rand::fill_with_entropy;
use crate::read_sysreg;
+use core::ffi::c_char;
+use core::ffi::c_int;
+use core::ffi::c_void;
+use core::ffi::CStr;
+use core::ptr::addr_of_mut;
+use core::slice;
+use core::str;
use cstr::cstr;
@@ -75,7 +75,7 @@
unsafe extern "C" fn __errno() -> *mut c_int {
// SAFETY: C functions which call this are only called from the main thread, not from exception
// handlers.
- unsafe { &mut ERRNO as *mut _ }
+ unsafe { addr_of_mut!(ERRNO) as *mut _ }
}
fn set_errno(value: c_int) {
diff --git a/vmlauncher_app/Android.bp b/vmlauncher_app/Android.bp
index 06dcf7a..f9c325c 100644
--- a/vmlauncher_app/Android.bp
+++ b/vmlauncher_app/Android.bp
@@ -21,4 +21,7 @@
],
platform_apis: true,
privileged: true,
+ apex_available: [
+ "com.android.virt",
+ ],
}
diff --git a/vmlauncher_app/AndroidManifest.xml b/vmlauncher_app/AndroidManifest.xml
index 860c03f..607a895 100644
--- a/vmlauncher_app/AndroidManifest.xml
+++ b/vmlauncher_app/AndroidManifest.xml
@@ -8,6 +8,7 @@
<application
android:label="VmLauncherApp">
<activity android:name=".MainActivity"
+ android:enabled="false"
android:screenOrientation="landscape"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
android:theme="@style/MyTheme"