Merge "Refactor VM config to support different CPU topologies"
diff --git a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
index afdc944..9281e73 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -57,6 +57,10 @@
};
RpcSessionHandle session;
+ // We need a thread pool to be able to support linkToDeath, or callbacks
+ // (b/268335700). This if a fairly arbitrary number, although it happens to
+ // match the default max outgoing threads.
+ ARpcSession_setMaxIncomingThreads(session.get(), 10);
auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
return AIBinder_toJavaBinder(env, client);
}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 383f371..bf2d755 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -19,8 +19,7 @@
"libbinder_rs",
"libbyteorder",
"libcap_rust",
- "libdiced",
- "libdiced_open_dice_cbor",
+ "libdiced_open_dice",
"libdiced_sample_inputs",
"libdiced_utils",
"libglob",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index a7288b6..cf5e73e 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -16,8 +16,8 @@
use anyhow::{bail, Context, Error, Result};
use byteorder::{NativeEndian, ReadBytesExt};
-use diced_open_dice_cbor::{
- Config, ContextImpl, DiceMode, Hash, Hidden, InputValues, OpenDiceCborContext, CDI_SIZE,
+use diced_open_dice::{
+ retry_bcc_main_flow, Config, DiceMode, Hash, Hidden, InputValues, OwnedDiceArtifacts, CDI_SIZE,
};
use keystore2_crypto::ZVec;
use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
@@ -31,12 +31,23 @@
/// Artifacts that are kept in the process address space after the artifacts from the driver have
/// been consumed.
+/// TODO(b/267575445): Replace with `OwnedDiceArtifacts` from the library `diced_open_dice`.
pub struct DiceContext {
pub cdi_attest: [u8; CDI_SIZE],
pub cdi_seal: [u8; CDI_SIZE],
pub bcc: Vec<u8>,
}
+impl From<OwnedDiceArtifacts> for DiceContext {
+ fn from(dice_artifacts: OwnedDiceArtifacts) -> Self {
+ Self {
+ cdi_attest: dice_artifacts.cdi_values.cdi_attest,
+ cdi_seal: dice_artifacts.cdi_values.cdi_seal,
+ bcc: dice_artifacts.bcc[..].to_vec(),
+ }
+ }
+}
+
impl DiceContext {
pub fn get_sealing_key(&self, salt: &[u8], identifier: &[u8], keysize: u32) -> Result<ZVec> {
// Deterministically derive a key to use for sealing data based on salt. Use different salt
@@ -68,13 +79,9 @@
bail!("Strict boot requires DICE value from driver but none were found");
} else {
log::warn!("Using sample DICE values");
- let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()
.expect("Failed to create sample dice artifacts.");
- return Ok(Self::Fake(DiceContext {
- cdi_attest: cdi_attest[..].try_into().unwrap(),
- cdi_seal: cdi_seal[..].try_into().unwrap(),
- bcc,
- }));
+ return Ok(Self::Fake(dice_artifacts.into()));
};
let mut file = fs::File::open(driver_path)
@@ -153,8 +160,7 @@
Self::Real { cdi_attest, cdi_seal, bcc, .. } => (*cdi_attest, *cdi_seal, *bcc),
Self::Fake(fake) => (&fake.cdi_attest, &fake.cdi_seal, fake.bcc.as_slice()),
};
- let (cdi_attest, cdi_seal, bcc) = OpenDiceCborContext::new()
- .bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
+ let dice_artifacts = retry_bcc_main_flow(cdi_attest, cdi_seal, bcc, &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
@@ -162,11 +168,7 @@
fs::write(driver_path, "wipe")
.map_err(|err| Error::new(err).context("Wiping driver"))?;
}
- Ok(DiceContext {
- cdi_attest: cdi_attest[..].try_into().unwrap(),
- cdi_seal: cdi_seal[..].try_into().unwrap(),
- bcc,
- })
+ Ok(dice_artifacts.into())
}
}
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 2591fce..4dbf4ba 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -94,6 +94,14 @@
@After
public void tearDown() throws Exception {
+ try {
+ testIfDeviceIsCapable(getDevice());
+ } catch (Exception e) {
+ // Suppress execption here.
+ // If we throw exceptions in both setUp() and tearDown(),
+ // then test is reported as fail with org.junit.TestCouldNotBeSkippedException.
+ return;
+ }
// Set PKVM enable and reboot to prevent previous staged session.
if (!isCuttlefish()) {
setPKVMStatusWithRebootToBootloader(true);
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index 6196ec5..e8b6f36 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -6,6 +6,7 @@
name: "MicrodroidHostTestHelper",
srcs: ["java/**/*.java"],
libs: [
+ "androidx.annotation_annotation",
"compatibility-tradefed",
"tradefed",
"truth-prebuilt",
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java b/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
new file mode 100644
index 0000000..95eaa58
--- /dev/null
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2023 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.microdroid.test.host;
+
+import static java.nio.ByteOrder.LITTLE_ENDIAN;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+import java.nio.ByteBuffer;
+
+/** pvmfw.bin with custom config payloads on host. */
+public final class Pvmfw {
+ private static final int SIZE_8B = 8; // 8 bytes
+ private static final int SIZE_4K = 4 << 10; // 4 KiB, PAGE_SIZE
+ private static final int BUFFER_SIZE = 1024;
+ private static final int HEADER_SIZE = Integer.BYTES * 8; // Header has 8 integers.
+ private static final int HEADER_MAGIC = 0x666d7670;
+ private static final int HEADER_VERSION = getVersion(1, 0);
+ private static final int HEADER_FLAGS = 0;
+
+ @NonNull private final File mPvmfwBinFile;
+ @NonNull private final File mBccFile;
+ @Nullable private final File mDebugPolicyFile;
+
+ private Pvmfw(
+ @NonNull File pvmfwBinFile, @NonNull File bccFile, @Nullable File debugPolicyFile) {
+ mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile);
+ mBccFile = Objects.requireNonNull(bccFile);
+ mDebugPolicyFile = debugPolicyFile;
+ }
+
+ /**
+ * Serializes pvmfw.bin and its config, as written in the <a
+ * href="https://android.googlesource.com/platform/packages/modules/Virtualization/+/master/pvmfw/README.md">README.md</a>
+ */
+ public void serialize(@NonNull File outFile) throws IOException {
+ Objects.requireNonNull(outFile);
+
+ int bccOffset = HEADER_SIZE;
+ int bccSize = (int) mBccFile.length();
+
+ int debugPolicyOffset = alignTo(bccOffset + bccSize, SIZE_8B);
+ int debugPolicySize = mDebugPolicyFile == null ? 0 : (int) mDebugPolicyFile.length();
+
+ int totalSize = debugPolicyOffset + debugPolicySize;
+
+ ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE).order(LITTLE_ENDIAN);
+ header.putInt(HEADER_MAGIC);
+ header.putInt(HEADER_VERSION);
+ header.putInt(totalSize);
+ header.putInt(HEADER_FLAGS);
+ header.putInt(bccOffset);
+ header.putInt(bccSize);
+ header.putInt(debugPolicyOffset);
+ header.putInt(debugPolicySize);
+
+ try (FileOutputStream pvmfw = new FileOutputStream(outFile)) {
+ appendFile(pvmfw, mPvmfwBinFile);
+ padTo(pvmfw, SIZE_4K);
+ pvmfw.write(header.array());
+ padTo(pvmfw, HEADER_SIZE);
+ appendFile(pvmfw, mBccFile);
+ if (mDebugPolicyFile != null) {
+ padTo(pvmfw, SIZE_8B);
+ appendFile(pvmfw, mDebugPolicyFile);
+ }
+ padTo(pvmfw, SIZE_4K);
+ }
+ }
+
+ private void appendFile(@NonNull FileOutputStream out, @NonNull File inFile)
+ throws IOException {
+ byte buffer[] = new byte[BUFFER_SIZE];
+ try (FileInputStream in = new FileInputStream(inFile)) {
+ int size;
+ while (true) {
+ size = in.read(buffer);
+ if (size < 0) {
+ return;
+ }
+ out.write(buffer, /* offset= */ 0, size);
+ }
+ }
+ }
+
+ private void padTo(@NonNull FileOutputStream out, int size) throws IOException {
+ int streamSize = (int) out.getChannel().size();
+ for (int i = streamSize; i < alignTo(streamSize, size); i++) {
+ out.write(0); // write byte.
+ }
+ }
+
+ private static int alignTo(int x, int size) {
+ return (x + size - 1) & ~(size - 1);
+ }
+
+ private static int getVersion(int major, int minor) {
+ return ((major & 0xFFFF) << 16) | (minor & 0xFFFF);
+ }
+
+ /** Builder for {@link Pvmfw}. */
+ public static final class Builder {
+ @NonNull private final File mPvmfwBinFile;
+ @NonNull private final File mBccFile;
+ @Nullable private File mDebugPolicyFile;
+
+ public Builder(@NonNull File pvmfwBinFile, @NonNull File bccFile) {
+ mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile);
+ mBccFile = Objects.requireNonNull(bccFile);
+ }
+
+ @NonNull
+ public Builder setDebugPolicyOverlay(@Nullable File debugPolicyFile) {
+ mDebugPolicyFile = debugPolicyFile;
+ return this;
+ }
+
+ @NonNull
+ public Pvmfw build() {
+ return new Pvmfw(mPvmfwBinFile, mBccFile, mDebugPolicyFile);
+ }
+ }
+}
diff --git a/tests/hostside/tools/Android.bp b/tests/hostside/tools/Android.bp
new file mode 100644
index 0000000..f3cc275
--- /dev/null
+++ b/tests/hostside/tools/Android.bp
@@ -0,0 +1,10 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_binary_host {
+ name: "pvmfw-tool",
+ manifest: "pvmfw-tool-manifest.txt",
+ srcs: ["PvmfwTool.java"],
+ static_libs: ["MicrodroidHostTestHelper"],
+}
diff --git a/tests/hostside/tools/PvmfwTool.java b/tests/hostside/tools/PvmfwTool.java
new file mode 100644
index 0000000..18dd6d7
--- /dev/null
+++ b/tests/hostside/tools/PvmfwTool.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 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.microdroid;
+
+import com.android.microdroid.test.host.Pvmfw;
+
+import java.io.File;
+import java.io.IOException;
+
+/** CLI for {@link com.android.microdroid.test.host.Pvmfw}. */
+public class PvmfwTool {
+ public static void printUsage() {
+ System.out.println("pvmfw-tool: Appends pvmfw.bin and config payloads.");
+ System.out.println("Requires BCC and debug policy dtbo files");
+ System.out.println("");
+ System.out.println("Usage: pvmfw-tool <pvmfw_with_config> <pvmfw_bin> <bcc.dat> <dp.dtbo>");
+ }
+
+ public static void main(String[] args) {
+ if (args.length != 4) {
+ printUsage();
+ System.exit(1);
+ }
+
+ File out = new File(args[0]);
+ File pvmfw_bin = new File(args[1]);
+ File bcc_dat = new File(args[2]);
+ File dtbo = new File(args[3]);
+
+ try {
+ Pvmfw pvmfw = new Pvmfw.Builder(pvmfw_bin, bcc_dat).setDebugPolicyOverlay(dtbo).build();
+ pvmfw.serialize(out);
+ } catch (IOException e) {
+ e.printStackTrace();
+ printUsage();
+ System.exit(1);
+ }
+ }
+}
diff --git a/tests/hostside/tools/pvmfw-tool-manifest.txt b/tests/hostside/tools/pvmfw-tool-manifest.txt
new file mode 100644
index 0000000..dc71fd2
--- /dev/null
+++ b/tests/hostside/tools/pvmfw-tool-manifest.txt
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.microdroid.PvmfwTool
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index d8f0e11..3c487ee 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -2,12 +2,22 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "MicrodroidTestApp",
+java_defaults {
+ name: "MicrodroidTestAppsDefaults",
test_suites: [
"cts",
"general-tests",
],
+ sdk_version: "test_current",
+ jni_uses_platform_apis: true,
+ use_embedded_native_libs: true,
+ // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
+ compile_multilib: "both",
+}
+
+android_test {
+ name: "MicrodroidTestApp",
+ defaults: ["MicrodroidTestAppsDefaults"],
srcs: ["src/java/**/*.java"],
static_libs: [
"MicrodroidDeviceTestHelper",
@@ -19,7 +29,6 @@
"truth-prebuilt",
"compatibility-common-util-devicesidelib",
],
- sdk_version: "test_current",
jni_libs: [
"MicrodroidTestNativeLib",
"MicrodroidIdleNativeLib",
@@ -28,15 +37,16 @@
"MicrodroidPrivateLinkingNativeLib",
"MicrodroidCrashNativeLib",
],
- jni_uses_platform_apis: true,
- use_embedded_native_libs: true,
- // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
- compile_multilib: "both",
min_sdk_version: "33",
+ // Defined in ../vmshareapp/Android.bp
+ data: [":MicrodroidVmShareApp"],
}
-cc_library_shared {
- name: "MicrodroidTestNativeLib",
+// Defaults shared between MicrodroidTestNativeLib and MicrodroidPayloadInOtherAppNativeLib shared
+// libs. They are expected to share everything apart from the name, so that one app
+// (MicrodroidTestApp) can start a payload defined in the another app (MicrodroidVmShareApp).
+cc_defaults {
+ name: "MicrodroidTestNativeLibDefaults",
srcs: ["src/native/testbinary.cpp"],
stl: "libc++_static",
header_libs: ["vm_payload_restricted_headers"],
@@ -56,6 +66,16 @@
}
cc_library_shared {
+ name: "MicrodroidPayloadInOtherAppNativeLib",
+ defaults: ["MicrodroidTestNativeLibDefaults"],
+}
+
+cc_library_shared {
+ name: "MicrodroidTestNativeLib",
+ defaults: ["MicrodroidTestNativeLibDefaults"],
+}
+
+cc_library_shared {
name: "MicrodroidTestNativeLibSub",
srcs: ["src/native/testlib.cpp"],
stl: "libc++_static",
diff --git a/tests/testapk/AndroidManifest.xml b/tests/testapk/AndroidManifest.xml
index fefd20a..2ea3f6c 100644
--- a/tests/testapk/AndroidManifest.xml
+++ b/tests/testapk/AndroidManifest.xml
@@ -19,6 +19,9 @@
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
<uses-feature android:name="android.software.virtualization_framework" android:required="false" />
+ <queries>
+ <package android:name="com.android.microdroid.vmshare_app" />
+ </queries>
<application>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index 787ebd4..929dd31 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -21,6 +21,7 @@
<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="MicrodroidTestApp.apk" />
+ <option name="test-file-name" value="MicrodroidVmShareApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.microdroid.test" />
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 c31bf52..4a76ead 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -127,6 +127,8 @@
private static final long MIN_MEM_X86_64 = 196 * ONE_MEBI;
private static final String EXAMPLE_STRING = "Literally any string!! :)";
+ private static final String VM_SHARE_APP_PACKAGE_NAME = "com.android.microdroid.vmshare_app";
+
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
public void createAndConnectToVm() throws Exception {
@@ -1517,6 +1519,33 @@
assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
}
+ @Test
+ public void testStartVmWithPayloadOfAnotherApp() throws Exception {
+ assumeSupportedKernel();
+
+ Context ctx = getContext();
+ Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
+
+ VirtualMachineConfig config =
+ new VirtualMachineConfig.Builder(otherAppCtx)
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setProtectedVm(isProtectedVm())
+ .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
+ .build();
+
+ try (VirtualMachine vm = forceCreateNewVirtualMachine("vm_from_another_app", config)) {
+ TestResults results =
+ runVmTestService(
+ vm,
+ (ts, tr) -> {
+ tr.mAddInteger = ts.addInteger(101, 303);
+ });
+ assertThat(results.mAddInteger).isEqualTo(404);
+ }
+
+ getVirtualMachineManager().delete("vm_from_another_app");
+ }
+
private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
throws IOException {
File file1 = getVmFile(vmName1, fileName);
@@ -1590,6 +1619,11 @@
mTestService =
ITestService.Stub.asInterface(
vm.connectToVsockServer(ITestService.SERVICE_PORT));
+ // Make sure linkToDeath works, and include it in the log in case it's
+ // helpful.
+ mTestService
+ .asBinder()
+ .linkToDeath(() -> Log.i(TAG, "ITestService binder died"), 0);
} catch (Exception e) {
testResults.mException = e;
}
diff --git a/tests/vmshareapp/Android.bp b/tests/vmshareapp/Android.bp
new file mode 100644
index 0000000..2b117a1
--- /dev/null
+++ b/tests/vmshareapp/Android.bp
@@ -0,0 +1,15 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Helper app to verify that we can create a VM using others app payload, and share VMs between apps
+android_test_helper_app {
+ name: "MicrodroidVmShareApp",
+ // Defaults are defined in ../testapk/Android.bp
+ defaults: ["MicrodroidTestAppsDefaults"],
+ jni_libs: [
+ // Defined in ../testapk/Android.bp
+ "MicrodroidPayloadInOtherAppNativeLib",
+ ],
+ min_sdk_version: "UpsideDownCake",
+}
diff --git a/tests/vmshareapp/AndroidManifest.xml b/tests/vmshareapp/AndroidManifest.xml
new file mode 100644
index 0000000..eed3364
--- /dev/null
+++ b/tests/vmshareapp/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.microdroid.vmshare_app">
+ <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+
+ <uses-feature android:name="android.software.virtualization_framework"
+ android:required="false" />
+
+ <application />
+</manifest>