Merge "Allow host-controlled avf/untrusted prop" into main
diff --git a/README.md b/README.md
index 3935f93..210f70e 100644
--- a/README.md
+++ b/README.md
@@ -29,3 +29,4 @@
* [Building and running a demo app in C++](demo_native/README.md)
* [Debugging](docs/debug)
* [Using custom VM](docs/custom_vm.md)
+* [Device assignment](docs/device_assignment.md)
\ No newline at end of file
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4da96c8..f146b4e 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -12,6 +12,9 @@
"name": "MicrodroidTestApp"
},
{
+ "name": "VmAttestationTestApp"
+ },
+ {
"name": "CustomPvmfwHostTestCases"
},
{
diff --git a/docs/device_assignment.md b/docs/device_assignment.md
new file mode 100644
index 0000000..4c5b477
--- /dev/null
+++ b/docs/device_assignment.md
@@ -0,0 +1,239 @@
+# Getting started with device assignment
+
+Device assignment allows a VM to have direct access to HW without host/hyp
+intervention. AVF uses `vfio-platform` for device assignment, and host kernel
+support is required.
+
+This document explains how to setup and launch VM with device assignments.
+
+## VM device assignment DTBO (a.k.a. VM DTBO)
+
+For device assignment, a VM device assignment DTBO (a.k.a. VM DTBO) is required.
+VM DTBO is a device tree overlay which describes all assignable devices
+information. Information includes physical reg, IOMMU, device properties, and
+dependencies.
+
+VM DTBO allows to pass extra properties of assignable platform
+devices to the VM (which can't be discovered from the HW) while keeping the VMM
+device-agnostic.
+
+When the host boots, the bootloader provides VM DTBO to both Android and pvmfw.
+
+When a VM boots, the VMM selectively applies the DTBO based from provided
+labels, describing the assigned devices.
+
+## Prepare VM DTBO
+
+VM DTBO should be included in the dtbo partition. It should be in its own
+entry, and not together with any host OS's. See [DTB/DTBO Paritions] for
+partition format.
+
+[DTB/DTBO Paritions]: https://source.android.com/docs/core/architecture/dto/partitions
+
+### Write VM DTS for VM DTBO
+
+DTBO is compiled from device tree source (DTS) with `dtc` tool. [DTBO syntax]
+explains basic syntax of DTS.
+
+[DTBO syntax]: https://source.android.com/docs/core/architecture/dto/syntax
+
+Here are details and requirements:
+
+#### Describe assignable devices
+
+VM DTBO should describe assignable devices and their labels.
+
+* VM DTBO should have assignable devices in the `&{/}`, so it can be
+ overlaid onto VM DT. Assignable devices should be backed by physical device.
+ * We only support overlaying onto root node (i.e. `&{/}`) to prevent
+ unexpected modification of VM DT.
+* VM DTBO should have labels for assignable devices, so AVF can recognize
+ assignable device list. Labels should point to valid 'overlayable' nodes.
+ * Overlayable node is a node that would be applied to the base device tree
+ when DTBO is applied.
+
+#### Describe physical devices and physical IOMMUs
+
+VM DTBO should describe a `/host` node which describes physical devices and
+physical IOMMUs. The `/host` node only describes information for verification of
+assigned devices, and wouldn't be applied to VM DT. Here are details:
+
+* Physical IOMMU nodes
+ * IOMMU nodes must have a phandle to be referenced by a physical device node.
+ * IOMMU nodes must have `<android,pvmfw,token>` property. The property
+ describes the IOMMU token. An IOMMU token is a hypervisor-specific `<u64>`
+ which uniquely identifies a physical IOMMU. IOMMU token must be constant
+ across the VM boot for provisioning by pvmfw remains valid. The token must
+ be kept up-to-date across hypervisor updates.
+ * IOMMU nodes should be multi-master IOMMUs. (i.e. `#iommu-cells = <1>`)
+ * Other `#iommu-cells` values aren't supported for now.
+ * See: [Device tree binding for IOMMUs][IOMMU]
+* Physical device nodes
+ * Physical device nodes must have a `<android,pvmfw,target>` property that
+ references an overlayable node. The overlayable node contains the properties
+ that would be included in VM DT.
+ * Physical device nodes must have `<reg>` property to provide physical
+ regions.
+ * Physical device nodes can optionally contain `<iommus>` property. The
+ property is a prop-encoded-array and contains a number of
+ (iommu phandle, SID) pairs.
+ * IOMMU can be shared among devices, but should use distinct SIDs. Sharing
+ the same IOMMU-SID pair among multiple devices isn't supported for now.
+
+[IOMMU]: https://www.kernel.org/doc/Documentation/devicetree/bindings/iommu/iommu.txt
+
+#### Describe dependencies
+
+VM DTBO may have dependencies via phandle references. When a device node is
+assigned, dependencies of the node are also applied to VM DT.
+
+When dependencies are applied, siblings or children nodes of dependencies are
+ignored unless explicitly referenced.
+
+#### VM DTBO example
+
+Here's a simple example device tree source with four assignable devices nodes.
+
+```dts
+/dts-v1/;
+/plugin/;
+
+/ {
+ // host node describes physical devices and IOMMUs, and wouldn't be applied to VM DT
+ host {
+ #address-cells = <0x2>;
+ #size-cells = <0x1>;
+ rng {
+ reg = <0x0 0x12f00000 0x1000>;
+ iommus = <&iommu0 0x3>;
+ android,pvmfw,target = <&rng>;
+ };
+ light {
+ reg = <0x0 0x00f00000 0x1000>, <0x0 0x00f10000 0x1000>;
+ iommus = <&iommu1 0x4>, <&iommu2 0x5>;
+ android,pvmfw,target = <&light>;
+ };
+ led {
+ reg = <0x0 0x12000000 0x1000>;
+ iommus = <&iommu1 0x3>;
+ android,pvmfw,target = <&led>;
+ };
+ bus0 {
+ #address-cells = <0x1>;
+ #size-cells = <0x1>;
+ backlight {
+ reg = <0x300 0x100>;
+ android,pvmfw,target = <&backlight>;
+ };
+ };
+ iommu0: iommu0 {
+ #iommu-cells = <0x1>;
+ android,pvmfw,token = <0x0 0x12e40000>;
+ };
+ iommu1: iommu1 {
+ #iommu-cells = <0x1>;
+ android,pvmfw,token = <0x0 0x40000>;
+ };
+ iommu2: iommu2 {
+ #iommu-cells = <0x1>;
+ android,pvmfw,token = <0x0 0x50000>;
+ };
+ };
+};
+
+// Beginning of the assignable devices. Assigned devices would be applied to VM DT
+&{/} { // We only allows to overlay to root node
+ rng: rng {
+ compatible = "android,rng";
+ android,rng,ignore-gctrl-reset;
+ };
+ light: light {
+ compatible = "android,light";
+ version = <0x1 0x2>;
+ };
+ led: led {
+ compatible = "android,led";
+ prop = <0x555>;
+ };
+ bus0 {
+ backlight: backlight {
+ compatible = "android,backlight";
+ android,backlight,ignore-gctrl-reset;
+ };
+ };
+};
+```
+
+If you compile the above with `dtc -@`, then you'll get `__symbols__` for free.
+The generated `__symbols__` indicates that there are four assignable devices.
+
+```dts
+ // generated __symbols__. AVF will ignore non-overlayable nodes.
+ __symbols__ {
+ iommu0 = "/host/iommu0";
+ iommu1 = "/host/iommu1";
+ iommu2 = "/host/iommu2";
+ rng = "/fragment@rng/__overlay__/rng";
+ light = "/fragment@sensor/__overlay__/light";
+ led = "/fragment@led/__overlay__/led";
+ backlight = "/fragment@backlight/__overlay__/bus0/backlight";
+ };
+```
+
+## Prepare AVF assignable devices XML
+
+AVF requires assignable device information to unbind from the host device driver
+and bind to VFIO driver. The information should be provided in an XML file at
+`/vendor/etc/avf/assignable_devices.xml`.
+
+Here's example.
+
+```xml
+<devices>
+ <device>
+ <kind>sensor</kind>
+ <dtbo_label>light</dtbo_label>
+ <sysfs_path>/sys/bus/platform/devices/16d00000.light</sysfs_path>
+ </device>
+</devices>
+```
+
+* `<kind>`: Device kind. Currently only used for debugging purposes and not used
+ for device assignment.
+* `<dtbo_label>`: Label in the VM DTBO (i.e. symbols in `__symbols__`). Must be
+ unique.
+* `<sysfs_path>`: Sysfs path of the device in host, used to bind to the VFIO
+ driver. Must be unique in the XML.
+
+## Boot with VM DTBO
+
+Bootloader should provide VM DTBO to both Android and pvmfw.
+
+### Provide VM DTBO index in dtbo.img
+
+Bootloader should provide the VM DTBO index with sysprop
+`ro.boot.hypervisor.vm_dtbo_idx.`. DTBO index represents DTBO location in
+dtbo.img.
+
+### Provide VM DTBO in the pvmfw config
+
+For protected VM, bootloader must provide VM DTBO to the pvmfw. pvmfw sanitizes
+incoming device tree with the VM DTBO.
+
+For more detail about providing VM DTBO in pvmfw,
+see: [pvmfw/README.md](../pvmfw/README.md#configuration-data-format)
+
+
+## Launch VM with device assignment
+
+We don't support client API yet in Android V, but you can use CLI to test device
+assignment. Note that host kernel support is required.
+
+Specify `--devices ${sysfs_path}` when booting VM. The parameter can be repeated
+multiple times for specifying multiple devices.
+
+Here's an example:
+
+```sh
+adb shell /apex/com.android.virt/bin/vm run-microdroid --devices /sys/bus/platform/devices/16d00000.light
+```
\ No newline at end of file
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 5aff93f..3ea50e2 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -2,6 +2,7 @@
package android.system.virtualmachine {
public class VirtualMachine implements java.lang.AutoCloseable {
+ method @FlaggedApi("RELEASE_AVF_ENABLE_REMOTE_ATTESTATION") @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public void enableTestAttestation() throws android.system.virtualmachine.VirtualMachineException;
method @NonNull @WorkerThread public java.io.OutputStream getConsoleInput() throws android.system.virtualmachine.VirtualMachineException;
method @NonNull public java.io.File getRootDir();
}
@@ -26,6 +27,7 @@
method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public boolean isFeatureEnabled(String) throws android.system.virtualmachine.VirtualMachineException;
field public static final String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
field public static final String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+ field public static final String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
field public static final String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 5025e88..b4ba00b 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -42,6 +42,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -1202,6 +1203,28 @@
}
/**
+ * Enables the VM to request attestation in testing mode.
+ *
+ * <p>This function provisions a key pair for the VM attestation testing, a fake certificate
+ * will be associated to the fake key pair when the VM requests attestation in testing mode.
+ *
+ * <p>The provisioned key pair can only be used in subsequent calls to {@link
+ * AVmPayload_requestAttestationForTesting} within a running VM.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
+ @FlaggedApi("RELEASE_AVF_ENABLE_REMOTE_ATTESTATION")
+ public void enableTestAttestation() throws VirtualMachineException {
+ try {
+ mVirtualizationService.getBinder().enableTestAttestation();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM
* needs to be stopped to avoid inconsistency in its state representation.
*
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 1607c0a..1a4b53a 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -116,7 +116,12 @@
@Retention(RetentionPolicy.SOURCE)
@StringDef(
prefix = "FEATURE_",
- value = {FEATURE_DICE_CHANGES, FEATURE_MULTI_TENANT, FEATURE_VENDOR_MODULES})
+ value = {
+ FEATURE_DICE_CHANGES,
+ FEATURE_MULTI_TENANT,
+ FEATURE_REMOTE_ATTESTATION,
+ FEATURE_VENDOR_MODULES
+ })
public @interface Features {}
/**
@@ -136,6 +141,15 @@
public static final String FEATURE_MULTI_TENANT = IVirtualizationService.FEATURE_MULTI_TENANT;
/**
+ * Feature to allow remote attestation in Microdroid.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String FEATURE_REMOTE_ATTESTATION =
+ IVirtualizationService.FEATURE_REMOTE_ATTESTATION;
+
+ /**
* Feature to allow vendor modules in Microdroid.
*
* @hide
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index ab3c83f..d1ab24e 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -459,7 +459,7 @@
}
/// Returns the first subnode of this
- pub fn first_subnode(&'a mut self) -> Result<Option<Self>> {
+ pub fn first_subnode(self) -> Result<Option<Self>> {
let offset = self.fdt.first_subnode(self.offset)?;
Ok(offset.map(|offset| Self { fdt: self.fdt, offset }))
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index cafbf97..ddc4538 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -378,7 +378,7 @@
let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
let fdt = Fdt::from_mut_slice(&mut data).unwrap();
- let mut root = fdt.root_mut().unwrap();
+ let root = fdt.root_mut().unwrap();
let mut subnode_iter = root.first_subnode().unwrap();
while let Some(subnode) = subnode_iter {
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 4813b35..b7a539b 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -107,9 +107,13 @@
* serving as proof of the freshness of the result.
*
* @param challenge the maximum supported challenge size is 64 bytes.
+ * @param testMode whether the attestation is only for testing purposes. If testMode is true,
+ * caller must invoke {@link VirtualMachineManager#enableTestAttestation} prior to
+ * calling this method to provision a key pair to sign the attested result, and the returned
+ * certificate chain will not be RKP server rooted.
*
* @return An {@link AttestationResult} parcelable containing an attested key pair and its
* certification chain.
*/
- AttestationResult requestAttestation(in byte[] challenge);
+ AttestationResult requestAttestation(in byte[] challenge, in boolean testMode);
}
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 20a1b89..959197a 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -68,7 +68,11 @@
Ok(self.secret.dice_artifacts().cdi_attest().to_vec())
}
- fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<AttestationResult> {
+ fn requestAttestation(
+ &self,
+ challenge: &[u8],
+ test_mode: bool,
+ ) -> binder::Result<AttestationResult> {
self.check_restricted_apis_allowed()?;
let ClientVmAttestationData { private_key, csr } =
generate_attestation_key_and_csr(challenge, self.secret.dice_artifacts())
@@ -88,7 +92,7 @@
)
})
.with_log()?;
- let cert_chain = self.virtual_machine_service.requestAttestation(&csr)?;
+ let cert_chain = self.virtual_machine_service.requestAttestation(&csr, test_mode)?;
Ok(AttestationResult {
privateKey: private_key.as_slice().to_vec(),
certificateChain: cert_chain,
diff --git a/pvmfw/README.md b/pvmfw/README.md
index d7884fb..2758a5d 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -195,7 +195,8 @@
In version 1.1, a third blob is added.
-- entry 2 may point to a [DTBO] that describes VM DTBO for device assignment.
+- entry 2 may point to a [DTBO] that describes VM DA DTBO for
+ [device assignment][device_assignment].
pvmfw will provision assigned devices with the VM DTBO.
In version 1.2, a fourth blob is added.
@@ -225,6 +226,7 @@
[header]: src/config.rs
[DTBO]: https://android.googlesource.com/platform/external/dtc/+/refs/heads/main/Documentation/dt-object-internal.txt
[debug_policy]: ../docs/debug/README.md#debug-policy
+[device_assignment]: ../docs/device_assignment.md
[secretkeeper_key]: https://android.googlesource.com/platform/system/secretkeeper/+/refs/heads/main/README.md#secretkeeper-public-key
[vendor_hashtree_digest]: ../microdroid/README.md#verification-of-vendor-image
diff --git a/pvmfw/platform.dts b/pvmfw/platform.dts
index 9abc123..cea1c33 100644
--- a/pvmfw/platform.dts
+++ b/pvmfw/platform.dts
@@ -57,96 +57,497 @@
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <0>;
+ operating-points-v2 = <&opp_table0>;
+ opp_table0: opp-table-0 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@1 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <1>;
+ operating-points-v2 = <&opp_table1>;
+ opp_table1: opp-table-1 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@2 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <2>;
+ operating-points-v2 = <&opp_table2>;
+ opp_table2: opp-table-2 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@3 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <3>;
+ operating-points-v2 = <&opp_table3>;
+ opp_table3: opp-table-3 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@4 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <4>;
+ operating-points-v2 = <&opp_table4>;
+ opp_table4: opp-table-4 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@5 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <5>;
+ operating-points-v2 = <&opp_table5>;
+ opp_table5: opp-table-5 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@6 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <6>;
+ operating-points-v2 = <&opp_table6>;
+ opp_table6: opp-table-6 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@7 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <7>;
+ operating-points-v2 = <&opp_table7>;
+ opp_table7: opp-table-7 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@8 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <8>;
+
+ operating-points-v2 = <&opp_table8>;
+ opp_table8: opp-table-8 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@9 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <9>;
+ operating-points-v2 = <&opp_table9>;
+ opp_table9: opp-table-9 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@10 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <10>;
+ operating-points-v2 = <&opp_table10>;
+ opp_table10: opp-table-10 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@11 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <11>;
+ operating-points-v2 = <&opp_table11>;
+ opp_table11: opp-table-11 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@12 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <12>;
+ operating-points-v2 = <&opp_table12>;
+ opp_table12: opp-table-12 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@13 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <13>;
+ operating-points-v2 = <&opp_table13>;
+ opp_table13: opp-table-13 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@14 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <14>;
+ operating-points-v2 = <&opp_table14>;
+ opp_table14: opp-table-14 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
cpu@15 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
reg = <15>;
+ operating-points-v2 = <&opp_table15>;
+ opp_table15: opp-table-15 {
+ compatible = "operating-points-v2";
+
+ opp1 { opp-hz = <PLACEHOLDER2>; };
+ opp2 { opp-hz = <PLACEHOLDER2>; };
+ opp3 { opp-hz = <PLACEHOLDER2>; };
+ opp4 { opp-hz = <PLACEHOLDER2>; };
+ opp5 { opp-hz = <PLACEHOLDER2>; };
+ opp6 { opp-hz = <PLACEHOLDER2>; };
+ opp7 { opp-hz = <PLACEHOLDER2>; };
+ opp8 { opp-hz = <PLACEHOLDER2>; };
+ opp9 { opp-hz = <PLACEHOLDER2>; };
+ opp10 { opp-hz = <PLACEHOLDER2>; };
+ opp11 { opp-hz = <PLACEHOLDER2>; };
+ opp12 { opp-hz = <PLACEHOLDER2>; };
+ opp13 { opp-hz = <PLACEHOLDER2>; };
+ opp14 { opp-hz = <PLACEHOLDER2>; };
+ opp15 { opp-hz = <PLACEHOLDER2>; };
+ opp16 { opp-hz = <PLACEHOLDER2>; };
+ opp17 { opp-hz = <PLACEHOLDER2>; };
+ opp18 { opp-hz = <PLACEHOLDER2>; };
+ opp19 { opp-hz = <PLACEHOLDER2>; };
+ opp20 { opp-hz = <PLACEHOLDER2>; };
+ };
};
};
@@ -321,4 +722,9 @@
id = <PLACEHOLDER>;
#iommu-cells = <1>;
};
+
+ cpufreq {
+ compatible = "virtual,android-v-only-cpufreq";
+ reg = <0x0 0x1040000 PLACEHOLDER2>;
+ };
};
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index ac52be9..f20451a 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -35,6 +35,7 @@
use libfdt::CellIterator;
use libfdt::Fdt;
use libfdt::FdtError;
+use libfdt::FdtNode;
use libfdt::FdtNodeMut;
use log::debug;
use log::error;
@@ -54,12 +55,17 @@
pub enum FdtValidationError {
/// Invalid CPU count.
InvalidCpuCount(usize),
+ /// Invalid VCpufreq Range.
+ InvalidVcpufreq(u64, u64),
}
impl fmt::Display for FdtValidationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InvalidCpuCount(num_cpus) => write!(f, "Invalid CPU count: {num_cpus}"),
+ Self::InvalidVcpufreq(addr, size) => {
+ write!(f, "Invalid vcpufreq region: ({addr:#x}, {size:#x})")
+ }
}
}
}
@@ -172,15 +178,41 @@
.setprop_inplace(cstr!("reg"), [addr.to_be(), size.to_be()].as_bytes())
}
+//TODO: Need to add info for cpu capacity
#[derive(Debug, Default)]
-struct CpuInfo {}
+struct CpuInfo {
+ opptable_info: Option<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>>,
+}
+
+impl CpuInfo {
+ const MAX_OPPTABLES: usize = 20;
+}
+
+fn read_opp_info_from(
+ opp_node: FdtNode,
+) -> libfdt::Result<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>> {
+ let mut table = ArrayVec::new();
+ for subnode in opp_node.subnodes()? {
+ let prop = subnode.getprop_u64(cstr!("opp-hz"))?.ok_or(FdtError::NotFound)?;
+ table.push(prop);
+ }
+
+ Ok(table)
+}
fn read_cpu_info_from(fdt: &Fdt) -> libfdt::Result<ArrayVec<[CpuInfo; DeviceTreeInfo::MAX_CPUS]>> {
let mut cpus = ArrayVec::new();
-
let mut cpu_nodes = fdt.compatible_nodes(cstr!("arm,arm-v8"))?;
- for _cpu in cpu_nodes.by_ref().take(cpus.capacity()) {
- let info = CpuInfo {};
+ for cpu in cpu_nodes.by_ref().take(cpus.capacity()) {
+ let opp_phandle = cpu.getprop_u32(cstr!("operating-points-v2"))?;
+ let opptable_info = if let Some(phandle) = opp_phandle {
+ let phandle = phandle.try_into()?;
+ let node = fdt.node_with_phandle(phandle)?.ok_or(FdtError::NotFound)?;
+ Some(read_opp_info_from(node)?)
+ } else {
+ None
+ };
+ let info = CpuInfo { opptable_info };
cpus.push(info);
}
if cpu_nodes.next().is_some() {
@@ -198,12 +230,87 @@
Ok(())
}
+fn read_vcpufreq_info(fdt: &Fdt) -> libfdt::Result<Option<VcpufreqInfo>> {
+ let mut nodes = fdt.compatible_nodes(cstr!("virtual,android-v-only-cpufreq"))?;
+ let Some(node) = nodes.next() else {
+ return Ok(None);
+ };
+
+ if nodes.next().is_some() {
+ warn!("DT has more than 1 cpufreq node: discarding extra nodes.");
+ }
+
+ let mut regs = node.reg()?.ok_or(FdtError::NotFound)?;
+ let reg = regs.next().ok_or(FdtError::NotFound)?;
+ let size = reg.size.ok_or(FdtError::NotFound)?;
+
+ Ok(Some(VcpufreqInfo { addr: reg.addr, size }))
+}
+
+fn validate_vcpufreq_info(
+ vcpufreq_info: &VcpufreqInfo,
+ cpus: &[CpuInfo],
+) -> Result<(), FdtValidationError> {
+ const VCPUFREQ_BASE_ADDR: u64 = 0x1040000;
+ const VCPUFREQ_SIZE_PER_CPU: u64 = 0x8;
+
+ let base = vcpufreq_info.addr;
+ let size = vcpufreq_info.size;
+ let expected_size = VCPUFREQ_SIZE_PER_CPU * cpus.len() as u64;
+
+ if (base, size) != (VCPUFREQ_BASE_ADDR, expected_size) {
+ return Err(FdtValidationError::InvalidVcpufreq(base, size));
+ }
+
+ Ok(())
+}
+
+fn patch_opptable(
+ node: FdtNodeMut,
+ opptable: Option<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>>,
+) -> libfdt::Result<()> {
+ let oppcompat = cstr!("operating-points-v2");
+ let next = node.next_compatible(oppcompat)?.ok_or(FdtError::NoSpace)?;
+
+ let Some(opptable) = opptable else {
+ return next.nop();
+ };
+
+ let mut next_subnode = next.first_subnode()?;
+
+ for entry in opptable {
+ let mut subnode = next_subnode.ok_or(FdtError::NoSpace)?;
+ subnode.setprop_inplace(cstr!("opp-hz"), &entry.to_be_bytes())?;
+ next_subnode = subnode.next_subnode()?;
+ }
+
+ while let Some(current) = next_subnode {
+ next_subnode = current.delete_and_next_subnode()?;
+ }
+
+ Ok(())
+}
+
+// TODO(ptosi): Rework FdtNodeMut and replace this function.
+fn get_nth_compatible<'a>(
+ fdt: &'a mut Fdt,
+ n: usize,
+ compat: &CStr,
+) -> libfdt::Result<Option<FdtNodeMut<'a>>> {
+ let mut node = fdt.root_mut()?.next_compatible(compat)?;
+ for _ in 0..n {
+ node = node.ok_or(FdtError::NoSpace)?.next_compatible(compat)?;
+ }
+ Ok(node)
+}
+
fn patch_cpus(fdt: &mut Fdt, cpus: &[CpuInfo]) -> libfdt::Result<()> {
const COMPAT: &CStr = cstr!("arm,arm-v8");
- let mut next = fdt.root_mut()?.next_compatible(COMPAT)?;
- for _cpu in cpus {
- next = next.ok_or(FdtError::NoSpace)?.next_compatible(COMPAT)?;
+ for (idx, cpu) in cpus.iter().enumerate() {
+ let cur = get_nth_compatible(fdt, idx, COMPAT)?.ok_or(FdtError::NoSpace)?;
+ patch_opptable(cur, cpu.opptable_info)?;
}
+ let mut next = get_nth_compatible(fdt, cpus.len(), COMPAT)?;
while let Some(current) = next {
next = current.delete_and_next_compatible(COMPAT)?;
}
@@ -628,6 +735,21 @@
}
#[derive(Debug)]
+struct VcpufreqInfo {
+ addr: u64,
+ size: u64,
+}
+
+fn patch_vcpufreq(fdt: &mut Fdt, vcpufreq_info: &Option<VcpufreqInfo>) -> libfdt::Result<()> {
+ let mut node = fdt.node_mut(cstr!("/cpufreq"))?.unwrap();
+ if let Some(info) = vcpufreq_info {
+ node.setprop_addrrange_inplace(cstr!("reg"), info.addr, info.size)
+ } else {
+ node.nop()
+ }
+}
+
+#[derive(Debug)]
pub struct DeviceTreeInfo {
pub kernel_range: Option<Range<usize>>,
pub initrd_range: Option<Range<usize>>,
@@ -639,6 +761,7 @@
pub swiotlb_info: SwiotlbInfo,
device_assignment: Option<DeviceAssignmentInfo>,
vm_ref_dt_props_info: BTreeMap<CString, Vec<u8>>,
+ vcpufreq_info: Option<VcpufreqInfo>,
}
impl DeviceTreeInfo {
@@ -751,6 +874,17 @@
RebootReason::InvalidFdt
})?;
+ let vcpufreq_info = read_vcpufreq_info(fdt).map_err(|e| {
+ error!("Failed to read vcpufreq info from DT: {e}");
+ RebootReason::InvalidFdt
+ })?;
+ if let Some(ref info) = vcpufreq_info {
+ validate_vcpufreq_info(info, &cpus).map_err(|e| {
+ error!("Failed to validate vcpufreq info from DT: {e}");
+ RebootReason::InvalidFdt
+ })?;
+ }
+
let pci_info = read_pci_info_from(fdt).map_err(|e| {
error!("Failed to read pci info from DT: {e}");
RebootReason::InvalidFdt
@@ -801,6 +935,7 @@
swiotlb_info,
device_assignment,
vm_ref_dt_props_info,
+ vcpufreq_info,
})
}
@@ -825,6 +960,10 @@
error!("Failed to patch cpus to DT: {e}");
RebootReason::InvalidFdt
})?;
+ patch_vcpufreq(fdt, &info.vcpufreq_info).map_err(|e| {
+ error!("Failed to patch vcpufreq info to DT: {e}");
+ RebootReason::InvalidFdt
+ })?;
patch_pci_info(fdt, &info.pci_info).map_err(|e| {
error!("Failed to patch pci info to DT: {e}");
RebootReason::InvalidFdt
diff --git a/service_vm/test_apk/Android.bp b/service_vm/test_apk/Android.bp
new file mode 100644
index 0000000..8f5fb41
--- /dev/null
+++ b/service_vm/test_apk/Android.bp
@@ -0,0 +1,44 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "VmAttestationTestApp",
+ test_suites: [
+ "general-tests",
+ ],
+ srcs: ["src/java/**/*.java"],
+ static_libs: [
+ "MicrodroidDeviceTestHelper",
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+ "com.android.virt.vm_attestation.testservice-java",
+ "truth",
+ ],
+ jni_libs: ["libvm_attestation_test_payload"],
+ jni_uses_platform_apis: true,
+ use_embedded_native_libs: true,
+ sdk_version: "test_current",
+ compile_multilib: "first",
+}
+
+rust_defaults {
+ name: "vm_attestation_test_payload_defaults",
+ crate_name: "vm_attestation_test_payload",
+ defaults: ["avf_build_flags_rust"],
+ srcs: ["src/native/main.rs"],
+ prefer_rlib: true,
+ rustlibs: [
+ "com.android.virt.vm_attestation.testservice-rust",
+ "libandroid_logger",
+ "libanyhow",
+ "libavflog",
+ "liblog_rust",
+ "libvm_payload_bindgen",
+ ],
+}
+
+rust_ffi {
+ name: "libvm_attestation_test_payload",
+ defaults: ["vm_attestation_test_payload_defaults"],
+}
diff --git a/service_vm/test_apk/AndroidManifest.xml b/service_vm/test_apk/AndroidManifest.xml
new file mode 100644
index 0000000..b998b7f
--- /dev/null
+++ b/service_vm/test_apk/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.virt.vm_attestation.testapp">
+ <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+ <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+
+ <application />
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.virt.vm_attestation.testapp"
+ android:label="Microdroid VM attestation" />
+</manifest>
diff --git a/service_vm/test_apk/AndroidTest.xml b/service_vm/test_apk/AndroidTest.xml
new file mode 100644
index 0000000..18b4e46
--- /dev/null
+++ b/service_vm/test_apk/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Microdroid VM remote attestation tests.">
+ <option name="config-descriptor:metadata" key="component" value="security" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="VmAttestationTestApp.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/microdroid" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts/microdroid" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.virt.vm_attestation.testapp" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="shell-timeout" value="300000" />
+ <option name="test-timeout" value="300000" />
+ </test>
+</configuration>
diff --git a/service_vm/test_apk/aidl/Android.bp b/service_vm/test_apk/aidl/Android.bp
new file mode 100644
index 0000000..3ecce46
--- /dev/null
+++ b/service_vm/test_apk/aidl/Android.bp
@@ -0,0 +1,18 @@
+package {
+ default_team: "trendy_team_virtualization",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+ name: "com.android.virt.vm_attestation.testservice",
+ srcs: ["com/android/virt/vm_attestation/testservice/**/*.aidl"],
+ unstable: true,
+ backend: {
+ java: {
+ gen_rpc: true,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl b/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
new file mode 100644
index 0000000..94a7b8d
--- /dev/null
+++ b/service_vm/test_apk/aidl/com/android/virt/vm_attestation/testservice/IAttestationService.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.vm_attestation.testservice;
+
+/** {@hide} */
+interface IAttestationService {
+ const int PORT = 5679;
+
+ /**
+ * Requests attestation for testing.
+ *
+ * A fake key pair should be provisioned with the call to
+ * {@link VirtualMachine#enableTestAttestation()} before calling this method.
+ *
+ * The attestation result will be cached in the VM and can be validated with
+ * {@link #validateAttestationResult}.
+ */
+ void requestAttestationForTesting();
+
+ /**
+ * Validates the attestation result returned by the last call to
+ * {@link #requestAttestationForTesting}.
+ */
+ void validateAttestationResult();
+}
diff --git a/service_vm/test_apk/assets/config.json b/service_vm/test_apk/assets/config.json
new file mode 100644
index 0000000..caae3ce
--- /dev/null
+++ b/service_vm/test_apk/assets/config.json
@@ -0,0 +1,10 @@
+{
+ "os": {
+ "name": "microdroid"
+ },
+ "task": {
+ "type": "microdroid_launcher",
+ "command": "libvm_attestation_test_payload.so"
+ },
+ "export_tombstones": true
+ }
\ No newline at end of file
diff --git a/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java b/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
new file mode 100644
index 0000000..7771e83
--- /dev/null
+++ b/service_vm/test_apk/src/java/com/android/virt/vm_attestation/testapp/VmAttestationTests.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.vm_attestation.testapp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
+import com.android.virt.vm_attestation.testservice.IAttestationService;
+
+@RunWith(Parameterized.class)
+public class VmAttestationTests extends MicrodroidDeviceTestBase {
+ private static final String TAG = "VmAttestationTest";
+ private static final String DEFAULT_CONFIG = "assets/config.json";
+
+ @Parameterized.Parameter(0)
+ public String mGki;
+
+ @Parameterized.Parameters(name = "gki={0}")
+ public static Collection<Object[]> params() {
+ List<Object[]> ret = new ArrayList<>();
+ ret.add(new Object[] {null /* use microdroid kernel */});
+ for (String gki : SUPPORTED_GKI_VERSIONS) {
+ ret.add(new Object[] {gki});
+ }
+ return ret;
+ }
+
+ @Before
+ public void setup() throws IOException {
+ grantPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
+ grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+ prepareTestSetup(true /* protectedVm */, mGki);
+ setMaxPerformanceTaskProfile();
+ }
+
+ @Test
+ public void requestingAttestationSucceeds() throws Exception {
+ assume().withMessage("Remote attestation is not supported on CF.")
+ .that(isCuttlefish())
+ .isFalse();
+ assumeFeatureEnabled(VirtualMachineManager.FEATURE_REMOTE_ATTESTATION);
+
+ VirtualMachineConfig.Builder builder =
+ newVmConfigBuilderWithPayloadConfig(DEFAULT_CONFIG)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setVmOutputCaptured(true);
+ VirtualMachineConfig config = builder.build();
+ VirtualMachine vm = forceCreateNewVirtualMachine("attestation_client", config);
+
+ vm.enableTestAttestation();
+ CompletableFuture<Exception> exception = new CompletableFuture<>();
+ CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
+ VmEventListener listener =
+ new VmEventListener() {
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ payloadReady.complete(true);
+ try {
+ IAttestationService service =
+ IAttestationService.Stub.asInterface(
+ vm.connectToVsockServer(IAttestationService.PORT));
+ android.os.Trace.beginSection("runningVmRequestsAttestation");
+ service.requestAttestationForTesting();
+ android.os.Trace.endSection();
+ service.validateAttestationResult();
+ } catch (Exception e) {
+ exception.complete(e);
+ } finally {
+ forceStop(vm);
+ }
+ }
+ };
+
+ listener.runToFinish(TAG, vm);
+ assertThat(payloadReady.getNow(false)).isTrue();
+ assertThat(exception.getNow(null)).isNull();
+ }
+}
diff --git a/service_vm/test_apk/src/native/main.rs b/service_vm/test_apk/src/native/main.rs
new file mode 100644
index 0000000..d5d599d
--- /dev/null
+++ b/service_vm/test_apk/src/native/main.rs
@@ -0,0 +1,271 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Main executable of VM attestation for end-to-end testing.
+
+use anyhow::{anyhow, ensure, Result};
+use avflog::LogResult;
+use com_android_virt_vm_attestation_testservice::{
+ aidl::com::android::virt::vm_attestation::testservice::IAttestationService::{
+ BnAttestationService, IAttestationService, PORT,
+ },
+ binder::{self, unstable_api::AsNative, BinderFeatures, Interface, IntoBinderResult, Strong},
+};
+use log::{error, info};
+use std::{
+ ffi::{c_void, CStr},
+ panic,
+ ptr::{self, NonNull},
+ result,
+ sync::{Arc, Mutex},
+};
+use vm_payload_bindgen::{
+ attestation_status_t, AIBinder, AVmAttestationResult, AVmAttestationResult_free,
+ AVmAttestationResult_getCertificateAt, AVmAttestationResult_getCertificateCount,
+ AVmAttestationResult_getPrivateKey, AVmAttestationResult_resultToString,
+ AVmAttestationResult_sign, AVmPayload_notifyPayloadReady,
+ AVmPayload_requestAttestationForTesting, AVmPayload_runVsockRpcServer,
+};
+
+/// Entry point of the Service VM client.
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn AVmPayload_main() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("service_vm_client")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+ // Redirect panic messages to logcat.
+ panic::set_hook(Box::new(|panic_info| {
+ error!("{}", panic_info);
+ }));
+ if let Err(e) = try_main() {
+ error!("failed with {:?}", e);
+ std::process::exit(1);
+ }
+}
+
+fn try_main() -> Result<()> {
+ info!("Welcome to Service VM Client!");
+
+ let mut service = AttestationService::new_binder().as_binder();
+ let service = service.as_native_mut() as *mut AIBinder;
+ let param = ptr::null_mut();
+ // SAFETY: We hold a strong pointer, so the raw pointer remains valid. The bindgen AIBinder
+ // is the same type as `sys::AIBinder`. It is safe for `on_ready` to be invoked at any time,
+ // with any parameter.
+ unsafe { AVmPayload_runVsockRpcServer(service, PORT.try_into()?, Some(on_ready), param) };
+}
+
+extern "C" fn on_ready(_param: *mut c_void) {
+ // SAFETY: It is safe to call `AVmPayload_notifyPayloadReady` at any time.
+ unsafe { AVmPayload_notifyPayloadReady() };
+}
+
+struct AttestationService {
+ res: Arc<Mutex<Option<AttestationResult>>>,
+}
+
+impl Interface for AttestationService {}
+
+impl AttestationService {
+ fn new_binder() -> Strong<dyn IAttestationService> {
+ let res = Arc::new(Mutex::new(None));
+ BnAttestationService::new_binder(AttestationService { res }, BinderFeatures::default())
+ }
+}
+
+impl IAttestationService for AttestationService {
+ fn requestAttestationForTesting(&self) -> binder::Result<()> {
+ // The data below is only a placeholder generated randomly with urandom
+ let challenge = &[
+ 0x6c, 0xad, 0x52, 0x50, 0x15, 0xe7, 0xf4, 0x1d, 0xa5, 0x60, 0x7e, 0xd2, 0x7d, 0xf1,
+ 0x51, 0x67, 0xc3, 0x3e, 0x73, 0x9b, 0x30, 0xbd, 0x04, 0x20, 0x2e, 0xde, 0x3b, 0x1d,
+ 0xc8, 0x07, 0x11, 0x7b,
+ ];
+ let res = AttestationResult::request_attestation(challenge)
+ .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ *self.res.lock().unwrap() = Some(res);
+ Ok(())
+ }
+
+ fn validateAttestationResult(&self) -> binder::Result<()> {
+ // TODO(b/191073073): Returns the attestation result to the host for validation.
+ self.res.lock().unwrap().as_ref().unwrap().log().or_service_specific_exception(-1)
+ }
+}
+
+#[derive(Debug)]
+struct AttestationResult(NonNull<AVmAttestationResult>);
+
+// Safety: `AttestationResult` is not `Send` because it contains a raw pointer to a C struct.
+unsafe impl Send for AttestationResult {}
+
+impl AttestationResult {
+ fn request_attestation(challenge: &[u8]) -> result::Result<Self, attestation_status_t> {
+ let mut res: *mut AVmAttestationResult = ptr::null_mut();
+ // SAFETY: It is safe as we only read the challenge within its bounds and the
+ // function does not retain any reference to it.
+ let status = unsafe {
+ AVmPayload_requestAttestationForTesting(
+ challenge.as_ptr() as *const c_void,
+ challenge.len(),
+ &mut res,
+ )
+ };
+ if status == attestation_status_t::ATTESTATION_OK {
+ info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
+ let res = NonNull::new(res).expect("The attestation result is null");
+ Ok(Self(res))
+ } else {
+ Err(status)
+ }
+ }
+
+ fn certificate_chain(&self) -> Result<Vec<Box<[u8]>>> {
+ let num_certs = get_certificate_count(self.as_ref());
+ let mut certs = Vec::with_capacity(num_certs);
+ for i in 0..num_certs {
+ certs.push(get_certificate_at(self.as_ref(), i)?);
+ }
+ Ok(certs)
+ }
+
+ fn private_key(&self) -> Result<Box<[u8]>> {
+ get_private_key(self.as_ref())
+ }
+
+ fn sign(&self, message: &[u8]) -> Result<Box<[u8]>> {
+ sign_with_attested_key(self.as_ref(), message)
+ }
+
+ fn log(&self) -> Result<()> {
+ let cert_chain = self.certificate_chain()?;
+ info!("Attestation result certificateChain = {:?}", cert_chain);
+
+ let private_key = self.private_key()?;
+ info!("Attestation result privateKey = {:?}", private_key);
+
+ let message = b"Hello from Service VM client";
+ info!("Signing message: {:?}", message);
+ let signature = self.sign(message)?;
+ info!("Signature: {:?}", signature);
+ Ok(())
+ }
+}
+
+impl AsRef<AVmAttestationResult> for AttestationResult {
+ fn as_ref(&self) -> &AVmAttestationResult {
+ // SAFETY: This field is private, and only populated with a successful call to
+ // `AVmPayload_requestAttestation`.
+ unsafe { self.0.as_ref() }
+ }
+}
+
+impl Drop for AttestationResult {
+ fn drop(&mut self) {
+ // SAFETY: This field is private, and only populated with a successful call to
+ // `AVmPayload_requestAttestation`, and not freed elsewhere.
+ unsafe { AVmAttestationResult_free(self.0.as_ptr()) };
+ }
+}
+
+fn get_certificate_count(res: &AVmAttestationResult) -> usize {
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed.
+ unsafe { AVmAttestationResult_getCertificateCount(res) }
+}
+
+fn get_certificate_at(res: &AVmAttestationResult, index: usize) -> Result<Box<[u8]>> {
+ let size =
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed.
+ unsafe { AVmAttestationResult_getCertificateAt(res, index, ptr::null_mut(), 0) };
+ let mut cert = vec![0u8; size];
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed. This function only writes within the bounds of `cert`.
+ // And `cert` cannot overlap `res` because we just allocated it.
+ let size = unsafe {
+ AVmAttestationResult_getCertificateAt(
+ res,
+ index,
+ cert.as_mut_ptr() as *mut c_void,
+ cert.len(),
+ )
+ };
+ ensure!(size == cert.len());
+ Ok(cert.into_boxed_slice())
+}
+
+fn get_private_key(res: &AVmAttestationResult) -> Result<Box<[u8]>> {
+ let size =
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed.
+ unsafe { AVmAttestationResult_getPrivateKey(res, ptr::null_mut(), 0) };
+ let mut private_key = vec![0u8; size];
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed. This function only writes within the bounds of `private_key`.
+ // And `private_key` cannot overlap `res` because we just allocated it.
+ let size = unsafe {
+ AVmAttestationResult_getPrivateKey(
+ res,
+ private_key.as_mut_ptr() as *mut c_void,
+ private_key.len(),
+ )
+ };
+ ensure!(size == private_key.len());
+ Ok(private_key.into_boxed_slice())
+}
+
+fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Box<[u8]>> {
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed.
+ let size = unsafe {
+ AVmAttestationResult_sign(
+ res,
+ message.as_ptr() as *const c_void,
+ message.len(),
+ ptr::null_mut(),
+ 0,
+ )
+ };
+ let mut signature = vec![0u8; size];
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed. This function only writes within the bounds of `signature`.
+ // And `signature` cannot overlap `res` because we just allocated it.
+ let size = unsafe {
+ AVmAttestationResult_sign(
+ res,
+ message.as_ptr() as *const c_void,
+ message.len(),
+ signature.as_mut_ptr() as *mut c_void,
+ signature.len(),
+ )
+ };
+ ensure!(size <= signature.len());
+ signature.truncate(size);
+ Ok(signature.into_boxed_slice())
+}
+
+fn status_to_cstr(status: attestation_status_t) -> &'static CStr {
+ // SAFETY: The function only reads the given enum status and returns a pointer to a
+ // static string.
+ let message = unsafe { AVmAttestationResult_resultToString(status) };
+ // SAFETY: The pointer returned by `AVmAttestationResult_resultToString` is guaranteed to
+ // point to a valid C String that lives forever.
+ unsafe { CStr::from_ptr(message) }
+}
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index e9c84fb..2d52732 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -49,7 +49,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
@@ -277,10 +276,6 @@
(builder) -> builder);
}
- // TODO(b/323768068): Enable this test when we can inject vendor digest for test purpose.
- // After introducing VM reference DT, non-pVM cannot trust test_microdroid_vendor_image.img
- // as well, because it doesn't pass the hashtree digest of testing image into VM.
- @Ignore
@Test
public void testMicrodroidDebugBootTime_withVendorPartition() throws Exception {
assume().withMessage("Cuttlefish doesn't support device tree under" + " /proc/device-tree")
@@ -293,8 +288,10 @@
.isFalse();
assumeFeatureEnabled(VirtualMachineManager.FEATURE_VENDOR_MODULES);
- File vendorDiskImage =
- new File("/data/local/tmp/microdroid-bench/microdroid_vendor_image.img");
+ File vendorDiskImage = new File("/vendor/etc/avf/microdroid/microdroid_vendor.img");
+ assume().withMessage("Microdroid vendor image doesn't exist, skip")
+ .that(vendorDiskImage.exists())
+ .isTrue();
runBootTimeTest(
"test_vm_boot_time_debug_with_vendor_partition",
"assets/" + os() + "/vm_config.json",
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 4e340f0..0687a7b 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -73,7 +73,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
@@ -2157,10 +2156,6 @@
.contains("android.permission.USE_CUSTOM_VIRTUAL_MACHINE permission");
}
- // TODO(b/323768068): Enable this test when we can inject vendor digest for test purpose.
- // After introducing VM reference DT, non-pVM cannot trust test_microdroid_vendor_image.img
- // as well, because it doesn't pass the hashtree digest of testing image into VM.
- @Ignore
@Test
public void bootsWithVendorPartition() throws Exception {
assumeSupportedDevice();
@@ -2174,8 +2169,8 @@
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
- File vendorDiskImage =
- new File("/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img");
+ File vendorDiskImage = new File("/vendor/etc/avf/microdroid/microdroid_vendor.img");
+ assumeTrue("Microdroid vendor image doesn't exist, skip", vendorDiskImage.exists());
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
.setVendorDiskImage(vendorDiskImage)
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 6a8f1a1..a2194cc 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -38,6 +38,7 @@
IVirtualizationService::FEATURE_MULTI_TENANT,
IVirtualizationService::FEATURE_VENDOR_MODULES,
IVirtualizationService::FEATURE_DICE_CHANGES,
+ IVirtualizationService::FEATURE_REMOTE_ATTESTATION,
MemoryTrimLevel::MemoryTrimLevel,
Partition::Partition,
PartitionType::PartitionType,
@@ -311,6 +312,7 @@
match feature {
FEATURE_DICE_CHANGES => Ok(cfg!(dice_changes)),
FEATURE_MULTI_TENANT => Ok(cfg!(multi_tenant)),
+ FEATURE_REMOTE_ATTESTATION => Ok(cfg!(remote_attestation)),
FEATURE_VENDOR_MODULES => Ok(cfg!(vendor_modules)),
_ => {
warn!("unknown feature {feature}");
@@ -318,6 +320,10 @@
}
}
}
+
+ fn enableTestAttestation(&self) -> binder::Result<()> {
+ GLOBAL_SERVICE.enableTestAttestation()
+ }
}
impl VirtualizationService {
@@ -1419,8 +1425,8 @@
Ok(sk.map(|s| BnSecretkeeper::new_binder(SecretkeeperProxy(s), BinderFeatures::default())))
}
- fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
- GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32)
+ fn requestAttestation(&self, csr: &[u8], test_mode: bool) -> binder::Result<Vec<Certificate>> {
+ GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32, test_mode)
}
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 92a5812..7962bc3 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -24,6 +24,7 @@
interface IVirtualizationService {
const String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
const String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
+ const String FEATURE_REMOTE_ATTESTATION = "com.android.kvm.REMOTE_ATTESTATION";
const String FEATURE_VENDOR_MODULES = "com.android.kvm.VENDOR_MODULES";
/**
@@ -73,4 +74,10 @@
/** Returns whether given feature is enabled. */
boolean isFeatureEnabled(in String feature);
+
+ /**
+ * Provisions a key pair for the VM attestation testing, a fake certificate will be
+ * associated to the fake key pair when the VM requests attestation in testing mode.
+ */
+ void enableTestAttestation();
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index dd94526..abfc45a 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -61,10 +61,20 @@
* attested is owned by this app.
* The uniqueness of the UID ensures that no two VMs owned by different apps
* are able to correlate keys.
+ * @param testMode Whether the request is for testing purposes.
* @return A sequence of DER-encoded X.509 certificates that make up the attestation
* key's certificate chain. The attestation key is provided in the CSR.
*/
- Certificate[] requestAttestation(in byte[] csr, int requesterUid);
+ Certificate[] requestAttestation(in byte[] csr, int requesterUid, in boolean testMode);
+
+ /**
+ * Provisions a key pair for the VM attestation testing, a fake certificate will be
+ * associated to the fake key pair when the VM requests attestation in testing mode.
+ *
+ * The provisioned key pair will be used in the subsequent call to {@link #requestAttestation}
+ * with testMode set to true.
+ */
+ void enableTestAttestation();
/**
* Get a list of assignable devices.
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index cf91302..6806a5c 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -51,10 +51,11 @@
* Requests a certificate chain for the provided certificate signing request (CSR).
*
* @param csr The certificate signing request.
+ * @param testMode Whether the request is for test purposes.
* @return A sequence of DER-encoded X.509 certificates that make up the attestation
* key's certificate chain. The attestation key is provided in the CSR.
*/
- Certificate[] requestAttestation(in byte[] csr);
+ Certificate[] requestAttestation(in byte[] csr, in boolean testMode);
/**
* Request connection to Secretkeeper. This is used by pVM to store Anti-Rollback protected
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index a1a1fb9..d0c5d4a 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -16,7 +16,8 @@
use crate::{get_calling_pid, get_calling_uid, REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME};
use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
-use crate::rkpvm::request_attestation;
+use crate::rkpvm::{request_attestation, generate_ecdsa_p256_key_pair};
+use crate::remote_provisioning;
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
use android_system_virtualizationservice::{
@@ -38,6 +39,7 @@
use anyhow::{anyhow, ensure, Context, Result};
use avflog::LogResult;
use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong, IntoBinderResult};
+use service_vm_comm::Response;
use lazy_static::lazy_static;
use libc::VMADDR_CID_HOST;
use log::{error, info, warn};
@@ -73,7 +75,73 @@
const CHUNK_RECV_MAX_LEN: usize = 1024;
+/// The fake certificate is used for testing only when a client VM requests attestation in test
+/// mode, it is a single certificate extracted on an unregistered device for testing.
+/// Here is the snapshot of the certificate:
+///
+/// ```
+/// Certificate:
+/// Data:
+/// Version: 3 (0x2)
+/// Serial Number:
+/// 59:ae:50:98:95:e1:34:25:f1:21:93:c0:4c:e5:24:66
+/// Signature Algorithm: ecdsa-with-SHA256
+/// Issuer: CN = Droid Unregistered Device CA, O = Google Test LLC
+/// Validity
+/// Not Before: Feb 5 14:39:39 2024 GMT
+/// Not After : Feb 14 14:39:39 2024 GMT
+/// Subject: CN = 59ae509895e13425f12193c04ce52466, O = TEE
+/// Subject Public Key Info:
+/// Public Key Algorithm: id-ecPublicKey
+/// Public-Key: (256 bit)
+/// pub:
+/// 04:30:32:cd:95:12:b0:71:8b:b7:14:44:26:58:d5:
+/// 82:8c:25:55:2c:6d:ef:98:e3:4f:88:d0:74:82:09:
+/// 3e:8d:6c:f0:f2:18:d5:83:0e:0d:f2:ce:c5:15:38:
+/// e5:6a:e6:4d:4d:95:15:b7:24:e7:cb:4b:63:42:21:
+/// bc:36:c6:0a:d8
+/// ASN1 OID: prime256v1
+/// NIST CURVE: P-256
+/// X509v3 extensions:
+/// ...
+/// ```
+const FAKE_CERTIFICATE_FOR_TESTING: &[u8] = &[
+ 0x30, 0x82, 0x01, 0xee, 0x30, 0x82, 0x01, 0x94, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x59,
+ 0xae, 0x50, 0x98, 0x95, 0xe1, 0x34, 0x25, 0xf1, 0x21, 0x93, 0xc0, 0x4c, 0xe5, 0x24, 0x66, 0x30,
+ 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x41, 0x31, 0x25, 0x30,
+ 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x44, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x55, 0x6e,
+ 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63,
+ 0x65, 0x20, 0x43, 0x41, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x4c, 0x4c, 0x43, 0x30, 0x1e,
+ 0x17, 0x0d, 0x32, 0x34, 0x30, 0x32, 0x30, 0x35, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x17,
+ 0x0d, 0x32, 0x34, 0x30, 0x32, 0x31, 0x34, 0x31, 0x34, 0x33, 0x39, 0x33, 0x39, 0x5a, 0x30, 0x39,
+ 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x35, 0x39, 0x61, 0x65, 0x35,
+ 0x30, 0x39, 0x38, 0x39, 0x35, 0x65, 0x31, 0x33, 0x34, 0x32, 0x35, 0x66, 0x31, 0x32, 0x31, 0x39,
+ 0x33, 0x63, 0x30, 0x34, 0x63, 0x65, 0x35, 0x32, 0x34, 0x36, 0x36, 0x31, 0x0c, 0x30, 0x0a, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, 0x54, 0x45, 0x45, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a,
+ 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
+ 0x03, 0x42, 0x00, 0x04, 0x30, 0x32, 0xcd, 0x95, 0x12, 0xb0, 0x71, 0x8b, 0xb7, 0x14, 0x44, 0x26,
+ 0x58, 0xd5, 0x82, 0x8c, 0x25, 0x55, 0x2c, 0x6d, 0xef, 0x98, 0xe3, 0x4f, 0x88, 0xd0, 0x74, 0x82,
+ 0x09, 0x3e, 0x8d, 0x6c, 0xf0, 0xf2, 0x18, 0xd5, 0x83, 0x0e, 0x0d, 0xf2, 0xce, 0xc5, 0x15, 0x38,
+ 0xe5, 0x6a, 0xe6, 0x4d, 0x4d, 0x95, 0x15, 0xb7, 0x24, 0xe7, 0xcb, 0x4b, 0x63, 0x42, 0x21, 0xbc,
+ 0x36, 0xc6, 0x0a, 0xd8, 0xa3, 0x76, 0x30, 0x74, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x39, 0x81, 0x41, 0x0a, 0xb9, 0xf3, 0xf4, 0x5b, 0x75, 0x97, 0x4a, 0x46, 0xd6,
+ 0x30, 0x9e, 0x1d, 0x7a, 0x3b, 0xec, 0xa8, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0x82, 0xbd, 0x00, 0xde, 0xcb, 0xc5, 0xe7, 0x72, 0x87, 0x3d, 0x1c, 0x0a,
+ 0x1e, 0x78, 0x4f, 0xf5, 0xd3, 0xc1, 0x3e, 0xb8, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x11, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x1e, 0x04, 0x03, 0xa1, 0x01, 0x08, 0x30, 0x0a, 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00,
+ 0xae, 0xd8, 0x40, 0x9e, 0x37, 0x3e, 0x5c, 0x9c, 0xe2, 0x93, 0x3d, 0x8c, 0xf7, 0x05, 0x10, 0xe7,
+ 0xd1, 0x2b, 0x87, 0x8a, 0xee, 0xd6, 0x1e, 0x6c, 0x3b, 0xd2, 0x91, 0x3e, 0xa5, 0xdf, 0x91, 0x20,
+ 0x02, 0x20, 0x7f, 0x0f, 0x29, 0x54, 0x60, 0x80, 0x07, 0x50, 0x5f, 0x56, 0x6b, 0x9f, 0xe0, 0x94,
+ 0xb4, 0x3f, 0x3b, 0x0f, 0x61, 0xa0, 0x33, 0x40, 0xe6, 0x1a, 0x42, 0xda, 0x4b, 0xa4, 0xfd, 0x92,
+ 0xb9, 0x0f,
+];
+
lazy_static! {
+ static ref FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING: Mutex<Option<Vec<u8>>> = Mutex::new(None);
static ref VFIO_SERVICE: Strong<dyn IVfioHandler> =
wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())
.expect("Could not connect to VfioHandler");
@@ -169,10 +237,41 @@
Ok(cids)
}
+ fn enableTestAttestation(&self) -> binder::Result<()> {
+ check_manage_access()?;
+ check_use_custom_virtual_machine()?;
+ if !cfg!(remote_attestation) {
+ return Err(Status::new_exception_str(
+ ExceptionCode::UNSUPPORTED_OPERATION,
+ Some(
+ "enableTestAttestation is not supported with the remote_attestation \
+ feature disabled",
+ ),
+ ))
+ .with_log();
+ }
+ let res = generate_ecdsa_p256_key_pair()
+ .context("Failed to generate ECDSA P-256 key pair for testing")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ match res {
+ Response::GenerateEcdsaP256KeyPair(key_pair) => {
+ FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
+ .lock()
+ .unwrap()
+ .replace(key_pair.key_blob.to_vec());
+ Ok(())
+ }
+ _ => Err(remote_provisioning::to_service_specific_error(res)),
+ }
+ .with_log()
+ }
+
fn requestAttestation(
&self,
csr: &[u8],
requester_uid: i32,
+ test_mode: bool,
) -> binder::Result<Vec<Certificate>> {
check_manage_access()?;
if !cfg!(remote_attestation) {
@@ -186,14 +285,31 @@
.with_log();
}
info!("Received csr. Requestting attestation...");
- let attestation_key = get_rkpd_attestation_key(
- REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
- requester_uid as u32,
- )
- .context("Failed to retrieve the remotely provisioned keys")
- .with_log()
- .or_service_specific_exception(-1)?;
- let mut certificate_chain = split_x509_certificate_chain(&attestation_key.encodedCertChain)
+ let (key_blob, certificate_chain) = if test_mode {
+ check_use_custom_virtual_machine()?;
+ info!("Using the fake key blob for testing...");
+ (
+ FAKE_PROVISIONED_KEY_BLOB_FOR_TESTING
+ .lock()
+ .unwrap()
+ .clone()
+ .ok_or_else(|| anyhow!("No key blob for testing"))
+ .with_log()
+ .or_service_specific_exception(-1)?,
+ FAKE_CERTIFICATE_FOR_TESTING.to_vec(),
+ )
+ } else {
+ info!("Retrieving the remotely provisioned keys from RKPD...");
+ let attestation_key = get_rkpd_attestation_key(
+ REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+ requester_uid as u32,
+ )
+ .context("Failed to retrieve the remotely provisioned keys")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ (attestation_key.keyBlob, attestation_key.encodedCertChain)
+ };
+ let mut certificate_chain = split_x509_certificate_chain(&certificate_chain)
.context("Failed to split the remotely provisioned certificate chain")
.with_log()
.or_service_specific_exception(-1)?;
@@ -206,7 +322,7 @@
}
let certificate = request_attestation(
csr.to_vec(),
- attestation_key.keyBlob,
+ key_blob,
certificate_chain[0].encodedCertificate.clone(),
)
.context("Failed to request attestation")
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index 40f54db..c2c04df 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -145,7 +145,7 @@
}
}
-fn to_service_specific_error(response: Response) -> Status {
+pub(crate) fn to_service_specific_error(response: Response) -> Status {
match response {
Response::Err(e) => match e {
RequestProcessingError::InvalidMac => {
diff --git a/vm_payload/include-restricted/vm_payload_restricted.h b/vm_payload/include-restricted/vm_payload_restricted.h
index 15c37ed..d7324a8 100644
--- a/vm_payload/include-restricted/vm_payload_restricted.h
+++ b/vm_payload/include-restricted/vm_payload_restricted.h
@@ -55,4 +55,25 @@
*/
size_t AVmPayload_getDiceAttestationCdi(void* _Nullable data, size_t size);
+/**
+ * Requests attestation for the VM for testing only.
+ *
+ * This function is only for testing and will not return a real RKP server backed
+ * certificate chain.
+ *
+ * Prior to calling this function, the caller must provision a key pair to be used in
+ * this function with `VirtualMachineManager#enableTestAttestation`.
+ *
+ * \param challenge A pointer to the challenge buffer.
+ * \param challenge_size size of the challenge. The maximum supported challenge size is
+ * 64 bytes. The status ATTESTATION_ERROR_INVALID_CHALLENGE will be returned if
+ * an invalid challenge is passed.
+ * \param result The remote attestation result will be filled here if the attestation
+ * succeeds. The result remains valid until it is freed with
+ * `AVmPayload_freeAttestationResult`.
+ */
+attestation_status_t AVmPayload_requestAttestationForTesting(
+ const void* _Nonnull challenge, size_t challenge_size,
+ struct AVmAttestationResult* _Nullable* _Nonnull result) __INTRODUCED_IN(__ANDROID_API_V__);
+
__END_DECLS
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 3483e1d..af755c9 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -211,7 +211,7 @@
* If `size` is smaller than the total size of the signature, the signature will be
* truncated to this `size`.
*
- * \return The total size of the signature.
+ * \return The size of the signature, or the size needed if the supplied buffer is too small.
*
* [RFC 6979]: https://datatracker.ietf.org/doc/html/rfc6979
*/
diff --git a/vm_payload/libvm_payload.map.txt b/vm_payload/libvm_payload.map.txt
index 975a5a3..caf8f84 100644
--- a/vm_payload/libvm_payload.map.txt
+++ b/vm_payload/libvm_payload.map.txt
@@ -8,6 +8,7 @@
AVmPayload_getApkContentsPath; # systemapi introduced=UpsideDownCake
AVmPayload_getEncryptedStoragePath; # systemapi introduced=UpsideDownCake
AVmPayload_requestAttestation; # systemapi introduced=VanillaIceCream
+ AVmPayload_requestAttestationForTesting; # systemapi introduced=VanillaIceCream
AVmAttestationResult_getPrivateKey; # systemapi introduced=VanillaIceCream
AVmAttestationResult_sign; # systemapi introduced=VanillaIceCream
AVmAttestationResult_free; # systemapi introduced=VanillaIceCream
diff --git a/vm_payload/src/lib.rs b/vm_payload/src/lib.rs
index 7978059..6188b21 100644
--- a/vm_payload/src/lib.rs
+++ b/vm_payload/src/lib.rs
@@ -39,6 +39,9 @@
};
use vm_payload_status_bindgen::attestation_status_t;
+/// Maximum size of an ECDSA signature for EC P-256 key is 72 bytes.
+const MAX_ECDSA_P256_SIGNATURE_SIZE: usize = 72;
+
lazy_static! {
static ref VM_APK_CONTENTS_PATH_C: CString =
CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed");
@@ -273,9 +276,6 @@
/// Behavior is undefined if any of the following conditions are violated:
///
/// * `challenge` must be [valid] for reads of `challenge_size` bytes.
-/// * `res` must be [valid] to write the attestation result.
-/// * The region of memory beginning at `challenge` with `challenge_size` bytes must not
-/// overlap with the region of memory `res` points to.
///
/// [valid]: ptr#safety
#[no_mangle]
@@ -284,6 +284,60 @@
challenge_size: usize,
res: &mut *mut AttestationResult,
) -> attestation_status_t {
+ // SAFETY: The caller guarantees that `challenge` is valid for reads and `res` is valid
+ // for writes.
+ unsafe {
+ request_attestation(
+ challenge,
+ challenge_size,
+ false, // test_mode
+ res,
+ )
+ }
+}
+
+/// Requests the remote attestation of the client VM for testing.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `challenge` must be [valid] for reads of `challenge_size` bytes.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmPayload_requestAttestationForTesting(
+ challenge: *const u8,
+ challenge_size: usize,
+ res: &mut *mut AttestationResult,
+) -> attestation_status_t {
+ // SAFETY: The caller guarantees that `challenge` is valid for reads and `res` is valid
+ // for writes.
+ unsafe {
+ request_attestation(
+ challenge,
+ challenge_size,
+ true, // test_mode
+ res,
+ )
+ }
+}
+
+/// Requests the remote attestation of the client VM.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `challenge` must be [valid] for reads of `challenge_size` bytes.
+///
+/// [valid]: ptr#safety
+unsafe fn request_attestation(
+ challenge: *const u8,
+ challenge_size: usize,
+ test_mode: bool,
+ res: &mut *mut AttestationResult,
+) -> attestation_status_t {
initialize_logging();
const MAX_CHALLENGE_SIZE: usize = 64;
if challenge_size > MAX_CHALLENGE_SIZE {
@@ -297,7 +351,7 @@
unsafe { std::slice::from_raw_parts(challenge, challenge_size) }
};
let service = unwrap_or_abort(get_vm_payload_service());
- match service.requestAttestation(challenge) {
+ match service.requestAttestation(challenge, test_mode) {
Ok(attestation_res) => {
*res = Box::into_raw(Box::new(attestation_res));
attestation_status_t::ATTESTATION_OK
@@ -400,27 +454,36 @@
data: *mut u8,
size: usize,
) -> usize {
+ // A DER-encoded ECDSA signature can have varying sizes even with the same EC Key and message,
+ // due to the encoding of the random values r and s that are part of the signature.
+ if size == 0 {
+ return MAX_ECDSA_P256_SIGNATURE_SIZE;
+ }
if message_size == 0 {
panic!("Message to be signed must not be empty.")
}
// SAFETY: See the requirements on `message` above.
let message = unsafe { std::slice::from_raw_parts(message, message_size) };
let signature = unwrap_or_abort(try_ecdsa_sign(message, &res.privateKey));
- if size != 0 {
- let data = NonNull::new(data).expect("data must not be null when size > 0");
- // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
- // the length of either buffer, and the caller ensures that `signature` cannot overlap
- // `data`. We allow data to be null, which is never valid, but only if size == 0
- // which is checked above.
- unsafe {
- ptr::copy_nonoverlapping(
- signature.as_ptr(),
- data.as_ptr(),
- std::cmp::min(signature.len(), size),
- )
- };
+ let data = NonNull::new(data).expect("data must not be null when size > 0");
+ // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+ // the length of either buffer, and the caller ensures that `signature` cannot overlap
+ // `data`. We allow data to be null, which is never valid, but only if size == 0
+ // which is checked above.
+ unsafe {
+ ptr::copy_nonoverlapping(
+ signature.as_ptr(),
+ data.as_ptr(),
+ usize::min(signature.len(), size),
+ )
+ };
+ if size < signature.len() {
+ // If the buffer is too small, return the maximum size of the signature to allow the caller
+ // to allocate a buffer large enough to call this function again.
+ MAX_ECDSA_P256_SIGNATURE_SIZE
+ } else {
+ signature.len()
}
- signature.len()
}
fn try_ecdsa_sign(message: &[u8], der_encoded_ec_private_key: &[u8]) -> Result<Vec<u8>> {