Merge "Report crosvm reboot reason on watchdog stall"
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
deleted file mode 100644
index 984625e..0000000
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ /dev/null
@@ -1,13 +0,0 @@
-drops {
- android_build_drop {
- build_id: "9110780"
- target: "u-boot_pvmfw"
- source_file: "pvmfw.img"
- }
- dest_file: "pvmfw/pvmfw.img"
- version: ""
- version_group: ""
- git_project: "platform/packages/modules/Virtualization"
- git_branch: "master"
- transform: TRANSFORM_NONE
-}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 46b9e77..0eb1257 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -17,26 +17,15 @@
package com.android.virt.fs;
import static com.android.microdroid.test.host.CommandResultSubject.assertThat;
-import static com.android.microdroid.test.host.LogArchiver.archiveLogThenDelete;
-import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
-import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.RootPermissionTest;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.util.PollingCheck;
import com.android.microdroid.test.host.CommandRunner;
-import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.TestDevice;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -45,19 +34,10 @@
import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
import com.android.tradefed.util.CommandResult;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestName;
import org.junit.runner.RunWith;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-
@RootPermissionTest
@RunWith(DeviceJUnit4ClassRunner.class)
public final class AuthFsHostTest extends BaseHostJUnit4Test {
@@ -68,39 +48,15 @@
/** Output directory where the test can generate output on Android */
private static final String TEST_OUTPUT_DIR = "/data/local/tmp/authfs/output_dir";
- /** VM's log file */
- private static final String LOG_PATH = TEST_OUTPUT_DIR + "/log.txt";
-
- /** File name of the test APK */
- private static final String TEST_APK_NAME = "MicrodroidTestApp.apk";
-
- /** VM config entry path in the test APK */
- private static final String VM_CONFIG_PATH_IN_APK = "assets/vm_config.json";
-
- /** Path to open_then_run on Android */
- private static final String OPEN_THEN_RUN_BIN = "/data/local/tmp/open_then_run";
-
/** Path to fsverity on Android */
private static final String FSVERITY_BIN = "/data/local/tmp/fsverity";
/** Mount point of authfs on Microdroid during the test */
private static final String MOUNT_DIR = "/data/local/tmp";
- /** Path to fd_server on Android */
- private static final String FD_SERVER_BIN = "/apex/com.android.virt/bin/fd_server";
-
- /** Path to authfs on Microdroid */
- private static final String AUTHFS_BIN = "/system/bin/authfs";
-
/** Input manifest path in the VM. */
private static final String INPUT_MANIFEST_PATH = "/mnt/apk/assets/input_manifest.pb";
- /** Plenty of time for authfs to get ready */
- private static final int AUTHFS_INIT_TIMEOUT_MS = 3000;
-
- /** FUSE's magic from statfs(2) */
- private static final String FUSE_SUPER_MAGIC_HEX = "65735546";
-
// fs-verity digest (sha256) of testdata/input.{4k, 4k1, 4m}
private static final String DIGEST_4K =
"sha256-9828cd65f4744d6adda216d3a63d8205375be485bfa261b3b8153d3358f5a576";
@@ -113,86 +69,20 @@
private static CommandRunner sAndroid;
private static CommandRunner sMicrodroid;
- private static boolean sAssumptionFailed;
- private ExecutorService mThreadPool = Executors.newCachedThreadPool();
-
- @Rule public TestLogData mTestLogs = new TestLogData();
- @Rule public TestName mTestName = new TestName();
+ @Rule public final AuthFsTestRule mAuthFsTestRule = new AuthFsTestRule();
@BeforeClassWithInfo
public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
- assertNotNull(testInfo.getDevice());
- if (!(testInfo.getDevice() instanceof TestDevice)) {
- CLog.w("Unexpected type of ITestDevice. Skipping.");
- return;
- }
- TestDevice androidDevice = (TestDevice) testInfo.getDevice();
- sAndroid = new CommandRunner(androidDevice);
-
- // NB: We can't use assumeTrue because the assumption exception is NOT handled by the test
- // infra when it is thrown from a class method (see b/37502066). We need to skip both here
- // and in setUp.
- if (!androidDevice.supportsMicrodroid()) {
- CLog.i("Microdroid not supported. Skipping.");
- return;
- }
-
- // For each test case, boot and adb connect to a new Microdroid
- CLog.i("Starting the shared VM");
- ITestDevice microdroidDevice =
- MicrodroidBuilder
- .fromFile(findTestApk(testInfo.getBuildInfo()), VM_CONFIG_PATH_IN_APK)
- .debugLevel("full")
- .build((TestDevice) androidDevice);
-
- // From this point on, we need to tear down the Microdroid instance
- sMicrodroid = new CommandRunner(microdroidDevice);
-
- // Root because authfs (started from shell in this test) currently require root to open
- // /dev/fuse and mount the FUSE.
- assertThat(microdroidDevice.enableAdbRoot()).isTrue();
+ AuthFsTestRule.setUpClass(testInfo);
+ sAndroid = AuthFsTestRule.getAndroid();
+ sMicrodroid = AuthFsTestRule.getMicrodroid();
}
@AfterClassWithInfo
public static void afterClassWithDevice(TestInformation testInfo)
throws DeviceNotAvailableException {
- assertNotNull(sAndroid);
-
- if (sMicrodroid != null) {
- CLog.i("Shutting down shared VM");
- ((TestDevice) testInfo.getDevice()).shutdownMicrodroid(sMicrodroid.getDevice());
- sMicrodroid = null;
- }
-
- sAndroid = null;
- }
-
- @Before
- public void setUp() throws Exception {
- assumeTrue(((TestDevice) getDevice()).supportsMicrodroid());
- sAndroid.run("mkdir " + TEST_OUTPUT_DIR);
- }
-
- @After
- public void tearDown() throws Exception {
- if (sMicrodroid != null) {
- sMicrodroid.tryRun("killall authfs");
- sMicrodroid.tryRun("umount " + MOUNT_DIR);
- }
-
- assertNotNull(sAndroid);
- sAndroid.tryRun("killall fd_server");
-
- // Even though we only run one VM for the whole class, and could have collect the VM log
- // after all tests are done, TestLogData doesn't seem to work at class level. Hence,
- // collect recent logs manually for each test method.
- String vmRecentLog = TEST_OUTPUT_DIR + "/vm_recent.log";
- sAndroid.tryRun("tail -n 50 " + LOG_PATH + " > " + vmRecentLog);
- archiveLogThenDelete(mTestLogs, getDevice(), vmRecentLog,
- "vm_recent.log-" + mTestName.getMethodName());
-
- sAndroid.run("rm -rf " + TEST_OUTPUT_DIR);
+ AuthFsTestRule.tearDownClass(testInfo);
}
@Test
@@ -534,7 +424,7 @@
sAndroid.run("test ! -d " + androidOutputDir + "/dir/dir2");
// Can only delete a directory if empty
assertThat(sMicrodroid.runForResult("rmdir " + authfsOutputDir + "/dir")).isFailed();
- sMicrodroid.run("test -d " + authfsOutputDir + "/dir"); // still there
+ sMicrodroid.run("test -d " + authfsOutputDir + "/dir"); // still there
sMicrodroid.run("rm " + authfsOutputDir + "/dir/file");
sMicrodroid.run("rmdir " + authfsOutputDir + "/dir");
sMicrodroid.run("test ! -d " + authfsOutputDir + "/dir");
@@ -560,9 +450,9 @@
assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_file")).isFailed();
assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir")).isFailed();
assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir/file"))
- .isFailed();
+ .isFailed();
assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir/dir"))
- .isFailed();
+ .isFailed();
}
@Test
@@ -741,16 +631,8 @@
// Verify
// Magic matches. Has only 2 inodes (root and "/3").
assertEquals(
- FUSE_SUPER_MAGIC_HEX + " 2", sMicrodroid.run("stat -f -c '%t %c' " + MOUNT_DIR));
- }
-
- private static File findTestApk(IBuildInfo buildInfo) {
- try {
- return (new CompatibilityBuildHelper(buildInfo)).getTestFile(TEST_APK_NAME);
- } catch (FileNotFoundException e) {
- fail("Missing test file: " + TEST_APK_NAME);
- return null;
- }
+ mAuthFsTestRule.FUSE_SUPER_MAGIC_HEX + " 2",
+ sMicrodroid.run("stat -f -c '%t %c' " + MOUNT_DIR));
}
private void expectBackingFileConsistency(
@@ -832,66 +714,11 @@
}
private void runAuthFsOnMicrodroid(String flags) {
- String cmd = AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
-
- AtomicBoolean starting = new AtomicBoolean(true);
- mThreadPool.submit(
- () -> {
- // authfs may fail to start if fd_server is not yet listening on the vsock
- // ("Error: Invalid raw AIBinder"). Just restart if that happens.
- while (starting.get()) {
- try {
- CLog.i("Starting authfs");
- CommandResult result = sMicrodroid.runForResult(cmd);
- CLog.w("authfs has stopped: " + result);
- } catch (DeviceNotAvailableException e) {
- CLog.e("Error running authfs", e);
- throw new RuntimeException(e);
- }
- }
- });
- try {
- PollingCheck.waitFor(
- AUTHFS_INIT_TIMEOUT_MS, () -> isMicrodroidDirectoryOnFuse(MOUNT_DIR));
- } catch (Exception e) {
- // Convert the broad Exception into an unchecked exception to avoid polluting all other
- // methods. waitFor throws Exception because the callback, Callable#call(), has a
- // signature to throw an Exception.
- throw new RuntimeException(e);
- } finally {
- starting.set(false);
- }
+ mAuthFsTestRule.runAuthFsOnMicrodroid(flags);
}
private void runFdServerOnAndroid(String helperFlags, String fdServerFlags)
throws DeviceNotAvailableException {
- String cmd =
- "cd "
- + TEST_DIR
- + " && "
- + OPEN_THEN_RUN_BIN
- + " "
- + helperFlags
- + " -- "
- + FD_SERVER_BIN
- + " "
- + fdServerFlags;
-
- mThreadPool.submit(
- () -> {
- try {
- CLog.i("Starting fd_server");
- CommandResult result = sAndroid.runForResult(cmd);
- CLog.w("fd_server has stopped: " + result);
- } catch (DeviceNotAvailableException e) {
- CLog.e("Error running fd_server", e);
- throw new RuntimeException(e);
- }
- });
- }
-
- private boolean isMicrodroidDirectoryOnFuse(String path) throws DeviceNotAvailableException {
- String fs_type = sMicrodroid.tryRun("stat -f -c '%t' " + path);
- return FUSE_SUPER_MAGIC_HEX.equals(fs_type);
+ mAuthFsTestRule.runFdServerOnAndroid(helperFlags, fdServerFlags);
}
}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java b/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java
new file mode 100644
index 0000000..e6081f7
--- /dev/null
+++ b/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2022 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.fs;
+
+import static com.android.microdroid.test.host.LogArchiver.archiveLogThenDelete;
+import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
+import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.microdroid.test.host.CommandRunner;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.TestDevice;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.CommandResult;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Custom TestRule for AuthFs tests. */
+public class AuthFsTestRule extends TestLogData {
+ /** FUSE's magic from statfs(2) */
+ static final String FUSE_SUPER_MAGIC_HEX = "65735546";
+
+ /** VM config entry path in the test APK */
+ private static final String VM_CONFIG_PATH_IN_APK = "assets/vm_config.json";
+
+ /** Test directory on Android where data are located */
+ private static final String TEST_DIR = "/data/local/tmp/authfs";
+
+ /** File name of the test APK */
+ private static final String TEST_APK_NAME = "MicrodroidTestApp.apk";
+
+ /** Output directory where the test can generate output on Android */
+ private static final String TEST_OUTPUT_DIR = "/data/local/tmp/authfs/output_dir";
+
+ /** Mount point of authfs on Microdroid during the test */
+ private static final String MOUNT_DIR = "/data/local/tmp";
+
+ /** VM's log file */
+ private static final String LOG_PATH = TEST_OUTPUT_DIR + "/log.txt";
+
+ /** Path to open_then_run on Android */
+ private static final String OPEN_THEN_RUN_BIN = "/data/local/tmp/open_then_run";
+
+ /** Path to fd_server on Android */
+ private static final String FD_SERVER_BIN = "/apex/com.android.virt/bin/fd_server";
+
+ /** Path to authfs on Microdroid */
+ private static final String AUTHFS_BIN = "/system/bin/authfs";
+
+ /** Plenty of time for authfs to get ready */
+ private static final int AUTHFS_INIT_TIMEOUT_MS = 3000;
+
+ private static TestInformation sTestInfo;
+ private static CommandRunner sAndroid;
+ private static CommandRunner sMicrodroid;
+
+ private final ExecutorService mThreadPool = Executors.newCachedThreadPool();
+
+ static void setUpClass(TestInformation testInfo) throws Exception {
+ assertNotNull(testInfo.getDevice());
+ if (!(testInfo.getDevice() instanceof TestDevice)) {
+ CLog.w("Unexpected type of ITestDevice. Skipping.");
+ return;
+ }
+ sTestInfo = testInfo;
+ TestDevice androidDevice = getDevice();
+ sAndroid = new CommandRunner(androidDevice);
+
+ // NB: We can't use assumeTrue because the assumption exception is NOT handled by the test
+ // infra when it is thrown from a class method (see b/37502066). We need to skip both here
+ // and in setUp.
+ if (!androidDevice.supportsMicrodroid()) {
+ CLog.i("Microdroid not supported. Skipping.");
+ return;
+ }
+
+ // For each test case, boot and adb connect to a new Microdroid
+ CLog.i("Starting the shared VM");
+ ITestDevice microdroidDevice =
+ MicrodroidBuilder.fromFile(
+ findTestApk(testInfo.getBuildInfo()), VM_CONFIG_PATH_IN_APK)
+ .debugLevel("full")
+ .build((TestDevice) androidDevice);
+
+ // From this point on, we need to tear down the Microdroid instance
+ sMicrodroid = new CommandRunner(microdroidDevice);
+
+ // Root because authfs (started from shell in this test) currently require root to open
+ // /dev/fuse and mount the FUSE.
+ assertThat(microdroidDevice.enableAdbRoot()).isTrue();
+ }
+
+ static void tearDownClass(TestInformation testInfo) throws DeviceNotAvailableException {
+ assertNotNull(sAndroid);
+
+ if (sMicrodroid != null) {
+ CLog.i("Shutting down shared VM");
+ ((TestDevice) testInfo.getDevice()).shutdownMicrodroid(sMicrodroid.getDevice());
+ sMicrodroid = null;
+ }
+
+ sAndroid = null;
+ }
+
+ /** This method is supposed to be called after {@link #setUpTest()}. */
+ static CommandRunner getAndroid() {
+ assertThat(sAndroid).isNotNull();
+ return sAndroid;
+ }
+
+ /** This method is supposed to be called after {@link #setUpTest()}. */
+ static CommandRunner getMicrodroid() {
+ assertThat(sMicrodroid).isNotNull();
+ return sMicrodroid;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return super.apply(
+ new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ setUpTest();
+ base.evaluate();
+ tearDownTest(description.getMethodName());
+ }
+ },
+ description);
+ }
+
+ void runFdServerOnAndroid(String helperFlags, String fdServerFlags)
+ throws DeviceNotAvailableException {
+ String cmd =
+ "cd "
+ + TEST_DIR
+ + " && "
+ + OPEN_THEN_RUN_BIN
+ + " "
+ + helperFlags
+ + " -- "
+ + FD_SERVER_BIN
+ + " "
+ + fdServerFlags;
+ Future<?> unusedFuture = mThreadPool.submit(() -> runForResult(sAndroid, cmd, "fd_server"));
+ }
+
+ void runAuthFsOnMicrodroid(String flags) {
+ String cmd = AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
+
+ AtomicBoolean starting = new AtomicBoolean(true);
+ Future<?> unusedFuture =
+ mThreadPool.submit(
+ () -> {
+ // authfs may fail to start if fd_server is not yet listening on the
+ // vsock
+ // ("Error: Invalid raw AIBinder"). Just restart if that happens.
+ while (starting.get()) {
+ runForResult(sMicrodroid, cmd, "authfs");
+ }
+ });
+ try {
+ PollingCheck.waitFor(
+ AUTHFS_INIT_TIMEOUT_MS, () -> isMicrodroidDirectoryOnFuse(MOUNT_DIR));
+ } catch (Exception e) {
+ // Convert the broad Exception into an unchecked exception to avoid polluting all other
+ // methods. waitFor throws Exception because the callback, Callable#call(), has a
+ // signature to throw an Exception.
+ throw new RuntimeException(e);
+ } finally {
+ starting.set(false);
+ }
+ }
+
+ private static File findTestApk(IBuildInfo buildInfo) {
+ try {
+ return (new CompatibilityBuildHelper(buildInfo)).getTestFile(TEST_APK_NAME);
+ } catch (FileNotFoundException e) {
+ fail("Missing test file: " + TEST_APK_NAME);
+ return null;
+ }
+ }
+
+ private static TestDevice getDevice() {
+ return (TestDevice) sTestInfo.getDevice();
+ }
+
+ private void runForResult(CommandRunner cmdRunner, String cmd, String serviceName) {
+ try {
+ CLog.i("Starting " + serviceName);
+ CommandResult result = cmdRunner.runForResult(cmd);
+ CLog.w(serviceName + " has stopped: " + result);
+ } catch (DeviceNotAvailableException e) {
+ CLog.e("Error running " + serviceName, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean isMicrodroidDirectoryOnFuse(String path) throws DeviceNotAvailableException {
+ String fs_type = sMicrodroid.tryRun("stat -f -c '%t' " + path);
+ return FUSE_SUPER_MAGIC_HEX.equals(fs_type);
+ }
+
+ private void setUpTest() throws Exception {
+ assumeTrue(getDevice().supportsMicrodroid());
+ sAndroid.run("mkdir -p " + TEST_OUTPUT_DIR);
+ }
+
+ private void tearDownTest(String testName) throws Exception {
+ if (sMicrodroid != null) {
+ sMicrodroid.tryRun("killall authfs");
+ sMicrodroid.tryRun("umount " + MOUNT_DIR);
+ }
+
+ assertNotNull(sAndroid);
+ sAndroid.tryRun("killall fd_server");
+
+ // Even though we only run one VM for the whole class, and could have collect the VM log
+ // after all tests are done, TestLogData doesn't seem to work at class level. Hence,
+ // collect recent logs manually for each test method.
+ String vmRecentLog = TEST_OUTPUT_DIR + "/vm_recent.log";
+ sAndroid.tryRun("tail -n 50 " + LOG_PATH + " > " + vmRecentLog);
+ archiveLogThenDelete(this, getDevice(), vmRecentLog, "vm_recent.log-" + testName);
+
+ sAndroid.run("rm -rf " + TEST_OUTPUT_DIR);
+ }
+}
diff --git a/compos/Android.bp b/compos/Android.bp
index ea7c4d6..0890e9d 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -7,7 +7,6 @@
edition: "2021",
srcs: ["src/compsvc_main.rs"],
rustlibs: [
- "android.system.virtualmachineservice-rust",
"authfs_aidl_interface-rust",
"compos_aidl_interface-rust",
"libandroid_logger",
@@ -24,6 +23,7 @@
"librpcbinder_rs",
"librustutils",
"libscopeguard",
+ "libvm_payload_bindgen",
],
prefer_rlib: true,
shared_libs: [
diff --git a/compos/compos_key_helper/Android.bp b/compos/compos_key_helper/Android.bp
index a932b40..c9480fc 100644
--- a/compos/compos_key_helper/Android.bp
+++ b/compos/compos_key_helper/Android.bp
@@ -24,10 +24,11 @@
defaults: ["compos_key_defaults"],
srcs: ["compos_key_main.cpp"],
- static_libs: ["libcompos_key"],
+ static_libs: [
+ "libcompos_key",
+ ],
shared_libs: [
- "android.hardware.security.dice-V1-ndk",
- "android.security.dice-ndk",
+ "libvm_payload",
"libbinder_ndk",
],
}
diff --git a/compos/compos_key_helper/compos_key.cpp b/compos/compos_key_helper/compos_key.cpp
index 2e3252c..34b931a 100644
--- a/compos/compos_key_helper/compos_key.cpp
+++ b/compos/compos_key_helper/compos_key.cpp
@@ -24,25 +24,11 @@
using android::base::Error;
using android::base::Result;
using compos_key::Ed25519KeyPair;
+using compos_key::Seed;
using compos_key::Signature;
-// Used to ensure the key we derive is distinct from any other.
-constexpr const char* kSigningKeyInfo = "CompOS signing key";
-
namespace compos_key {
-Result<Ed25519KeyPair> deriveKeyFromSecret(const uint8_t* secret, size_t secret_size) {
- // Ed25519 private keys are derived from a 32 byte seed:
- // https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5
- std::array<uint8_t, 32> seed;
-
- // We derive the seed from the secret using HKDF - see
- // https://datatracker.ietf.org/doc/html/rfc5869#section-2.
- if (!HKDF(seed.data(), seed.size(), EVP_sha256(), secret, secret_size, /*salt=*/nullptr,
- /*salt_len=*/0, reinterpret_cast<const uint8_t*>(kSigningKeyInfo),
- strlen(kSigningKeyInfo))) {
- return Error() << "HKDF failed";
- }
-
+Result<Ed25519KeyPair> keyFromSeed(const Seed& seed) {
Ed25519KeyPair result;
ED25519_keypair_from_seed(result.public_key.data(), result.private_key.data(), seed.data());
return result;
diff --git a/compos/compos_key_helper/compos_key.h b/compos/compos_key_helper/compos_key.h
index e9c6061..8629cf6 100644
--- a/compos/compos_key_helper/compos_key.h
+++ b/compos/compos_key_helper/compos_key.h
@@ -22,8 +22,11 @@
#include <array>
namespace compos_key {
+constexpr size_t ED25519_SEED_LEN = 32;
+
using PrivateKey = std::array<uint8_t, ED25519_PRIVATE_KEY_LEN>;
using PublicKey = std::array<uint8_t, ED25519_PUBLIC_KEY_LEN>;
+using Seed = std::array<uint8_t, ED25519_SEED_LEN>;
using Signature = std::array<uint8_t, ED25519_SIGNATURE_LEN>;
struct Ed25519KeyPair {
@@ -31,8 +34,7 @@
PublicKey public_key;
};
-android::base::Result<Ed25519KeyPair> deriveKeyFromSecret(const uint8_t* secret,
- size_t secret_size);
+android::base::Result<Ed25519KeyPair> keyFromSeed(const Seed& seed);
android::base::Result<Signature> sign(const PrivateKey& private_key, const uint8_t* data,
size_t data_size);
diff --git a/compos/compos_key_helper/compos_key_main.cpp b/compos/compos_key_helper/compos_key_main.cpp
index 9ba9f8d..224e42b 100644
--- a/compos/compos_key_helper/compos_key_main.cpp
+++ b/compos/compos_key_helper/compos_key_main.cpp
@@ -14,50 +14,38 @@
* limitations under the License.
*/
-#include <aidl/android/security/dice/IDiceNode.h>
#include <android-base/file.h>
#include <android-base/logging.h>
-#include <android/binder_auto_utils.h>
-#include <android/binder_manager.h>
#include <unistd.h>
+#include <vm_payload.h>
#include <string_view>
#include "compos_key.h"
-using aidl::android::hardware::security::dice::Bcc;
-using aidl::android::hardware::security::dice::BccHandover;
-using aidl::android::hardware::security::dice::InputValues;
-using aidl::android::security::dice::IDiceNode;
using android::base::Error;
using android::base::ReadFdToString;
using android::base::Result;
using android::base::WriteFully;
using namespace std::literals;
using compos_key::Ed25519KeyPair;
+using compos_key::Seed;
namespace {
-Result<Ed25519KeyPair> deriveKeyFromDice() {
- ndk::SpAIBinder binder{AServiceManager_getService("android.security.dice.IDiceNode")};
- auto dice_node = IDiceNode::fromBinder(binder);
- if (!dice_node) {
- return Error() << "Unable to connect to IDiceNode";
- }
- const std::vector<InputValues> empty_input_values;
- BccHandover bcc;
- auto status = dice_node->derive(empty_input_values, &bcc);
- if (!status.isOk()) {
- return Error() << "Derive failed: " << status.getDescription();
- }
+constexpr const char* kSigningKeySeedIdentifier = "CompOS signing key seed";
- // We use the sealing CDI because we want stability - the key needs to be the same
- // for any instance of the "same" VM.
- return compos_key::deriveKeyFromSecret(bcc.cdiSeal.data(), bcc.cdiSeal.size());
+Result<Ed25519KeyPair> getSigningKey() {
+ Seed seed;
+ if (!get_vm_instance_secret(kSigningKeySeedIdentifier, strlen(kSigningKeySeedIdentifier),
+ seed.data(), seed.size())) {
+ return Error() << "Failed to get signing key seed";
+ }
+ return compos_key::keyFromSeed(seed);
}
int write_public_key() {
- auto key_pair = deriveKeyFromDice();
+ auto key_pair = getSigningKey();
if (!key_pair.ok()) {
LOG(ERROR) << key_pair.error();
return 1;
@@ -70,22 +58,14 @@
}
int write_bcc() {
- ndk::SpAIBinder binder{AServiceManager_getService("android.security.dice.IDiceNode")};
- auto dice_node = IDiceNode::fromBinder(binder);
- if (!dice_node) {
- LOG(ERROR) << "Unable to connect to IDiceNode";
+ uint8_t bcc[4096];
+ size_t bcc_size = get_dice_attestation_chain(bcc, sizeof(bcc));
+ if (bcc_size == 0) {
+ LOG(ERROR) << "Failed to get attestation chain";
return 1;
}
- const std::vector<InputValues> empty_input_values;
- Bcc bcc;
- auto status = dice_node->getAttestationChain(empty_input_values, &bcc);
- if (!status.isOk()) {
- LOG(ERROR) << "GetAttestationChain failed: " << status.getDescription();
- return 1;
- }
-
- if (!WriteFully(STDOUT_FILENO, bcc.data.data(), bcc.data.size())) {
+ if (!WriteFully(STDOUT_FILENO, bcc, bcc_size)) {
PLOG(ERROR) << "Write failed";
return 1;
}
@@ -100,7 +80,7 @@
return 1;
}
- auto key_pair = deriveKeyFromDice();
+ auto key_pair = getSigningKey();
if (!key_pair.ok()) {
LOG(ERROR) << key_pair.error();
return 1;
diff --git a/compos/compos_key_helper/compos_key_test.cpp b/compos/compos_key_helper/compos_key_test.cpp
index e4c3e8a..37aefcf 100644
--- a/compos/compos_key_helper/compos_key_test.cpp
+++ b/compos/compos_key_helper/compos_key_test.cpp
@@ -22,30 +22,32 @@
using namespace compos_key;
-const std::vector<uint8_t> secret = {1, 2, 3};
-const std::vector<uint8_t> other_secret = {3, 2, 3};
+constexpr Seed seed = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
+constexpr Seed other_seed = {3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2,
+ 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2};
const std::vector<uint8_t> data = {42, 180, 65, 0};
struct ComposKeyTest : public testing::Test {
Ed25519KeyPair key_pair;
void SetUp() override {
- auto key_pair = deriveKeyFromSecret(secret.data(), secret.size());
+ auto key_pair = keyFromSeed(seed);
ASSERT_TRUE(key_pair.ok()) << key_pair.error();
this->key_pair = *key_pair;
}
};
-TEST_F(ComposKeyTest, SameSecretSameKey) {
- auto other_key_pair = deriveKeyFromSecret(secret.data(), secret.size());
+TEST_F(ComposKeyTest, SameSeedSameKey) {
+ auto other_key_pair = keyFromSeed(seed);
ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
ASSERT_EQ(key_pair.private_key, other_key_pair->private_key);
ASSERT_EQ(key_pair.public_key, other_key_pair->public_key);
}
-TEST_F(ComposKeyTest, DifferentSecretDifferentKey) {
- auto other_key_pair = deriveKeyFromSecret(other_secret.data(), other_secret.size());
+TEST_F(ComposKeyTest, DifferentSeedDifferentKey) {
+ auto other_key_pair = keyFromSeed(other_seed);
ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
ASSERT_NE(key_pair.private_key, other_key_pair->private_key);
@@ -84,7 +86,7 @@
TEST_F(ComposKeyTest, WrongKeyDoesNotVerify) {
auto signature = sign(key_pair.private_key, data.data(), data.size());
- auto other_key_pair = deriveKeyFromSecret(other_secret.data(), other_secret.size());
+ auto other_key_pair = keyFromSeed(other_seed);
ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
bool verified = verify(other_key_pair->public_key, *signature, data.data(), data.size());
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 16d258e..64e2ced 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -22,20 +22,12 @@
mod compsvc;
mod fsverity;
-use android_system_virtualmachineservice::{
- aidl::android::system::virtualmachineservice::IVirtualMachineService::{
- IVirtualMachineService, VM_BINDER_SERVICE_PORT,
- },
- binder::Strong,
-};
-use anyhow::{bail, Context, Result};
+use anyhow::{bail, Result};
use compos_common::COMPOS_VSOCK_PORT;
use log::{debug, error};
-use rpcbinder::{get_vsock_rpc_interface, run_rpc_server};
+use rpcbinder::run_rpc_server;
use std::panic;
-
-/// The CID representing the host VM
-const VMADDR_CID_HOST: u32 = 2;
+use vm_payload_bindgen::notify_payload_ready;
fn main() {
if let Err(e) = try_main() {
@@ -54,14 +46,10 @@
}));
let service = compsvc::new_binder()?.as_binder();
- let vm_service = get_vm_service()?;
-
debug!("compsvc is starting as a rpc service.");
-
- let retval = run_rpc_server(service, COMPOS_VSOCK_PORT, || {
- if let Err(e) = vm_service.notifyPayloadReady() {
- error!("Unable to notify ready: {}", e);
- }
+ // SAFETY: Invokes a method from the bindgen library `vm_payload_bindgen`.
+ let retval = run_rpc_server(service, COMPOS_VSOCK_PORT, || unsafe {
+ notify_payload_ready();
});
if retval {
debug!("RPC server has shut down gracefully");
@@ -70,8 +58,3 @@
bail!("Premature termination of RPC server");
}
}
-
-fn get_vm_service() -> Result<Strong<dyn IVirtualMachineService>> {
- get_vsock_rpc_interface(VMADDR_CID_HOST, VM_BINDER_SERVICE_PORT as u32)
- .context("Connecting to IVirtualMachineService")
-}
diff --git a/demo/assets/vm_config.json b/demo/assets/vm_config.json
deleted file mode 100644
index da420ff..0000000
--- a/demo/assets/vm_config.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "os": {
- "name": "microdroid"
- },
- "task": {
- "type": "microdroid_launcher",
- "command": "MicrodroidTestNativeLib.so",
- "args": [
- "hello",
- "microdroid"
- ]
- },
- "export_tombstones": true
-}
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 6266c18..b52ef40 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -24,7 +24,6 @@
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
import android.system.virtualmachine.VirtualMachineException;
import android.system.virtualmachine.VirtualMachineManager;
import android.util.Log;
@@ -49,7 +48,6 @@
import java.io.InputStreamReader;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
/**
* This app is to demonstrate the use of APIs in the android.system.virtualmachine library.
@@ -75,10 +73,11 @@
// When the button is clicked, run or stop the VM
runStopButton.setOnClickListener(
v -> {
- if (model.getStatus().getValue() == VirtualMachine.Status.RUNNING) {
+ Integer status = model.getStatus().getValue();
+ if (status != null && status == VirtualMachine.STATUS_RUNNING) {
model.stop();
} else {
- CheckBox debugModeCheckBox = (CheckBox) findViewById(R.id.debugMode);
+ CheckBox debugModeCheckBox = findViewById(R.id.debugMode);
final boolean debug = debugModeCheckBox.isChecked();
model.run(debug);
}
@@ -88,7 +87,7 @@
model.getStatus()
.observeForever(
status -> {
- if (status == VirtualMachine.Status.RUNNING) {
+ if (status == VirtualMachine.STATUS_RUNNING) {
runStopButton.setText("Stop");
// Clear the outputs from the previous run
consoleView.setText("");
@@ -150,12 +149,12 @@
private final MutableLiveData<String> mConsoleOutput = new MutableLiveData<>();
private final MutableLiveData<String> mLogOutput = new MutableLiveData<>();
private final MutableLiveData<String> mPayloadOutput = new MutableLiveData<>();
- private final MutableLiveData<VirtualMachine.Status> mStatus = new MutableLiveData<>();
+ private final MutableLiveData<Integer> mStatus = new MutableLiveData<>();
private ExecutorService mExecutorService;
public VirtualMachineModel(Application app) {
super(app);
- mStatus.setValue(VirtualMachine.Status.DELETED);
+ mStatus.setValue(VirtualMachine.STATUS_DELETED);
}
/** Runs a VM */
@@ -169,8 +168,8 @@
private final ExecutorService mService = mExecutorService;
@Override
- public void onPayloadStarted(
- VirtualMachine vm, ParcelFileDescriptor stream) {
+ public void onPayloadStarted(VirtualMachine vm,
+ ParcelFileDescriptor stream) {
if (stream == null) {
mPayloadOutput.postValue("(no output available)");
return;
@@ -189,25 +188,13 @@
}
mPayloadOutput.postValue("(Payload is ready. Testing VM service...)");
- Future<IBinder> service;
- try {
- service = vm.connectToVsockServer(ITestService.SERVICE_PORT);
- } catch (VirtualMachineException e) {
- mPayloadOutput.postValue(
- String.format(
- "(Exception while connecting VM's binder"
- + " service: %s)",
- e.getMessage()));
- return;
- }
-
- mService.execute(() -> testVMService(service));
+ mService.execute(() -> testVmService(vm));
}
- private void testVMService(Future<IBinder> service) {
+ private void testVmService(VirtualMachine vm) {
IBinder binder;
try {
- binder = service.get();
+ binder = vm.connectToVsockServer(ITestService.SERVICE_PORT);
} catch (Exception e) {
if (!Thread.interrupted()) {
mPayloadOutput.postValue(
@@ -256,9 +243,9 @@
}
@Override
- public void onDied(VirtualMachine vm, int reason) {
+ public void onStopped(VirtualMachine vm, int reason) {
mService.shutdownNow();
- mStatus.postValue(VirtualMachine.Status.STOPPED);
+ mStatus.postValue(VirtualMachine.STATUS_STOPPED);
}
@Override
@@ -271,9 +258,11 @@
try {
VirtualMachineConfig.Builder builder =
- new VirtualMachineConfig.Builder(getApplication(), "assets/vm_config.json");
+ new VirtualMachineConfig.Builder(getApplication());
+ builder.setPayloadBinaryPath("MicrodroidTestNativeLib.so");
+ builder.setProtectedVm(true);
if (debug) {
- builder.debugLevel(DebugLevel.FULL);
+ builder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL);
}
VirtualMachineConfig config = builder.build();
VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
@@ -306,7 +295,7 @@
}
mVirtualMachine = null;
mExecutorService.shutdownNow();
- mStatus.postValue(VirtualMachine.Status.STOPPED);
+ mStatus.postValue(VirtualMachine.STATUS_STOPPED);
}
/** Returns the console output from the VM */
@@ -325,7 +314,7 @@
}
/** Returns the status of the VM */
- public LiveData<VirtualMachine.Status> getStatus() {
+ public LiveData<Integer> getStatus() {
return mStatus;
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index cc99006..bdec164 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -18,16 +18,43 @@
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED;
+import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_INVALID_CONFIG;
+import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_VERIFICATION_FAILED;
+import static android.system.virtualmachine.VirtualMachineCallback.ERROR_UNKNOWN;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_CRASH;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_ERROR;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_HANGUP;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_INFRASTRUCTURE_ERROR;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_KILLED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_REBOOT;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_SHUTDOWN;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_UNKNOWN;
+
+import static java.util.Objects.requireNonNull;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.system.virtualizationcommon.ErrorCode;
+import android.system.virtualizationservice.DeathReason;
import android.system.virtualizationservice.IVirtualMachine;
import android.system.virtualizationservice.IVirtualMachineCallback;
import android.system.virtualizationservice.IVirtualizationService;
@@ -45,19 +72,19 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.zip.ZipFile;
@@ -68,7 +95,7 @@
*
* @hide
*/
-public class VirtualMachine {
+public class VirtualMachine implements AutoCloseable {
private static final Map<Context, Map<String, WeakReference<VirtualMachine>>> sInstances =
new WeakHashMap<>();
@@ -92,18 +119,35 @@
/** Name of the virtualization service. */
private static final String SERVICE_NAME = "android.system.virtualizationservice";
- /** Status of a virtual machine */
- public enum Status {
- /** The virtual machine has just been created, or {@link #stop()} was called on it. */
- STOPPED,
- /** The virtual machine is running. */
- RUNNING,
- /**
- * The virtual machine is deleted. This is a irreversable state. Once a virtual machine is
- * deleted, it can never be undone, which means all its secrets are permanently lost.
- */
- DELETED,
- }
+ /** The permission needed to create or run a virtual machine. */
+ public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION =
+ "android.permission.MANAGE_VIRTUAL_MACHINE";
+
+ /**
+ * Status of a virtual machine
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ STATUS_STOPPED,
+ STATUS_RUNNING,
+ STATUS_DELETED
+ })
+ public @interface Status {}
+
+ /** The virtual machine has just been created, or {@link #stop()} was called on it. */
+ public static final int STATUS_STOPPED = 0;
+
+ /** The virtual machine is running. */
+ public static final int STATUS_RUNNING = 1;
+
+ /**
+ * The virtual machine has been deleted. This is an irreversible state. Once a virtual machine
+ * is deleted all its secrets are permanently lost, and it cannot be run. A new virtual machine
+ * with the same name and config may be created, with new and different secrets.
+ */
+ public static final int STATUS_DELETED = 2;
/** Lock for internal synchronization. */
private final Object mLock = new Object();
@@ -166,8 +210,6 @@
@Nullable private ParcelFileDescriptor mLogReader;
@Nullable private ParcelFileDescriptor mLogWriter;
- private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
-
@NonNull private final Context mContext;
static {
@@ -179,8 +221,8 @@
throws VirtualMachineException {
mContext = context;
mPackageName = context.getPackageName();
- mName = name;
- mConfig = config;
+ mName = requireNonNull(name, "Name must not be null");
+ mConfig = requireNonNull(config, "Config must not be null");
mConfigFilePath = getConfigFilePath(context, name);
final File vmRoot = new File(context.getFilesDir(), VM_DIR);
@@ -193,15 +235,12 @@
/**
* Creates a virtual machine with the given name and config. Once a virtual machine is created
* it is persisted until it is deleted by calling {@link #delete()}. The created virtual machine
- * is in {@link Status#STOPPED} state. To run the VM, call {@link #run()}.
+ * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run()}.
*/
@NonNull
static VirtualMachine create(
@NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
- if (config == null) {
- throw new VirtualMachineException("null config");
- }
VirtualMachine vm = new VirtualMachine(context, name, config);
try {
@@ -332,29 +371,29 @@
*
* @hide
*/
- @NonNull
- public Status getStatus() throws VirtualMachineException {
+ @Status
+ public int getStatus() throws VirtualMachineException {
try {
if (mVirtualMachine != null) {
switch (mVirtualMachine.getState()) {
case VirtualMachineState.NOT_STARTED:
- return Status.STOPPED;
+ return STATUS_STOPPED;
case VirtualMachineState.STARTING:
case VirtualMachineState.STARTED:
case VirtualMachineState.READY:
case VirtualMachineState.FINISHED:
- return Status.RUNNING;
+ return STATUS_RUNNING;
case VirtualMachineState.DEAD:
- return Status.STOPPED;
+ return STATUS_STOPPED;
}
}
} catch (RemoteException e) {
throw new VirtualMachineException(e);
}
if (!mConfigFilePath.exists()) {
- return Status.DELETED;
+ return STATUS_DELETED;
}
- return Status.STOPPED;
+ return STATUS_STOPPED;
}
/**
@@ -363,8 +402,7 @@
*
* @hide
*/
- public void setCallback(
- @NonNull @CallbackExecutor Executor executor,
+ public void setCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull VirtualMachineCallback callback) {
synchronized (mLock) {
mCallback = callback;
@@ -406,12 +444,13 @@
/**
* Runs this virtual machine. The returning of this method however doesn't mean that the VM has
* actually started running or the OS has booted there. Such events can be notified by
- * registering a callback object (not implemented currently).
+ * registering a callback using {@link #setCallback(Executor, VirtualMachineCallback)}.
*
* @hide
*/
+ @RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION)
public void run() throws VirtualMachineException {
- if (getStatus() != Status.STOPPED) {
+ if (getStatus() != STATUS_STOPPED) {
throw new VirtualMachineException(this + " is not in stopped state");
}
@@ -472,8 +511,8 @@
IBinder.DeathRecipient deathRecipient = () -> {
if (onDiedCalled.compareAndSet(false, true)) {
- executeCallback((cb) -> cb.onDied(VirtualMachine.this,
- VirtualMachineCallback.DEATH_REASON_VIRTUALIZATIONSERVICE_DIED));
+ executeCallback((cb) -> cb.onStopped(VirtualMachine.this,
+ VirtualMachineCallback.STOP_REASON_VIRTUALIZATION_SERVICE_DIED));
}
};
@@ -485,28 +524,37 @@
executeCallback(
(cb) -> cb.onPayloadStarted(VirtualMachine.this, stream));
}
+
@Override
public void onPayloadReady(int cid) {
executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
}
+
@Override
public void onPayloadFinished(int cid, int exitCode) {
executeCallback(
(cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode));
}
+
@Override
public void onError(int cid, int errorCode, String message) {
+ int translatedError = getTranslatedError(errorCode);
executeCallback(
- (cb) -> cb.onError(VirtualMachine.this, errorCode, message));
+ (cb) -> cb.onError(VirtualMachine.this, translatedError,
+ message));
}
+
@Override
public void onDied(int cid, int reason) {
- // TODO(b/236811123) translate `reason` into a stable reason numbers
service.asBinder().unlinkToDeath(deathRecipient, 0);
+ int translatedReason = getTranslatedReason(reason);
if (onDiedCalled.compareAndSet(false, true)) {
- executeCallback((cb) -> cb.onDied(VirtualMachine.this, reason));
+ executeCallback(
+ (cb) -> cb.onStopped(VirtualMachine.this,
+ translatedReason));
}
}
+
@Override
public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
executeCallback(
@@ -521,6 +569,60 @@
}
}
+ @VirtualMachineCallback.ErrorCode
+ private int getTranslatedError(int reason) {
+ switch (reason) {
+ case ErrorCode.PAYLOAD_VERIFICATION_FAILED:
+ return ERROR_PAYLOAD_VERIFICATION_FAILED;
+ case ErrorCode.PAYLOAD_CHANGED:
+ return ERROR_PAYLOAD_CHANGED;
+ case ErrorCode.PAYLOAD_CONFIG_INVALID:
+ return ERROR_PAYLOAD_INVALID_CONFIG;
+ default:
+ return ERROR_UNKNOWN;
+ }
+ }
+
+ @VirtualMachineCallback.StopReason
+ private int getTranslatedReason(int reason) {
+ switch (reason) {
+ case DeathReason.INFRASTRUCTURE_ERROR:
+ return STOP_REASON_INFRASTRUCTURE_ERROR;
+ case DeathReason.KILLED:
+ return STOP_REASON_KILLED;
+ case DeathReason.SHUTDOWN:
+ return STOP_REASON_SHUTDOWN;
+ case DeathReason.ERROR:
+ return STOP_REASON_ERROR;
+ case DeathReason.REBOOT:
+ return STOP_REASON_REBOOT;
+ case DeathReason.CRASH:
+ return STOP_REASON_CRASH;
+ case DeathReason.PVM_FIRMWARE_PUBLIC_KEY_MISMATCH:
+ return STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH;
+ case DeathReason.PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED:
+ return STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED;
+ case DeathReason.BOOTLOADER_PUBLIC_KEY_MISMATCH:
+ return STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH;
+ case DeathReason.BOOTLOADER_INSTANCE_IMAGE_CHANGED:
+ return STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED;
+ case DeathReason.MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE:
+ return STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE;
+ case DeathReason.MICRODROID_PAYLOAD_HAS_CHANGED:
+ return STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED;
+ case DeathReason.MICRODROID_PAYLOAD_VERIFICATION_FAILED:
+ return STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED;
+ case DeathReason.MICRODROID_INVALID_PAYLOAD_CONFIG:
+ return STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG;
+ case DeathReason.MICRODROID_UNKNOWN_RUNTIME_ERROR:
+ return STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR;
+ case DeathReason.HANGUP:
+ return STOP_REASON_HANGUP;
+ default:
+ return STOP_REASON_UNKNOWN;
+ }
+ }
+
/**
* Returns the stream object representing the console output from the virtual machine.
*
@@ -550,7 +652,7 @@
/**
* Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
* computer; the machine halts immediately. Software running on the virtual machine is not
- * notified with the event. A stopped virtual machine can be re-started by calling {@link
+ * notified of the event. A stopped virtual machine can be re-started by calling {@link
* #run()}.
*
* @hide
@@ -566,15 +668,25 @@
}
/**
+ * Stops this virtual machine. See {@link #stop()}.
+ *
+ * @hide
+ */
+ @Override
+ public void close() throws VirtualMachineException {
+ stop();
+ }
+
+ /**
* Deletes this virtual machine. Deleting a virtual machine means deleting any persisted data
- * associated with it including the per-VM secret. This is an irreversable action. A virtual
+ * associated with it including the per-VM secret. This is an irreversible action. A virtual
* machine once deleted can never be restored. A new virtual machine created with the same name
* and the same config is different from an already deleted virtual machine.
*
* @hide
*/
public void delete() throws VirtualMachineException {
- if (getStatus() != Status.STOPPED) {
+ if (getStatus() != STATUS_STOPPED) {
throw new VirtualMachineException("Virtual machine is not stopped");
}
final File vmRootDir = mConfigFilePath.getParentFile();
@@ -599,7 +711,7 @@
*/
@NonNull
public Optional<Integer> getCid() throws VirtualMachineException {
- if (getStatus() != Status.RUNNING) {
+ if (getStatus() != STATUS_RUNNING) {
return Optional.empty();
}
try {
@@ -629,7 +741,7 @@
if (!oldConfig.isCompatibleWith(newConfig)) {
throw new VirtualMachineException("incompatible config");
}
- if (getStatus() != Status.STOPPED) {
+ if (getStatus() != STATUS_STOPPED) {
throw new VirtualMachineException(
"can't change config while virtual machine is not stopped");
}
@@ -649,19 +761,19 @@
private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
/**
- * Connects to a VM's RPC server via vsock, and returns a root IBinder object. Guest VMs are
+ * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
* expected to set up vsock servers in their payload. After the host app receives the {@link
* VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to
- * establish an RPC session to the guest VMs.
+ * establish a connection to the guest VM.
*
* @hide
*/
- public Future<IBinder> connectToVsockServer(int port) throws VirtualMachineException {
- if (getStatus() != Status.RUNNING) {
+ @NonNull
+ public IBinder connectToVsockServer(int port) throws VirtualMachineException {
+ if (getStatus() != STATUS_RUNNING) {
throw new VirtualMachineException("VM is not running");
}
- return mExecutorService.submit(
- () -> nativeConnectToVsockServer(mVirtualMachine.asBinder(), port));
+ return nativeConnectToVsockServer(mVirtualMachine.asBinder(), port);
}
/**
@@ -669,21 +781,40 @@
*
* @hide
*/
+ @NonNull
public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
try {
return mVirtualMachine.connectVsock(port);
} catch (RemoteException e) {
- throw new VirtualMachineException("failed to connect Vsock", e);
+ throw new VirtualMachineException("failed to connect vsock", e);
}
}
@Override
public String toString() {
- return "VirtualMachine("
- + "name:" + getName() + ", "
- + "config:" + getConfig().getPayloadConfigPath() + ", "
- + "package: " + mPackageName
- + ")";
+ VirtualMachineConfig config = getConfig();
+ String payloadConfigPath = config.getPayloadConfigPath();
+ String payloadBinaryPath = config.getPayloadBinaryPath();
+
+ StringBuilder result = new StringBuilder();
+ result.append("VirtualMachine(")
+ .append("name:")
+ .append(getName())
+ .append(", ");
+ if (payloadBinaryPath != null) {
+ result.append("payload:")
+ .append(payloadBinaryPath)
+ .append(", ");
+ }
+ if (payloadConfigPath != null) {
+ result.append("config:")
+ .append(payloadConfigPath)
+ .append(", ");
+ }
+ result.append("package: ")
+ .append(mPackageName)
+ .append(")");
+ return result.toString();
}
private static List<String> parseExtraApkListFromPayloadConfig(JsonReader reader)
@@ -729,11 +860,14 @@
private static List<ExtraApkSpec> setupExtraApks(
@NonNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir)
throws VirtualMachineException {
+ String configPath = config.getPayloadConfigPath();
+ if (configPath == null) {
+ return Collections.emptyList();
+ }
try {
ZipFile zipFile = new ZipFile(context.getPackageCodePath());
- String payloadPath = config.getPayloadConfigPath();
InputStream inputStream =
- zipFile.getInputStream(zipFile.getEntry(config.getPayloadConfigPath()));
+ zipFile.getInputStream(zipFile.getEntry(configPath));
List<String> apkList =
parseExtraApkListFromPayloadConfig(
new JsonReader(new InputStreamReader(inputStream)));
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index c802678..c89b8bb 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.os.ParcelFileDescriptor;
import java.lang.annotation.Retention;
@@ -30,6 +31,7 @@
*
* @hide
*/
+@SuppressLint("CallbackInterface") // Guidance has changed, lint is out of date (b/245552641)
public interface VirtualMachineCallback {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -43,90 +45,95 @@
/** Error code for all other errors not listed below. */
int ERROR_UNKNOWN = 0;
-
/**
* Error code indicating that the payload can't be verified due to various reasons (e.g invalid
* merkle tree, invalid formats, etc).
*/
int ERROR_PAYLOAD_VERIFICATION_FAILED = 1;
-
/** Error code indicating that the payload is verified, but has changed since the last boot. */
int ERROR_PAYLOAD_CHANGED = 2;
-
/** Error code indicating that the payload config is invalid. */
int ERROR_PAYLOAD_INVALID_CONFIG = 3;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
- DEATH_REASON_VIRTUALIZATIONSERVICE_DIED,
- DEATH_REASON_INFRASTRUCTURE_ERROR,
- DEATH_REASON_KILLED,
- DEATH_REASON_UNKNOWN,
- DEATH_REASON_SHUTDOWN,
- DEATH_REASON_ERROR,
- DEATH_REASON_REBOOT,
- DEATH_REASON_CRASH,
- DEATH_REASON_HANGUP,
+ STOP_REASON_VIRTUALIZATION_SERVICE_DIED,
+ STOP_REASON_INFRASTRUCTURE_ERROR,
+ STOP_REASON_KILLED,
+ STOP_REASON_UNKNOWN,
+ STOP_REASON_SHUTDOWN,
+ STOP_REASON_ERROR,
+ STOP_REASON_REBOOT,
+ STOP_REASON_CRASH,
+ STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH,
+ STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED,
+ STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH,
+ STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED,
+ STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE,
+ STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED,
+ STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED,
+ STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG,
+ STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR,
+ STOP_REASON_HANGUP,
})
- @interface DeathReason {}
+ @interface StopReason {}
- /**
- * virtualizationservice itself died, taking the VM down with it. This is a negative number to
- * avoid conflicting with the other death reasons which match the ones in the AIDL interface.
- */
- int DEATH_REASON_VIRTUALIZATIONSERVICE_DIED = -1;
+ /** The virtualization service itself died, taking the VM down with it. */
+ // This is a negative number to avoid conflicting with the other death reasons which match
+ // the ones in the AIDL interface.
+ int STOP_REASON_VIRTUALIZATION_SERVICE_DIED = -1;
/** There was an error waiting for the VM. */
- int DEATH_REASON_INFRASTRUCTURE_ERROR = 0;
+ int STOP_REASON_INFRASTRUCTURE_ERROR = 0;
/** The VM was killed. */
- int DEATH_REASON_KILLED = 1;
+ int STOP_REASON_KILLED = 1;
/** The VM died for an unknown reason. */
- int DEATH_REASON_UNKNOWN = 2;
+ int STOP_REASON_UNKNOWN = 2;
/** The VM requested to shut down. */
- int DEATH_REASON_SHUTDOWN = 3;
+ int STOP_REASON_SHUTDOWN = 3;
/** crosvm had an error starting the VM. */
- int DEATH_REASON_ERROR = 4;
+ int STOP_REASON_ERROR = 4;
/** The VM requested to reboot, possibly as the result of a kernel panic. */
- int DEATH_REASON_REBOOT = 5;
+ int STOP_REASON_REBOOT = 5;
/** The VM or crosvm crashed. */
- int DEATH_REASON_CRASH = 6;
+ int STOP_REASON_CRASH = 6;
/** The pVM firmware failed to verify the VM because the public key doesn't match. */
- int DEATH_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 7;
+ int STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 7;
/** The pVM firmware failed to verify the VM because the instance image changed. */
- int DEATH_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 8;
+ int STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 8;
/** The bootloader failed to verify the VM because the public key doesn't match. */
- int DEATH_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH = 9;
+ int STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH = 9;
/** The bootloader failed to verify the VM because the instance image changed. */
- int DEATH_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED = 10;
+ int STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED = 10;
/** The microdroid failed to connect to VirtualizationService's RPC server. */
- int DEATH_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE = 11;
+ int STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE = 11;
/** The payload for microdroid is changed. */
- int DEATH_REASON_MICRODROID_PAYLOAD_HAS_CHANGED = 12;
+ int STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED = 12;
/** The microdroid failed to verify given payload APK. */
- int DEATH_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED = 13;
+ int STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED = 13;
/** The VM config for microdroid is invalid (e.g. missing tasks). */
- int DEATH_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG = 14;
+ int STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG = 14;
/** There was a runtime error while running microdroid manager. */
- int DEATH_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR = 15;
+ int STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR = 15;
/** The VM killed due to hangup */
- int DEATH_REASON_HANGUP = 16;
+ int STOP_REASON_HANGUP = 16;
/**
* Called when the payload starts in the VM. The stream, if non-null, provides access
@@ -136,7 +143,7 @@
/**
* Called when the payload in the VM is ready to serve. See
- * {@link VirtualMachine#connectToVsockServer(int)} ()}.
+ * {@link VirtualMachine#connectToVsockServer(int)}.
*/
void onPayloadReady(@NonNull VirtualMachine vm);
@@ -146,8 +153,8 @@
/** Called when an error occurs in the VM. */
void onError(@NonNull VirtualMachine vm, @ErrorCode int errorCode, @NonNull String message);
- /** Called when the VM has ended. */
- void onDied(@NonNull VirtualMachine vm, @DeathReason int reason);
+ /** Called when the VM has stopped. */
+ void onStopped(@NonNull VirtualMachine vm, @StopReason int reason);
/** Called when kernel panic occurs and as a result ramdump is generated from the VM. */
void onRamdump(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor ramdump);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 63a3f43..3061f65 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -18,24 +18,23 @@
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.content.pm.Signature; // This actually is certificate!
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.sysprop.HypervisorProperties;
import android.system.virtualizationservice.VirtualMachineAppConfig;
+import android.system.virtualizationservice.VirtualMachinePayloadConfig;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -47,11 +46,11 @@
*/
public final class VirtualMachineConfig {
// These defines the schema of the config file persisted on disk.
- private static final int VERSION = 1;
+ private static final int VERSION = 2;
private static final String KEY_VERSION = "version";
- private static final String KEY_CERTS = "certs";
private static final String KEY_APKPATH = "apkPath";
private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath";
+ private static final String KEY_PAYLOADBINARYPATH = "payloadBinaryPath";
private static final String KEY_DEBUGLEVEL = "debugLevel";
private static final String KEY_PROTECTED_VM = "protectedVm";
private static final String KEY_MEMORY_MIB = "memoryMib";
@@ -59,34 +58,41 @@
// Paths to the APK file of this application.
@NonNull private final String mApkPath;
- @NonNull private final Signature[] mCerts;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DEBUG_LEVEL_NONE,
+ DEBUG_LEVEL_APP_ONLY,
+ DEBUG_LEVEL_FULL
+ })
+ public @interface DebugLevel {}
/**
- * A debug level defines the set of debug features that the VM can be configured to.
+ * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the
+ * app process running in the VM. This is the default level.
*
* @hide
*/
- public enum DebugLevel {
- /**
- * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the
- * app process running in the VM. This is the default level.
- */
- NONE,
+ public static final int DEBUG_LEVEL_NONE = 0;
- /**
- * Only the app is debuggable. Log from the app is exported from the VM. Debugger can be
- * attached to the app process. Rest of the VM is not debuggable.
- */
- APP_ONLY,
+ /**
+ * Only the app is debuggable. Log from the app is exported from the VM. Debugger can be
+ * attached to the app process. Rest of the VM is not debuggable.
+ *
+ * @hide
+ */
+ public static final int DEBUG_LEVEL_APP_ONLY = 1;
- /**
- * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes
- * running in the VM can be attached to the debugger. Rooting is possible.
- */
- FULL,
- }
+ /**
+ * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes
+ * running in the VM can be attached to the debugger. Rooting is possible.
+ *
+ * @hide
+ */
+ public static final int DEBUG_LEVEL_FULL = 2;
- private final DebugLevel mDebugLevel;
+ @DebugLevel private final int mDebugLevel;
/**
* Whether to run the VM in protected mode, so the host can't access its memory.
@@ -104,21 +110,26 @@
private final int mNumCpus;
/**
- * Path within the APK to the payload config file that defines software aspects of this config.
+ * Path within the APK to the payload config file that defines software aspects of the VM.
*/
- @NonNull private final String mPayloadConfigPath;
+ @Nullable private final String mPayloadConfigPath;
+
+ /**
+ * Path within the APK to the payload binary file that will be executed within the VM.
+ */
+ @Nullable private final String mPayloadBinaryPath;
private VirtualMachineConfig(
@NonNull String apkPath,
- @NonNull Signature[] certs,
- @NonNull String payloadConfigPath,
- DebugLevel debugLevel,
+ @Nullable String payloadConfigPath,
+ @Nullable String payloadBinaryPath,
+ @DebugLevel int debugLevel,
boolean protectedVm,
int memoryMib,
int numCpus) {
- mApkPath = apkPath;
- mCerts = certs;
+ mApkPath = Objects.requireNonNull(apkPath);
mPayloadConfigPath = payloadConfigPath;
+ mPayloadBinaryPath = payloadBinaryPath;
mDebugLevel = debugLevel;
mProtectedVm = protectedVm;
mMemoryMib = memoryMib;
@@ -130,33 +141,33 @@
static VirtualMachineConfig from(@NonNull InputStream input)
throws IOException, VirtualMachineException {
PersistableBundle b = PersistableBundle.readFromStream(input);
- final int version = b.getInt(KEY_VERSION);
+ int version = b.getInt(KEY_VERSION);
if (version > VERSION) {
throw new VirtualMachineException("Version too high");
}
- final String apkPath = b.getString(KEY_APKPATH);
+ String apkPath = b.getString(KEY_APKPATH);
if (apkPath == null) {
throw new VirtualMachineException("No apkPath");
}
- final String[] certStrings = b.getStringArray(KEY_CERTS);
- if (certStrings == null || certStrings.length == 0) {
- throw new VirtualMachineException("No certs");
+ String payloadBinaryPath = b.getString(KEY_PAYLOADBINARYPATH);
+ String payloadConfigPath = null;
+ if (payloadBinaryPath == null) {
+ payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
+ if (payloadConfigPath == null) {
+ throw new VirtualMachineException("No payloadBinaryPath");
+ }
}
- List<Signature> certList = new ArrayList<>();
- for (String s : certStrings) {
- certList.add(new Signature(s));
+ @DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL);
+ if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_APP_ONLY
+ && debugLevel != DEBUG_LEVEL_FULL) {
+ throw new VirtualMachineException("Invalid debugLevel: " + debugLevel);
}
- Signature[] certs = certList.toArray(new Signature[0]);
- final String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
- if (payloadConfigPath == null) {
- throw new VirtualMachineException("No payloadConfigPath");
- }
- final DebugLevel debugLevel = DebugLevel.values()[b.getInt(KEY_DEBUGLEVEL)];
- final boolean protectedVm = b.getBoolean(KEY_PROTECTED_VM);
- final int memoryMib = b.getInt(KEY_MEMORY_MIB);
- final int numCpus = b.getInt(KEY_NUM_CPUS);
- return new VirtualMachineConfig(apkPath, certs, payloadConfigPath, debugLevel, protectedVm,
- memoryMib, numCpus);
+ boolean protectedVm = b.getBoolean(KEY_PROTECTED_VM);
+ int memoryMib = b.getInt(KEY_MEMORY_MIB);
+ int numCpus = b.getInt(KEY_NUM_CPUS);
+
+ return new VirtualMachineConfig(apkPath, payloadConfigPath, payloadBinaryPath, debugLevel,
+ protectedVm, memoryMib, numCpus);
}
/** Persists this config to a stream, for example a file. */
@@ -164,14 +175,9 @@
PersistableBundle b = new PersistableBundle();
b.putInt(KEY_VERSION, VERSION);
b.putString(KEY_APKPATH, mApkPath);
- List<String> certList = new ArrayList<>();
- for (Signature cert : mCerts) {
- certList.add(cert.toCharsString());
- }
- String[] certs = certList.toArray(new String[0]);
- b.putStringArray(KEY_CERTS, certs);
b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
- b.putInt(KEY_DEBUGLEVEL, mDebugLevel.ordinal());
+ b.putString(KEY_PAYLOADBINARYPATH, mPayloadBinaryPath);
+ b.putInt(KEY_DEBUGLEVEL, mDebugLevel);
b.putBoolean(KEY_PROTECTED_VM, mProtectedVm);
b.putInt(KEY_NUM_CPUS, mNumCpus);
if (mMemoryMib > 0) {
@@ -185,32 +191,74 @@
*
* @hide
*/
- @NonNull
+ @Nullable
public String getPayloadConfigPath() {
return mPayloadConfigPath;
}
/**
+ * Returns the path within the APK to the payload binary file that will be executed within the
+ * VM.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getPayloadBinaryPath() {
+ return mPayloadBinaryPath;
+ }
+
+ /**
+ * Returns the debug level for the VM.
+ *
+ * @hide
+ */
+ @NonNull
+ @DebugLevel
+ public int getDebugLevel() {
+ return mDebugLevel;
+ }
+
+ /**
+ * Returns whether the VM's memory will be protected from the host.
+ *
+ * @hide
+ */
+ public boolean isProtectedVm() {
+ return mProtectedVm;
+ }
+
+ /**
+ * Returns the amount of RAM that will be made available to the VM.
+ *
+ * @hide
+ */
+ public int getMemoryMib() {
+ return mMemoryMib;
+ }
+
+ /**
+ * Returns the number of vCPUs that the VM will have.
+ *
+ * @hide
+ */
+ public int getNumCpus() {
+ return mNumCpus;
+ }
+
+ /**
* Tests if this config is compatible with other config. Being compatible means that the configs
* can be interchangeably used for the same virtual machine. Compatible changes includes the
- * number of CPUs and the size of the RAM, and change of the payload as long as the payload is
- * signed by the same signer. All other changes (e.g. using a payload from a different signer,
+ * number of CPUs and the size of the RAM. All other changes (e.g. using a different payload,
* change of the debug mode, etc.) are considered as incompatible.
*
* @hide
*/
public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
- if (!Arrays.equals(this.mCerts, other.mCerts)) {
- return false;
- }
- if (this.mDebugLevel != other.mDebugLevel) {
- // TODO(jiyong): should we treat APP_ONLY and FULL the same?
- return false;
- }
- if (this.mProtectedVm != other.mProtectedVm) {
- return false;
- }
- return true;
+ return this.mDebugLevel == other.mDebugLevel
+ && this.mProtectedVm == other.mProtectedVm
+ && Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
+ && Objects.equals(this.mPayloadBinaryPath, other.mPayloadBinaryPath)
+ && this.mApkPath.equals(other.mApkPath);
}
/**
@@ -219,20 +267,29 @@
* service doesn't accept paths as it might not have permission to open app-owned files and that
* could be abused to run a VM with software that the calling application doesn't own.
*/
- /* package */ VirtualMachineAppConfig toParcel() throws FileNotFoundException {
+ VirtualMachineAppConfig toParcel() throws FileNotFoundException {
VirtualMachineAppConfig parcel = new VirtualMachineAppConfig();
parcel.apk = ParcelFileDescriptor.open(new File(mApkPath), MODE_READ_ONLY);
- parcel.payload = VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath);
+ if (mPayloadBinaryPath != null) {
+ VirtualMachinePayloadConfig payloadConfig = new VirtualMachinePayloadConfig();
+ payloadConfig.payloadPath = mPayloadBinaryPath;
+ payloadConfig.args = new String[]{};
+ parcel.payload =
+ VirtualMachineAppConfig.Payload.payloadConfig(payloadConfig);
+ } else {
+ parcel.payload =
+ VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath);
+ }
switch (mDebugLevel) {
- case NONE:
- parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE;
- break;
- case APP_ONLY:
+ case DEBUG_LEVEL_APP_ONLY:
parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.APP_ONLY;
break;
- case FULL:
+ case DEBUG_LEVEL_FULL:
parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.FULL;
break;
+ default:
+ parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE;
+ break;
}
parcel.protectedVm = mProtectedVm;
parcel.memoryMib = mMemoryMib;
@@ -248,69 +305,28 @@
*
* @hide
*/
- public static class Builder {
+ public static final class Builder {
private final Context mContext;
- private final String mPayloadConfigPath;
- private DebugLevel mDebugLevel;
+ @Nullable private String mPayloadConfigPath;
+ @Nullable private String mPayloadBinaryPath;
+ @DebugLevel private int mDebugLevel;
private boolean mProtectedVm;
+ private boolean mProtectedVmSet;
private int mMemoryMib;
private int mNumCpus;
/**
- * Creates a builder for the given context (APK), and the payload config file in APK.
+ * Creates a builder for the given context (APK).
*
* @hide
*/
- public Builder(@NonNull Context context, @NonNull String payloadConfigPath) {
+ public Builder(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
- mPayloadConfigPath = Objects.requireNonNull(payloadConfigPath);
- mDebugLevel = DebugLevel.NONE;
- mProtectedVm = false;
+ mDebugLevel = DEBUG_LEVEL_NONE;
mNumCpus = 1;
}
/**
- * Sets the debug level
- *
- * @hide
- */
- public Builder debugLevel(DebugLevel debugLevel) {
- mDebugLevel = debugLevel;
- return this;
- }
-
- /**
- * Sets whether to protect the VM memory from the host. Defaults to false.
- *
- * @hide
- */
- public Builder protectedVm(boolean protectedVm) {
- mProtectedVm = protectedVm;
- return this;
- }
-
- /**
- * Sets the amount of RAM to give the VM. If this is zero or negative then the default will
- * be used.
- *
- * @hide
- */
- public Builder memoryMib(int memoryMib) {
- mMemoryMib = memoryMib;
- return this;
- }
-
- /**
- * Sets the number of vCPUs in the VM. Defaults to 1.
- *
- * @hide
- */
- public Builder numCpus(int num) {
- mNumCpus = num;
- return this;
- }
-
- /**
* Builds an immutable {@link VirtualMachineConfig}
*
* @hide
@@ -318,18 +334,6 @@
@NonNull
public VirtualMachineConfig build() {
final String apkPath = mContext.getPackageCodePath();
- final String packageName = mContext.getPackageName();
- Signature[] certs;
- try {
- certs = mContext.getPackageManager()
- .getPackageInfo(packageName,
- PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES))
- .signingInfo
- .getSigningCertificateHistory();
- } catch (PackageManager.NameNotFoundException e) {
- // This cannot happen as `packageName` is from this app.
- throw new RuntimeException(e);
- }
final int availableCpus = Runtime.getRuntime().availableProcessors();
if (mNumCpus < 1 || mNumCpus > availableCpus) {
@@ -337,6 +341,21 @@
+ "range [1, " + availableCpus + "]");
}
+ if (mPayloadBinaryPath == null) {
+ if (mPayloadConfigPath == null) {
+ throw new IllegalStateException("payloadBinaryPath must be set");
+ }
+ } else {
+ if (mPayloadConfigPath != null) {
+ throw new IllegalStateException(
+ "payloadBinaryPath and payloadConfigPath may not both be set");
+ }
+ }
+
+ if (!mProtectedVmSet) {
+ throw new IllegalStateException("protectedVm must be set explicitly");
+ }
+
if (mProtectedVm
&& !HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
throw new UnsupportedOperationException(
@@ -348,8 +367,79 @@
}
return new VirtualMachineConfig(
- apkPath, certs, mPayloadConfigPath, mDebugLevel, mProtectedVm, mMemoryMib,
- mNumCpus);
+ apkPath, mPayloadConfigPath, mPayloadBinaryPath, mDebugLevel, mProtectedVm,
+ mMemoryMib, mNumCpus);
+ }
+
+ /**
+ * Sets the path within the APK to the payload config file that defines software aspects
+ * of the VM.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) {
+ mPayloadConfigPath = Objects.requireNonNull(payloadConfigPath);
+ return this;
+ }
+
+ /**
+ * Sets the path within the APK to the payload binary file that will be executed within
+ * the VM.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setPayloadBinaryPath(@NonNull String payloadBinaryPath) {
+ mPayloadBinaryPath = Objects.requireNonNull(payloadBinaryPath);
+ return this;
+ }
+
+ /**
+ * Sets the debug level
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setDebugLevel(@DebugLevel int debugLevel) {
+ mDebugLevel = debugLevel;
+ return this;
+ }
+
+ /**
+ * Sets whether to protect the VM memory from the host. No default is provided, this
+ * must be set explicitly.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setProtectedVm(boolean protectedVm) {
+ mProtectedVm = protectedVm;
+ mProtectedVmSet = true;
+ return this;
+ }
+
+ /**
+ * Sets the amount of RAM to give the VM. If this is zero or negative then the default will
+ * be used.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setMemoryMib(int memoryMib) {
+ mMemoryMib = memoryMib;
+ return this;
+ }
+
+ /**
+ * Sets the number of vCPUs in the VM. Defaults to 1.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setNumCpus(int num) {
+ mNumCpus = num;
+ return this;
}
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineException.java b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
index d6aeab3..88b5ea3 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineException.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
@@ -16,21 +16,27 @@
package android.system.virtualmachine;
-/** @hide */
+import android.annotation.Nullable;
+
+/**
+ * Exception thrown when operations on virtual machines fail.
+ *
+ * @hide
+ */
public class VirtualMachineException extends Exception {
public VirtualMachineException() {
super();
}
- public VirtualMachineException(String message) {
+ public VirtualMachineException(@Nullable String message) {
super(message);
}
- public VirtualMachineException(String message, Throwable cause) {
+ public VirtualMachineException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
- public VirtualMachineException(Throwable cause) {
+ public VirtualMachineException(@Nullable Throwable cause) {
super(cause);
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 1ffc6bb..ad5864e 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -16,13 +16,16 @@
package android.system.virtualmachine;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.content.Context;
import java.lang.ref.WeakReference;
import java.util.Map;
-import java.util.Objects;
import java.util.WeakHashMap;
/**
@@ -46,8 +49,9 @@
* @hide
*/
@NonNull
+ @SuppressLint("ManagerLookup") // Optional API
public static VirtualMachineManager getInstance(@NonNull Context context) {
- Objects.requireNonNull(context);
+ requireNonNull(context, "context must not be null");
synchronized (sInstances) {
VirtualMachineManager vmm =
sInstances.containsKey(context) ? sInstances.get(context).get() : null;
@@ -65,13 +69,17 @@
/**
* Creates a new {@link VirtualMachine} with the given name and config. Creating a virtual
* machine with the same name as an existing virtual machine is an error. The existing virtual
- * machine has to be deleted before its name can be reused. Every call to this methods creates a
- * new (and different) virtual machine even if the name and the config are the same as the
- * deleted one.
+ * machine has to be deleted before its name can be reused.
*
+ * Each successful call to this method creates a new (and different) virtual machine even if the
+ * name and the config are the same as a deleted one. The new virtual machine will initially
+ * be stopped.
+ *
+ * @throws VirtualMachineException If there is an existing virtual machine with the given name
* @hide
*/
@NonNull
+ @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
public VirtualMachine create(
@NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 10e5858..81f94bc 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -70,7 +70,6 @@
"apexd",
"atrace",
"debuggerd",
- "dice-service.microdroid",
"linker",
"linkerconfig",
"servicemanager.microdroid",
@@ -80,7 +79,6 @@
"task_profiles.json",
"public.libraries.android.txt",
- "microdroid_compatibility_matrix",
"microdroid_event-log-tags",
"microdroid_file_contexts",
"microdroid_manifest",
@@ -533,14 +531,6 @@
}
prebuilt_etc {
- name: "microdroid_compatibility_matrix",
- src: "microdroid_compatibility_matrix.xml",
- filename: "compatibility_matrix.current.xml",
- relative_install_path: "vintf",
- installable: false,
-}
-
-prebuilt_etc {
name: "microdroid_manifest",
src: "microdroid_manifest.xml",
filename: "manifest.xml",
diff --git a/microdroid/dice/Android.bp b/microdroid/dice/Android.bp
deleted file mode 100644
index 859533e..0000000
--- a/microdroid/dice/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_binary {
- name: "dice-service.microdroid",
- srcs: ["service.rs"],
- prefer_rlib: true,
- rustlibs: [
- "android.hardware.security.dice-V1-rust",
- "android.security.dice-rust",
- "libandroid_logger",
- "libanyhow",
- "libbinder_rs",
- "libbyteorder",
- "libdiced",
- "libdiced_open_dice_cbor",
- "libdiced_sample_inputs",
- "libdiced_utils",
- "liblibc",
- "liblog_rust",
- "libserde",
- ],
- init_rc: ["dice-service.microdroid.rc"],
- bootstrap: true,
-}
diff --git a/microdroid/dice/dice-service.microdroid.rc b/microdroid/dice/dice-service.microdroid.rc
deleted file mode 100644
index 5bcb049..0000000
--- a/microdroid/dice/dice-service.microdroid.rc
+++ /dev/null
@@ -1,3 +0,0 @@
-service dice-microdroid /system/bin/dice-service.microdroid
- user diced
- group diced
diff --git a/microdroid/dice/service.rs b/microdroid/dice/service.rs
deleted file mode 100644
index 2c19481..0000000
--- a/microdroid/dice/service.rs
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright 2022, 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 entry point for the microdroid DICE service implementation.
-
-use android_hardware_security_dice::aidl::android::hardware::security::dice::{
- Bcc::Bcc, BccHandover::BccHandover, InputValues::InputValues as BinderInputValues,
- Signature::Signature,
-};
-use anyhow::{bail, ensure, Context, Error, Result};
-use byteorder::{NativeEndian, ReadBytesExt};
-use dice::{ContextImpl, OpenDiceCborContext};
-use diced::{dice, DiceMaintenance, DiceNode, DiceNodeImpl};
-use diced_utils::make_bcc_handover;
-use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
-use serde::{Deserialize, Serialize};
-use std::fs;
-use std::os::unix::io::AsRawFd;
-use std::panic;
-use std::path::{Path, PathBuf};
-use std::ptr::null_mut;
-use std::slice;
-use std::sync::{Arc, RwLock};
-
-const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
-const DICE_NODE_SERVICE_NAME: &str = "android.security.dice.IDiceNode";
-const DICE_MAINTENANCE_SERVICE_NAME: &str = "android.security.dice.IDiceMaintenance";
-
-/// Artifacts that are mapped into the process address space from the driver.
-struct MappedDriverArtifacts<'a> {
- mmap_addr: *mut c_void,
- mmap_size: usize,
- cdi_attest: &'a [u8; dice::CDI_SIZE],
- cdi_seal: &'a [u8; dice::CDI_SIZE],
- bcc: &'a [u8],
-}
-
-impl MappedDriverArtifacts<'_> {
- fn new(driver_path: &Path) -> Result<Self> {
- let mut file = fs::File::open(driver_path)
- .map_err(|error| Error::new(error).context("Opening driver"))?;
- let mmap_size =
- file.read_u64::<NativeEndian>()
- .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
- // It's safe to map the driver as the service will only create a single
- // mapping per process.
- let mmap_addr = unsafe {
- let fd = file.as_raw_fd();
- mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
- };
- if mmap_addr == MAP_FAILED {
- bail!("Failed to mmap {:?}", driver_path);
- }
- // The slice is created for the region of memory that was just
- // successfully mapped into the process address space so it will be
- // accessible and not referenced from anywhere else.
- let mmap_buf =
- unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
- // Very inflexible parsing / validation of the BccHandover data. Assumes deterministically
- // encoded CBOR.
- //
- // BccHandover = {
- // 1 : bstr .size 32, ; CDI_Attest
- // 2 : bstr .size 32, ; CDI_Seal
- // 3 : Bcc, ; Certificate chain
- // }
- if mmap_buf[0..4] != [0xa3, 0x01, 0x58, 0x20]
- || mmap_buf[36..39] != [0x02, 0x58, 0x20]
- || mmap_buf[71] != 0x03
- {
- bail!("BccHandover format mismatch");
- }
- Ok(Self {
- mmap_addr,
- mmap_size,
- cdi_attest: mmap_buf[4..36].try_into().unwrap(),
- cdi_seal: mmap_buf[39..71].try_into().unwrap(),
- bcc: &mmap_buf[72..],
- })
- }
-}
-
-impl Drop for MappedDriverArtifacts<'_> {
- fn drop(&mut self) {
- // All references to the mapped region have the same lifetime as self.
- // Since self is being dropped, so are all the references to the mapped
- // region meaning its safe to unmap.
- let ret = unsafe { munmap(self.mmap_addr, self.mmap_size) };
- if ret != 0 {
- log::warn!("Failed to munmap ({})", ret);
- }
- }
-}
-
-/// Artifacts that are kept in the process address space after the artifacts
-/// from the driver have been consumed.
-#[derive(Clone, Serialize, Deserialize)]
-struct RawArtifacts {
- cdi_attest: [u8; dice::CDI_SIZE],
- cdi_seal: [u8; dice::CDI_SIZE],
- bcc: Vec<u8>,
-}
-
-#[derive(Clone, Serialize, Deserialize)]
-enum UpdatableArtifacts {
- Invalid,
- Driver(PathBuf),
- Updated(RawArtifacts),
-}
-
-impl UpdatableArtifacts {
- fn get(
- &self,
- input_values: &BinderInputValues,
- ) -> Result<(dice::CdiAttest, dice::CdiSeal, Vec<u8>)> {
- let input_values: diced_utils::InputValues = input_values.into();
- match self {
- Self::Invalid => bail!("No DICE artifacts available."),
- Self::Driver(driver_path) => {
- let artifacts = MappedDriverArtifacts::new(driver_path.as_path())?;
- dice::OpenDiceCborContext::new().bcc_main_flow(
- artifacts.cdi_attest,
- artifacts.cdi_seal,
- artifacts.bcc,
- &input_values,
- )
- }
- Self::Updated(artifacts) => dice::OpenDiceCborContext::new().bcc_main_flow(
- &artifacts.cdi_attest,
- &artifacts.cdi_seal,
- &artifacts.bcc,
- &input_values,
- ),
- }
- .context("Deriving artifacts")
- }
-
- fn update(self, inputs: &BinderInputValues) -> Result<Self> {
- if let Self::Invalid = self {
- bail!("Cannot update invalid DICE artifacts.");
- }
- let (cdi_attest, cdi_seal, bcc) =
- self.get(inputs).context("Failed to get update artifacts.")?;
- if let Self::Driver(ref driver_path) = self {
- // Writing to the device wipes the artifacts. The string is ignored
- // by the driver but included for documentation.
- fs::write(driver_path, "wipe")
- .map_err(|error| Error::new(error).context("Wiping driver"))?;
- }
- Ok(Self::Updated(RawArtifacts {
- cdi_attest: cdi_attest[..].try_into().unwrap(),
- cdi_seal: cdi_seal[..].try_into().unwrap(),
- bcc,
- }))
- }
-}
-
-struct ArtifactManager {
- artifacts: RwLock<UpdatableArtifacts>,
-}
-
-impl ArtifactManager {
- fn new(driver_path: &Path) -> Self {
- Self {
- artifacts: RwLock::new(if driver_path.exists() {
- log::info!("Using DICE values from driver");
- UpdatableArtifacts::Driver(driver_path.to_path_buf())
- } else if Path::new(AVF_STRICT_BOOT).exists() {
- log::error!("Strict boot requires DICE value from driver but none were found");
- UpdatableArtifacts::Invalid
- } else {
- log::warn!("Using sample DICE values");
- let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
- .expect("Failed to create sample dice artifacts.");
- UpdatableArtifacts::Updated(RawArtifacts {
- cdi_attest: cdi_attest[..].try_into().unwrap(),
- cdi_seal: cdi_seal[..].try_into().unwrap(),
- bcc,
- })
- }),
- }
- }
-}
-
-impl DiceNodeImpl for ArtifactManager {
- fn sign(
- &self,
- client: BinderInputValues,
- input_values: &[BinderInputValues],
- message: &[u8],
- ) -> Result<Signature> {
- ensure!(input_values.is_empty(), "Extra input values not supported");
- let artifacts = self.artifacts.read().unwrap().clone();
- let (cdi_attest, _, _) =
- artifacts.get(&client).context("Failed to get signing artifacts.")?;
- let mut dice = OpenDiceCborContext::new();
- let seed = dice
- .derive_cdi_private_key_seed(
- cdi_attest[..].try_into().context("Failed to convert cdi_attest.")?,
- )
- .context("Failed to derive seed from cdi_attest.")?;
- let (_public_key, private_key) = dice
- .keypair_from_seed(seed[..].try_into().context("Failed to convert seed.")?)
- .context("Failed to derive keypair from seed.")?;
- let signature = dice
- .sign(message, private_key[..].try_into().context("Failed to convert private_key.")?)
- .context("Failed to sign.")?;
- Ok(Signature { data: signature })
- }
-
- fn get_attestation_chain(
- &self,
- client: BinderInputValues,
- input_values: &[BinderInputValues],
- ) -> Result<Bcc> {
- ensure!(input_values.is_empty(), "Extra input values not supported");
- let artifacts = self.artifacts.read().unwrap().clone();
- let (_, _, bcc) =
- artifacts.get(&client).context("Failed to get attestation chain artifacts.")?;
- Ok(Bcc { data: bcc })
- }
-
- fn derive(
- &self,
- client: BinderInputValues,
- input_values: &[BinderInputValues],
- ) -> Result<BccHandover> {
- ensure!(input_values.is_empty(), "Extra input values not supported");
- let artifacts = self.artifacts.read().unwrap().clone();
- let (cdi_attest, cdi_seal, bcc) =
- artifacts.get(&client).context("Failed to get attestation chain artifacts.")?;
- make_bcc_handover(
- &cdi_attest
- .to_vec()
- .as_slice()
- .try_into()
- .context("Trying to convert cdi_attest to sized array.")?,
- &cdi_seal
- .to_vec()
- .as_slice()
- .try_into()
- .context("Trying to convert cdi_seal to sized array.")?,
- &bcc,
- )
- .context("Trying to construct BccHandover.")
- }
-
- fn demote(
- &self,
- _client: BinderInputValues,
- _input_values: &[BinderInputValues],
- ) -> Result<()> {
- bail!("Demote not supported.");
- }
-
- fn demote_self(&self, input_values: &[BinderInputValues]) -> Result<()> {
- ensure!(input_values.len() == 1, "Can only demote_self one level.");
- let mut artifacts = self.artifacts.write().unwrap();
- *artifacts = (*artifacts).clone().update(&input_values[0])?;
- Ok(())
- }
-}
-
-fn main() {
- android_logger::init_once(
- android_logger::Config::default().with_tag("dice").with_min_level(log::Level::Debug),
- );
- // Redirect panic messages to logcat.
- panic::set_hook(Box::new(|panic_info| {
- log::error!("{}", panic_info);
- }));
-
- // Saying hi.
- log::info!("DICE service is starting.");
-
- let node_impl = Arc::new(ArtifactManager::new(Path::new("/dev/open-dice0")));
-
- let node = DiceNode::new_as_binder(node_impl.clone())
- .expect("Failed to create IDiceNode service instance.");
-
- let maintenance = DiceMaintenance::new_as_binder(node_impl)
- .expect("Failed to create IDiceMaintenance service instance.");
-
- binder::add_service(DICE_NODE_SERVICE_NAME, node.as_binder())
- .expect("Failed to register IDiceNode Service");
-
- binder::add_service(DICE_MAINTENANCE_SERVICE_NAME, maintenance.as_binder())
- .expect("Failed to register IDiceMaintenance Service");
-
- log::info!("Joining thread pool now.");
- binder::ProcessState::join_thread_pool();
-}
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 8b2bbdb..2b4295f 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -67,8 +67,6 @@
start servicemanager
- start dice-microdroid
-
on init
mkdir /mnt/apk 0755 system system
mkdir /mnt/extra-apk 0755 root root
diff --git a/microdroid/microdroid_compatibility_matrix.xml b/microdroid/microdroid_compatibility_matrix.xml
deleted file mode 100644
index a345e30..0000000
--- a/microdroid/microdroid_compatibility_matrix.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<compatibility-matrix version="1.0" type="framework">
- <hal format="aidl" optional="true">
- <name>android.hardware.security.dice</name>
- <version>1</version>
- <interface>
- <name>IDiceDevice</name>
- <instance>default</instance>
- </interface>
- </hal>
-</compatibility-matrix>
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index fc165c8..268d3a2 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -28,4 +28,4 @@
# Virtual console for logcat
/dev/hvc2 0666 system system
-/dev/open-dice0 0660 diced diced
+/dev/open-dice0 0660 root root
diff --git a/microdroid/vm_payload/Android.bp b/microdroid/vm_payload/Android.bp
index 0424fc1..a68595f 100644
--- a/microdroid/vm_payload/Android.bp
+++ b/microdroid/vm_payload/Android.bp
@@ -2,18 +2,32 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-rust_ffi_static {
+rust_ffi_shared {
name: "libvm_payload",
crate_name: "vm_payload",
srcs: ["src/*.rs"],
include_dirs: ["include"],
prefer_rlib: true,
rustlibs: [
- "android.system.virtualmachineservice-rust",
+ "android.system.virtualization.payload-rust",
"libandroid_logger",
"libanyhow",
"libbinder_rs",
"liblog_rust",
- "librpcbinder_rs",
+ ],
+ apex_available: [
+ "com.android.compos",
+ ],
+}
+
+rust_bindgen {
+ name: "libvm_payload_bindgen",
+ wrapper_src: "include/vm_payload.h",
+ crate_name: "vm_payload_bindgen",
+ source_stem: "bindings",
+ apex_available: ["com.android.compos"],
+ visibility: ["//packages/modules/Virtualization/compos"],
+ shared_libs: [
+ "libvm_payload",
],
}
diff --git a/microdroid/vm_payload/include/vm_payload.h b/microdroid/vm_payload/include/vm_payload.h
index 36480da..0744146 100644
--- a/microdroid/vm_payload/include/vm_payload.h
+++ b/microdroid/vm_payload/include/vm_payload.h
@@ -16,13 +16,48 @@
#pragma once
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
#ifdef __cplusplus
extern "C" {
#endif
-/// Notifies the host that the payload is ready.
-/// Returns true if the notification succeeds else false.
-bool notify_payload_ready();
+/**
+ * Notifies the host that the payload is ready.
+ * Returns true if the notification succeeds else false.
+ */
+bool notify_payload_ready(void);
+
+/**
+ * Get a secret that is uniquely bound to this VM instance. The secrets are 32-byte values and the
+ * value associated with an identifier will not change over the lifetime of the VM instance.
+ *
+ * \param identifier identifier of the secret to return.
+ * \param identifier_size size of the secret identifier.
+ * \param secret pointer to size bytes where the secret is written.
+ * \param size number of bytes of the secret to get, up to the secret size.
+ *
+ * \return true on success and false on failure.
+ */
+bool get_vm_instance_secret(const void *identifier, size_t identifier_size, void *secret,
+ size_t size);
+
+/**
+ * Get the VM's attestation chain.
+ * Returns the size of data or 0 on failure.
+ * TODO: don't expose the contained privacy breaking identifiers to the payload
+ * TODO: keep the DICE chain as an internal detail for as long as possible
+ */
+size_t get_dice_attestation_chain(void *data, size_t size);
+
+/**
+ * Get the VM's attestation CDI.
+ * Returns the size of data or 0 on failure.
+ * TODO: don't expose the raw CDI, only derived values
+ */
+size_t get_dice_attestation_cdi(void *data, size_t size);
#ifdef __cplusplus
} // extern "C"
diff --git a/microdroid/vm_payload/src/lib.rs b/microdroid/vm_payload/src/lib.rs
index 394578a..74dd8f4 100644
--- a/microdroid/vm_payload/src/lib.rs
+++ b/microdroid/vm_payload/src/lib.rs
@@ -16,4 +16,7 @@
mod vm_service;
-pub use vm_service::notify_payload_ready;
+pub use vm_service::{
+ get_dice_attestation_cdi, get_dice_attestation_chain, get_vm_instance_secret,
+ notify_payload_ready,
+};
diff --git a/microdroid/vm_payload/src/vm_service.rs b/microdroid/vm_payload/src/vm_service.rs
index e5a6b9a..cfc3884 100644
--- a/microdroid/vm_payload/src/vm_service.rs
+++ b/microdroid/vm_payload/src/vm_service.rs
@@ -12,17 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! This module handles the interaction with virtual machine service.
+//! This module handles the interaction with virtual machine payload service.
-use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
- VM_BINDER_SERVICE_PORT, IVirtualMachineService};
+use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
+ IVmPayloadService, VM_PAYLOAD_SERVICE_NAME};
use anyhow::{Context, Result};
-use binder::Strong;
+use binder::{wait_for_interface, Strong};
use log::{error, info, Level};
-use rpcbinder::get_vsock_rpc_interface;
-
-/// The CID representing the host VM
-const VMADDR_CID_HOST: u32 = 2;
/// Notifies the host that the payload is ready.
/// Returns true if the notification succeeds else false.
@@ -32,7 +28,7 @@
android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Debug),
);
if let Err(e) = try_notify_payload_ready() {
- error!("Failed to notify ready: {}", e);
+ error!("{:?}", e);
false
} else {
info!("Notified host payload ready successfully");
@@ -43,10 +39,100 @@
/// Notifies the host that the payload is ready.
/// Returns a `Result` containing error information if failed.
fn try_notify_payload_ready() -> Result<()> {
- get_vm_service()?.notifyPayloadReady().context("Cannot notify payload ready")
+ get_vm_payload_service()?.notifyPayloadReady().context("Cannot notify payload ready")
}
-fn get_vm_service() -> Result<Strong<dyn IVirtualMachineService>> {
- get_vsock_rpc_interface(VMADDR_CID_HOST, VM_BINDER_SERVICE_PORT as u32)
- .context("Connecting to IVirtualMachineService")
+/// Get a secret that is uniquely bound to this VM instance.
+///
+/// # Safety
+///
+/// The identifier must be identifier_size bytes and secret must be size bytes.
+#[no_mangle]
+pub unsafe extern "C" fn get_vm_instance_secret(
+ identifier: *const u8,
+ identifier_size: usize,
+ secret: *mut u8,
+ size: usize,
+) -> bool {
+ let identifier = std::slice::from_raw_parts(identifier, identifier_size);
+ match try_get_vm_instance_secret(identifier, size) {
+ Err(e) => {
+ error!("{:?}", e);
+ false
+ }
+ Ok(vm_secret) => {
+ if vm_secret.len() != size {
+ return false;
+ }
+ std::ptr::copy_nonoverlapping(vm_secret.as_ptr(), secret, size);
+ true
+ }
+ }
+}
+
+fn try_get_vm_instance_secret(identifier: &[u8], size: usize) -> Result<Vec<u8>> {
+ get_vm_payload_service()?
+ .getVmInstanceSecret(identifier, i32::try_from(size)?)
+ .context("Cannot get VM instance secret")
+}
+
+/// Get the VM's attestation chain.
+/// Returns the size of data or 0 on failure.
+///
+/// # Safety
+///
+/// The data must be size bytes big.
+#[no_mangle]
+pub unsafe extern "C" fn get_dice_attestation_chain(data: *mut u8, size: usize) -> usize {
+ match try_get_dice_attestation_chain() {
+ Err(e) => {
+ error!("{:?}", e);
+ 0
+ }
+ Ok(chain) => {
+ if size < chain.len() {
+ 0
+ } else {
+ std::ptr::copy_nonoverlapping(chain.as_ptr(), data, chain.len());
+ chain.len()
+ }
+ }
+ }
+}
+
+fn try_get_dice_attestation_chain() -> Result<Vec<u8>> {
+ get_vm_payload_service()?.getDiceAttestationChain().context("Cannot get attestation chain")
+}
+
+/// Get the VM's attestation CDI.
+/// Returns the size of data or 0 on failure.
+///
+/// # Safety
+///
+/// The data must be size bytes big.
+#[no_mangle]
+pub unsafe extern "C" fn get_dice_attestation_cdi(data: *mut u8, size: usize) -> usize {
+ match try_get_dice_attestation_cdi() {
+ Err(e) => {
+ error!("{:?}", e);
+ 0
+ }
+ Ok(cdi) => {
+ if size < cdi.len() {
+ 0
+ } else {
+ std::ptr::copy_nonoverlapping(cdi.as_ptr(), data, cdi.len());
+ cdi.len()
+ }
+ }
+ }
+}
+
+fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
+ get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
+}
+
+fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
+ wait_for_interface(VM_PAYLOAD_SERVICE_NAME)
+ .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_NAME))
}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 67130b4..4b06b3e 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -9,16 +9,18 @@
edition: "2021",
prefer_rlib: true,
rustlibs: [
- "android.hardware.security.dice-V1-rust",
- "android.security.dice-rust",
"android.system.virtualizationcommon-rust",
"android.system.virtualizationservice-rust",
"android.system.virtualmachineservice-rust",
+ "android.system.virtualization.payload-rust",
"libanyhow",
"libapexutil_rust",
"libapkverify",
"libbinder_rs",
"libbyteorder",
+ "libdiced",
+ "libdiced_open_dice_cbor",
+ "libdiced_sample_inputs",
"libdiced_utils",
"libglob",
"libitertools",
diff --git a/microdroid_manager/aidl/Android.bp b/microdroid_manager/aidl/Android.bp
new file mode 100644
index 0000000..0aa8662
--- /dev/null
+++ b/microdroid_manager/aidl/Android.bp
@@ -0,0 +1,18 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+ name: "android.system.virtualization.payload",
+ srcs: ["android/system/virtualization/payload/*.aidl"],
+ unstable: true,
+ backend: {
+ rust: {
+ enabled: true,
+ apex_available: [
+ "com.android.virt",
+ "com.android.compos",
+ ],
+ },
+ },
+}
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
new file mode 100644
index 0000000..d3ebe5c
--- /dev/null
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2022 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 android.system.virtualization.payload;
+
+/**
+ * This interface regroups the tasks that payloads delegate to
+ * Microdroid Manager for execution.
+ */
+interface IVmPayloadService {
+ /** Name of the service IVmPayloadService. */
+ const String VM_PAYLOAD_SERVICE_NAME = "virtual_machine_payload_service";
+
+ /** Notifies that the payload is ready to serve. */
+ void notifyPayloadReady();
+
+ /**
+ * Gets a secret that is uniquely bound to this VM instance.
+ *
+ * @param identifier the identifier of the secret to return.
+ * @param size the number of bytes of the secret to return.
+ * @return size bytes of the identified secret.
+ */
+ byte[] getVmInstanceSecret(in byte[] identifier, int size);
+
+ /**
+ * Gets the DICE attestation chain for the VM.
+ *
+ * STOPSHIP:
+ * TODO: don't expose this to untrusted payloads as it contains privacy breaking identifiers.
+ */
+ byte[] getDiceAttestationChain();
+
+ /**
+ * Gets the DICE attestation CDI for the VM.
+ *
+ * STOPSHIP:
+ * TODO: A better API would handle key derivation on behalf of the payload so they can't forget
+ * to do it themselves. It also means the payload doesn't get the raw CDI so there's less chance
+ * of it leaking.
+ */
+ byte[] getDiceAttestationCdi();
+}
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
new file mode 100644
index 0000000..3881db3
--- /dev/null
+++ b/microdroid_manager/src/dice.rs
@@ -0,0 +1,177 @@
+// Copyright 2022, 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.
+
+//! Logic for handling the DICE values and boot operations.
+
+use anyhow::{bail, Context, Error, Result};
+use byteorder::{NativeEndian, ReadBytesExt};
+use diced_open_dice_cbor::{
+ Config, ContextImpl, InputValuesOwned, Mode, OpenDiceCborContext, CDI_SIZE, HASH_SIZE,
+ HIDDEN_SIZE,
+};
+use keystore2_crypto::ZVec;
+use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+use std::fs;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::ptr::null_mut;
+use std::slice;
+
+/// Artifacts that are kept in the process address space after the artifacts from the driver have
+/// been consumed.
+pub struct DiceContext {
+ pub cdi_attest: [u8; CDI_SIZE],
+ pub cdi_seal: [u8; CDI_SIZE],
+ pub bcc: Vec<u8>,
+}
+
+/// Artifacts that are mapped into the process address space from the driver.
+pub enum DiceDriver<'a> {
+ Real {
+ driver_path: PathBuf,
+ mmap_addr: *mut c_void,
+ mmap_size: usize,
+ cdi_attest: &'a [u8; CDI_SIZE],
+ cdi_seal: &'a [u8; CDI_SIZE],
+ bcc: &'a [u8],
+ },
+ Fake(DiceContext),
+}
+
+impl DiceDriver<'_> {
+ pub fn new(driver_path: &Path) -> Result<Self> {
+ if driver_path.exists() {
+ log::info!("Using DICE values from driver");
+ } else if super::is_strict_boot() {
+ 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()
+ .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,
+ }));
+ };
+
+ let mut file = fs::File::open(driver_path)
+ .map_err(|error| Error::new(error).context("Opening driver"))?;
+ let mmap_size =
+ file.read_u64::<NativeEndian>()
+ .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
+ // It's safe to map the driver as the service will only create a single
+ // mapping per process.
+ let mmap_addr = unsafe {
+ let fd = file.as_raw_fd();
+ mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
+ };
+ if mmap_addr == MAP_FAILED {
+ bail!("Failed to mmap {:?}", driver_path);
+ }
+ // The slice is created for the region of memory that was just
+ // successfully mapped into the process address space so it will be
+ // accessible and not referenced from anywhere else.
+ let mmap_buf =
+ unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
+ // Very inflexible parsing / validation of the BccHandover data. Assumes deterministically
+ // encoded CBOR.
+ //
+ // BccHandover = {
+ // 1 : bstr .size 32, ; CDI_Attest
+ // 2 : bstr .size 32, ; CDI_Seal
+ // 3 : Bcc, ; Certificate chain
+ // }
+ if mmap_buf[0..4] != [0xa3, 0x01, 0x58, 0x20]
+ || mmap_buf[36..39] != [0x02, 0x58, 0x20]
+ || mmap_buf[71] != 0x03
+ {
+ bail!("BccHandover format mismatch");
+ }
+ Ok(Self::Real {
+ driver_path: driver_path.to_path_buf(),
+ mmap_addr,
+ mmap_size,
+ cdi_attest: mmap_buf[4..36].try_into().unwrap(),
+ cdi_seal: mmap_buf[39..71].try_into().unwrap(),
+ bcc: &mmap_buf[72..],
+ })
+ }
+
+ pub fn get_sealing_key(&self, identifier: &[u8]) -> Result<ZVec> {
+ // Deterministically derive a key to use for sealing data, rather than using the CDI
+ // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
+ // input key material is already cryptographically strong.
+ let cdi_seal = match self {
+ Self::Real { cdi_seal, .. } => cdi_seal,
+ Self::Fake(fake) => &fake.cdi_seal,
+ };
+ let salt = &[];
+ let mut key = ZVec::new(32)?;
+ hkdf(&mut key, Md::sha256(), cdi_seal, salt, identifier)?;
+ Ok(key)
+ }
+
+ pub fn derive(
+ self,
+ code_hash: [u8; HASH_SIZE],
+ config_desc: &[u8],
+ authority_hash: [u8; HASH_SIZE],
+ debug: bool,
+ hidden: [u8; HIDDEN_SIZE],
+ ) -> Result<DiceContext> {
+ let input_values = InputValuesOwned::new(
+ code_hash,
+ Config::Descriptor(config_desc),
+ authority_hash,
+ None,
+ if debug { Mode::Debug } else { Mode::Normal },
+ hidden,
+ );
+ let (cdi_attest, cdi_seal, bcc) = match &self {
+ 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)
+ .context("DICE derive from driver")?;
+ if let Self::Real { driver_path, .. } = &self {
+ // Writing to the device wipes the artifacts. The string is ignored by the driver but
+ // included for documentation.
+ fs::write(driver_path, "wipe")
+ .map_err(|err| Error::new(err).context("Wiping driver"))?;
+ }
+ Ok(DiceContext {
+ cdi_attest: cdi_attest[..].try_into().unwrap(),
+ cdi_seal: cdi_seal[..].try_into().unwrap(),
+ bcc,
+ })
+ }
+}
+
+impl Drop for DiceDriver<'_> {
+ fn drop(&mut self) {
+ if let &mut Self::Real { mmap_addr, mmap_size, .. } = self {
+ // All references to the mapped region have the same lifetime as self. Since self is
+ // being dropped, so are all the references to the mapped region meaning its safe to
+ // unmap.
+ let ret = unsafe { munmap(mmap_addr, mmap_size) };
+ if ret != 0 {
+ log::warn!("Failed to munmap ({})", ret);
+ }
+ }
+ }
+}
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 358d88b..96e9360 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,15 +33,11 @@
//! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
//! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
+use crate::dice::DiceDriver;
use crate::ioutil;
-use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode;
use anyhow::{anyhow, bail, Context, Result};
-use binder::wait_for_interface;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
-use keystore2_crypto::ZVec;
-use openssl::hkdf::hkdf;
-use openssl::md::Md;
use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
use serde::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
@@ -51,6 +47,9 @@
/// Path to the instance disk inside the VM
const INSTANCE_IMAGE_PATH: &str = "/dev/block/by-name/vm-instance";
+/// Identifier for the key used to seal the instance data.
+const INSTANCE_KEY_IDENTIFIER: &[u8] = b"microdroid_manager_key";
+
/// Magic string in the instance disk header
const DISK_HEADER_MAGIC: &str = "Android-VM-instance";
@@ -114,7 +113,7 @@
/// Reads the identity data that was written by microdroid manager. The returned data is
/// plaintext, although it is stored encrypted. In case when the partition for microdroid
/// manager doesn't exist, which can happen if it's the first boot, `Ok(None)` is returned.
- pub fn read_microdroid_data(&mut self) -> Result<Option<MicrodroidData>> {
+ pub fn read_microdroid_data(&mut self, dice: &DiceDriver) -> Result<Option<MicrodroidData>> {
let (header, offset) = self.locate_microdroid_header()?;
if header.is_none() {
return Ok(None);
@@ -143,7 +142,7 @@
self.file.read_exact(&mut header)?;
// Decrypt and authenticate the data (along with the header).
- let key = get_key()?;
+ let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
let plaintext =
decrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &tag)?;
@@ -153,7 +152,11 @@
/// Writes identity data to the partition for microdroid manager. The partition is appended
/// if it doesn't exist. The data is stored encrypted.
- pub fn write_microdroid_data(&mut self, microdroid_data: &MicrodroidData) -> Result<()> {
+ pub fn write_microdroid_data(
+ &mut self,
+ microdroid_data: &MicrodroidData,
+ dice: &DiceDriver,
+ ) -> Result<()> {
let (header, offset) = self.locate_microdroid_header()?;
let data = serde_cbor::to_vec(microdroid_data)?;
@@ -185,7 +188,7 @@
self.file.write_all(nonce.as_ref())?;
// Then encrypt and sign the data.
- let key = get_key()?;
+ let key = dice.get_sealing_key(INSTANCE_KEY_IDENTIFIER)?;
let mut tag = [0; AES_256_GCM_TAG_LENGTH];
let ciphertext =
encrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &mut tag)?;
@@ -268,23 +271,6 @@
Ok(ret)
}
-/// Returns the key that is used to encrypt the microdroid manager partition. It is derived from
-/// the sealing CDI of the previous stage, which is Android Boot Loader (ABL).
-fn get_key() -> Result<ZVec> {
- // Sealing CDI from the previous stage.
- let diced = wait_for_interface::<dyn IDiceNode>("android.security.dice.IDiceNode")
- .context("IDiceNode service not found")?;
- let bcc_handover = diced.derive(&[]).context("Failed to get BccHandover")?;
- // Deterministically derive another key to use for encrypting the data, rather than using the
- // CDI directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
- // input key material is already cryptographically strong.
- let salt = &[];
- let info = b"microdroid_manager_key".as_ref();
- let mut key = ZVec::new(32)?;
- hkdf(&mut key, Md::sha256(), &bcc_handover.cdiSeal, salt, info)?;
- Ok(key)
-}
-
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct MicrodroidData {
pub salt: Vec<u8>, // Should be [u8; 64] but that isn't serializable.
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index b4b2c8b..a6856d0 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -14,22 +14,22 @@
//! Microdroid Manager
+mod dice;
mod instance;
mod ioutil;
mod payload;
+mod vm_payload_service;
+use crate::dice::{DiceContext, DiceDriver};
use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
-use android_hardware_security_dice::aidl::android::hardware::security::dice::{
- Config::Config, InputValues::InputValues, Mode::Mode,
-};
-use android_security_dice::aidl::android::security::dice::IDiceMaintenance::IDiceMaintenance;
+use crate::vm_payload_service::register_vm_payload_service;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
};
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
use apkverify::{get_public_key_der, verify, V4Signature};
-use binder::{wait_for_interface, Strong};
+use binder::{ProcessState, Strong};
use diced_utils::cbor::{encode_header, encode_number};
use glob::glob;
use itertools::sorted;
@@ -71,6 +71,7 @@
const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
const APP_DEBUGGABLE_PROP: &str = "ro.boot.microdroid.app_debuggable";
+const APK_MOUNT_DONE_PROP: &str = "microdroid_manager.apk.mounted";
// SYNC WITH virtualizationservice/src/crosvm.rs
const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
@@ -192,9 +193,10 @@
}
fn dice_derivation(
+ dice: DiceDriver,
verified_data: &MicrodroidData,
payload_metadata: &PayloadMetadata,
-) -> Result<()> {
+) -> Result<DiceContext> {
// Calculate compound digests of code and authorities
let mut code_hash_ctx = Sha512::new();
let mut authority_hash_ctx = Sha512::new();
@@ -245,20 +247,8 @@
let app_debuggable = system_properties::read_bool(APP_DEBUGGABLE_PROP, true)?;
// Send the details to diced
- let diced =
- wait_for_interface::<dyn IDiceMaintenance>("android.security.dice.IDiceMaintenance")
- .context("IDiceMaintenance service not found")?;
- diced
- .demoteSelf(&[InputValues {
- codeHash: code_hash,
- config: Config { desc: config_desc },
- authorityHash: authority_hash,
- authorityDescriptor: None,
- mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
- hidden: verified_data.salt.clone().try_into().unwrap(),
- }])
- .context("IDiceMaintenance::demoteSelf failed")?;
- Ok(())
+ let hidden = verified_data.salt.clone().try_into().unwrap();
+ dice.derive(code_hash, &config_desc, authority_hash, app_debuggable, hidden)
}
fn encode_tstr(tstr: &str, buffer: &mut Vec<u8>) -> Result<()> {
@@ -288,9 +278,11 @@
fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
let metadata = load_metadata().context("Failed to load payload metadata")?;
+ let dice = DiceDriver::new(Path::new("/dev/open-dice0")).context("Failed to load DICE")?;
let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
- let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
+ let saved_data =
+ instance.read_microdroid_data(&dice).context("Failed to read identity data")?;
if is_strict_boot() {
// Provisioning must happen on the first boot and never again.
@@ -331,7 +323,9 @@
saved_data
} else {
info!("Saving verified data.");
- instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
+ instance
+ .write_microdroid_data(&verified_data, &dice)
+ .context("Failed to write identity data")?;
verified_data
};
@@ -341,15 +335,15 @@
// To minimize the exposure to untrusted data, derive dice profile as soon as possible.
info!("DICE derivation for payload");
- dice_derivation(&verified_data, &payload_metadata)?;
+ let dice = dice_derivation(dice, &verified_data, &payload_metadata)?;
// Before reading a file from the APK, start zipfuse
- let noexec = false;
run_zipfuse(
- noexec,
+ MountForExec::Allowed,
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
Path::new("/dev/block/mapper/microdroid-apk"),
Path::new("/mnt/apk"),
+ Some(APK_MOUNT_DONE_PROP),
)
.context("Failed to run zipfuse")?;
@@ -385,7 +379,12 @@
control_service("start", "authfs_service")?;
}
+ // Wait until zipfuse has mounted the APK so we can access the payload
+ wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
+
system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
+ register_vm_payload_service(service.clone(), dice)?;
+ ProcessState::start_thread_pool();
exec_task(task, service)
}
@@ -418,11 +417,25 @@
cmd.spawn().context("Spawn apkdmverity")
}
-fn run_zipfuse(noexec: bool, option: &str, zip_path: &Path, mount_dir: &Path) -> Result<Child> {
+enum MountForExec {
+ Allowed,
+ Disallowed,
+}
+
+fn run_zipfuse(
+ noexec: MountForExec,
+ option: &str,
+ zip_path: &Path,
+ mount_dir: &Path,
+ ready_prop: Option<&str>,
+) -> Result<Child> {
let mut cmd = Command::new(ZIPFUSE_BIN);
- if noexec {
+ if let MountForExec::Disallowed = noexec {
cmd.arg("--noexec");
}
+ if let Some(property_name) = ready_prop {
+ cmd.args(["-p", property_name]);
+ }
cmd.arg("-o")
.arg(option)
.arg(zip_path)
@@ -608,12 +621,12 @@
create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
// don't wait, just detach
- let noexec = true;
run_zipfuse(
- noexec,
+ MountForExec::Disallowed,
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
Path::new(&mount_dir),
+ None,
)
.context("Failed to zipfuse extra apks")?;
}
@@ -623,10 +636,14 @@
// Waits until linker config is generated
fn wait_for_apex_config_done() -> Result<()> {
- let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
+ wait_for_property_true(APEX_CONFIG_DONE_PROP).context("Failed waiting for apex config done")
+}
+
+fn wait_for_property_true(property_name: &str) -> Result<()> {
+ let mut prop = PropertyWatcher::new(property_name)?;
loop {
prop.wait()?;
- if system_properties::read_bool(APEX_CONFIG_DONE_PROP, false)? {
+ if system_properties::read_bool(property_name, false)? {
break;
}
}
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
new file mode 100644
index 0000000..4b47ad9
--- /dev/null
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -0,0 +1,87 @@
+// Copyright 2022, 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.
+
+//! Implementation of the AIDL interface `IVmPayloadService`.
+
+use crate::dice::DiceContext;
+use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
+ BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_NAME};
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
+use anyhow::{Context, Result};
+use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong, add_service};
+use log::error;
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+
+/// Implementation of `IVmPayloadService`.
+struct VmPayloadService {
+ virtual_machine_service: Strong<dyn IVirtualMachineService>,
+ dice: DiceContext,
+}
+
+impl IVmPayloadService for VmPayloadService {
+ fn notifyPayloadReady(&self) -> binder::Result<()> {
+ self.virtual_machine_service.notifyPayloadReady()
+ }
+
+ fn getVmInstanceSecret(&self, identifier: &[u8], size: i32) -> binder::Result<Vec<u8>> {
+ if !(0..=32).contains(&size) {
+ return Err(Status::new_exception(ExceptionCode::ILLEGAL_ARGUMENT, None));
+ }
+ // Use a fixed salt to scope the derivation to this API. It was randomly generated.
+ let salt = [
+ 0x8B, 0x0F, 0xF0, 0xD3, 0xB1, 0x69, 0x2B, 0x95, 0x84, 0x2C, 0x9E, 0x3C, 0x99, 0x56,
+ 0x7A, 0x22, 0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA,
+ 0xB7, 0xA8, 0x43, 0x92,
+ ];
+ let mut secret = vec![0; size.try_into().unwrap()];
+ hkdf(&mut secret, Md::sha256(), &self.dice.cdi_seal, &salt, identifier).map_err(|e| {
+ error!("Failed to derive VM instance secret: {:?}", e);
+ Status::new_service_specific_error(-1, None)
+ })?;
+ Ok(secret)
+ }
+
+ fn getDiceAttestationChain(&self) -> binder::Result<Vec<u8>> {
+ Ok(self.dice.bcc.clone())
+ }
+
+ fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
+ Ok(self.dice.cdi_attest.to_vec())
+ }
+}
+
+impl Interface for VmPayloadService {}
+
+impl VmPayloadService {
+ /// Creates a new `VmPayloadService` instance from the `IVirtualMachineService` reference.
+ fn new(vm_service: Strong<dyn IVirtualMachineService>, dice: DiceContext) -> Self {
+ Self { virtual_machine_service: vm_service, dice }
+ }
+}
+
+/// Registers the `IVmPayloadService` service.
+pub(crate) fn register_vm_payload_service(
+ vm_service: Strong<dyn IVirtualMachineService>,
+ dice: DiceContext,
+) -> Result<()> {
+ let vm_payload_binder = BnVmPayloadService::new_binder(
+ VmPayloadService::new(vm_service, dice),
+ BinderFeatures::default(),
+ );
+ add_service(VM_PAYLOAD_SERVICE_NAME, vm_payload_binder.as_binder())
+ .with_context(|| format!("Failed to register service {}", VM_PAYLOAD_SERVICE_NAME))?;
+ log::info!("{} is running", VM_PAYLOAD_SERVICE_NAME);
+ Ok(())
+}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 57a3df0..84cb18c 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -43,6 +43,12 @@
},
}
+prebuilt_etc {
+ name: "pvmfw_sign_key",
+ src: ":avb_testkey_rsa4096",
+ installable: false,
+}
+
bootimg {
name: "pvmfw_img",
stem: "pvmfw.img",
@@ -55,4 +61,6 @@
enabled: true,
},
},
+ use_avb: true,
+ avb_private_key: ":pvmfw_sign_key",
}
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
deleted file mode 100644
index bacf213..0000000
--- a/pvmfw/pvmfw.img
+++ /dev/null
Binary files differ
diff --git a/pvmfw/src/exceptions.rs b/pvmfw/src/exceptions.rs
index 596ecc7..0fb2911 100644
--- a/pvmfw/src/exceptions.rs
+++ b/pvmfw/src/exceptions.rs
@@ -14,14 +14,23 @@
//! Exception handlers.
+use crate::helpers::page_4kb_of;
use core::arch::asm;
+use vmbase::console;
use vmbase::{console::emergency_write_str, eprintln, power::reboot};
+const ESR_32BIT_EXT_DABT: u64 = 0x96000010;
+const UART_PAGE: u64 = page_4kb_of(console::BASE_ADDRESS as u64);
+
#[no_mangle]
extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
let esr = read_esr();
- emergency_write_str("sync_exception_current\n");
- print_esr(esr);
+ let far = read_far();
+ // Don't print to the UART if we're handling the exception it could raise.
+ if esr != ESR_32BIT_EXT_DABT || page_4kb_of(far) != UART_PAGE {
+ emergency_write_str("sync_exception_current\n");
+ print_esr(esr);
+ }
reboot();
}
@@ -86,3 +95,12 @@
fn print_esr(esr: u64) {
eprintln!("esr={:#08x}", esr);
}
+
+#[inline]
+fn read_far() -> u64 {
+ let mut far: u64;
+ unsafe {
+ asm!("mrs {far}, far_el1", far = out(reg) far);
+ }
+ far
+}
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
new file mode 100644
index 0000000..781c1ac
--- /dev/null
+++ b/pvmfw/src/helpers.rs
@@ -0,0 +1,36 @@
+// Copyright 2022, 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.
+
+//! Miscellaneous helper functions.
+
+/// Computes the address of the page containing a given address.
+pub const fn page_of(addr: u64, page_size: u64) -> u64 {
+ addr & !(page_size - 1)
+}
+
+/// Validates a page size and computes the address of the page containing a given address.
+pub const fn checked_page_of(addr: u64, page_size: u64) -> Option<u64> {
+ if page_size.is_power_of_two() {
+ Some(page_of(addr, page_size))
+ } else {
+ None
+ }
+}
+
+/// Computes the address of the 4KiB page containing a given address.
+pub const fn page_4kb_of(addr: u64) -> u64 {
+ const PAGE_SIZE: u64 = 4 << 10;
+
+ page_of(addr, PAGE_SIZE)
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 8526a92..eb97961 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -18,13 +18,36 @@
#![no_std]
mod exceptions;
+mod helpers;
+mod smccc;
-use vmbase::{main, println};
+use core::fmt;
+use helpers::checked_page_of;
-main!(main);
+use vmbase::{console, main, power::reboot, println};
-/// Entry point for pVM firmware.
-pub fn main(fdt_address: u64, payload_start: u64, payload_size: u64, arg3: u64) {
+#[derive(Debug, Clone)]
+enum Error {
+ /// Failed to configure the UART; no logs available.
+ FailedUartSetup,
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let msg = match self {
+ Self::FailedUartSetup => "Failed to configure the UART",
+ };
+ write!(f, "{}", msg)
+ }
+}
+
+fn main(fdt_address: u64, payload_start: u64, payload_size: u64, arg3: u64) -> Result<(), Error> {
+ // We need to inform the hypervisor that the MMIO page containing the UART may be shared back.
+ let uart = console::BASE_ADDRESS as u64;
+ let mmio_granule = smccc::mmio_guard_info().map_err(|_| Error::FailedUartSetup)?;
+ let uart_page = checked_page_of(uart, mmio_granule).ok_or(Error::FailedUartSetup)?;
+ smccc::mmio_guard_map(uart_page).map_err(|_| Error::FailedUartSetup)?;
+
println!("pVM firmware");
println!(
"fdt_address={:#018x}, payload_start={:#018x}, payload_size={:#018x}, x3={:#018x}",
@@ -32,6 +55,24 @@
);
println!("Starting payload...");
+
+ Ok(())
+}
+
+main!(main_wrapper);
+
+/// Entry point for pVM firmware.
+pub fn main_wrapper(fdt_address: u64, payload_start: u64, payload_size: u64, arg3: u64) {
+ match main(fdt_address, payload_start, payload_size, arg3) {
+ Ok(()) => jump_to_payload(fdt_address, payload_start),
+ Err(e) => {
+ println!("Boot rejected: {}", e);
+ }
+ }
+ reboot()
+}
+
+fn jump_to_payload(fdt_address: u64, payload_start: u64) {
// Safe because this is a function we have implemented in assembly that matches its signature
// here.
unsafe {
diff --git a/pvmfw/src/smccc.rs b/pvmfw/src/smccc.rs
new file mode 100644
index 0000000..e3a2b05
--- /dev/null
+++ b/pvmfw/src/smccc.rs
@@ -0,0 +1,115 @@
+// Copyright 2022, 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.
+
+use core::fmt;
+
+// TODO(b/245889995): use psci-0.1.1 crate
+#[inline(always)]
+fn hvc64(function: u32, args: [u64; 17]) -> [u64; 18] {
+ #[cfg(target_arch = "aarch64")]
+ unsafe {
+ let mut ret = [0; 18];
+
+ core::arch::asm!(
+ "hvc #0",
+ inout("x0") function as u64 => ret[0],
+ inout("x1") args[0] => ret[1],
+ inout("x2") args[1] => ret[2],
+ inout("x3") args[2] => ret[3],
+ inout("x4") args[3] => ret[4],
+ inout("x5") args[4] => ret[5],
+ inout("x6") args[5] => ret[6],
+ inout("x7") args[6] => ret[7],
+ inout("x8") args[7] => ret[8],
+ inout("x9") args[8] => ret[9],
+ inout("x10") args[9] => ret[10],
+ inout("x11") args[10] => ret[11],
+ inout("x12") args[11] => ret[12],
+ inout("x13") args[12] => ret[13],
+ inout("x14") args[13] => ret[14],
+ inout("x15") args[14] => ret[15],
+ inout("x16") args[15] => ret[16],
+ inout("x17") args[16] => ret[17],
+ options(nomem, nostack)
+ );
+
+ ret
+ }
+}
+
+/// Standard SMCCC error values as described in DEN 0028E.
+#[derive(Debug, Clone)]
+pub enum Error {
+ /// The call is not supported by the implementation.
+ NotSupported,
+ /// The call is deemed not required by the implementation.
+ NotRequired,
+ /// One of the call parameters has a non-supported value.
+ InvalidParameter,
+ /// Negative values indicate error.
+ Unknown(i64),
+ /// The call returned a positive value when 0 was expected.
+ Unexpected(u64),
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::NotSupported => write!(f, "SMCCC call not supported"),
+ Self::NotRequired => write!(f, "SMCCC call not required"),
+ Self::InvalidParameter => write!(f, "SMCCC call received non-supported value"),
+ Self::Unexpected(v) => write!(f, "Unexpected SMCCC return value '{}'", v),
+ Self::Unknown(e) => write!(f, "Unknown SMCCC return value '{}'", e),
+ }
+ }
+}
+
+fn check_smccc_err(ret: i64) -> Result<(), Error> {
+ match check_smccc_value(ret)? {
+ 0 => Ok(()),
+ v => Err(Error::Unexpected(v)),
+ }
+}
+
+fn check_smccc_value(ret: i64) -> Result<u64, Error> {
+ match ret {
+ x if x >= 0 => Ok(ret as u64),
+ -1 => Err(Error::NotSupported),
+ -2 => Err(Error::NotRequired),
+ -3 => Err(Error::InvalidParameter),
+ _ => Err(Error::Unknown(ret)),
+ }
+}
+
+const VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
+const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
+
+/// Issue pKVM-specific MMIO_GUARD_INFO HVC64.
+pub fn mmio_guard_info() -> Result<u64, Error> {
+ let args = [0u64; 17];
+
+ let res = hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args);
+
+ check_smccc_value(res[0] as i64)
+}
+
+/// Issue pKVM-specific MMIO_GUARD_MAP HVC64.
+pub fn mmio_guard_map(ipa: u64) -> Result<(), Error> {
+ let mut args = [0u64; 17];
+ args[0] = ipa;
+
+ let res = hvc64(VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID, args);
+
+ check_smccc_err(res[0] as i64)
+}
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 0913fe3..ebb2bcf 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -25,8 +25,8 @@
/* read a system property. */
String readProperty(String prop);
- /* get the VM's stable secret, this is _only_ done for testing. */
- byte[] insecurelyExposeSealingCdi();
+ /* get a VM instance secret, this is _only_ done for testing. */
+ byte[] insecurelyExposeVmInstanceSecret();
/* get the VM's attestation secret, this is _only_ done for testing. */
byte[] insecurelyExposeAttestationCdi();
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index f1aebd2..e3f4685 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -19,6 +19,7 @@
libs: ["android.system.virtualmachine"],
jni_libs: [
"MicrodroidBenchmarkNativeLib",
+ "MicrodroidIdleNativeLib",
"libiovsock_host_jni",
],
platform_apis: true,
@@ -27,6 +28,14 @@
}
cc_library_shared {
+ name: "MicrodroidIdleNativeLib",
+ srcs: ["src/native/idlebinary.cpp"],
+ shared_libs: [
+ "libbase",
+ ],
+}
+
+cc_library_shared {
name: "MicrodroidBenchmarkNativeLib",
srcs: ["src/native/benchmarkbinary.cpp"],
static_libs: ["libiobenchmark"],
@@ -37,6 +46,7 @@
"libbinder_ndk",
"libbinder_rpc_unstable",
"liblog",
+ "libvm_payload",
],
}
diff --git a/tests/benchmark/assets/vm_config.json b/tests/benchmark/assets/vm_config.json
index e8f43e0..5a604a9 100644
--- a/tests/benchmark/assets/vm_config.json
+++ b/tests/benchmark/assets/vm_config.json
@@ -4,10 +4,7 @@
},
"task": {
"type": "microdroid_launcher",
- "command": "MicrodroidBenchmarkNativeLib.so",
- "args": [
- "no_io"
- ]
+ "command": "MicrodroidIdleNativeLib.so"
},
"export_tombstones": true
}
diff --git a/tests/benchmark/assets/vm_config_io.json b/tests/benchmark/assets/vm_config_io.json
index 1a5a9e5..66046ba 100644
--- a/tests/benchmark/assets/vm_config_io.json
+++ b/tests/benchmark/assets/vm_config_io.json
@@ -4,10 +4,7 @@
},
"task": {
"type": "microdroid_launcher",
- "command": "MicrodroidBenchmarkNativeLib.so",
- "args": [
- "io"
- ]
+ "command": "MicrodroidBenchmarkNativeLib.so"
},
"apexes": [
{
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
index da08a47..cbb9a0a 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
@@ -49,7 +49,7 @@
try {
IBenchmarkService benchmarkService =
IBenchmarkService.Stub.asInterface(
- vm.connectToVsockServer(IBenchmarkService.SERVICE_PORT).get());
+ vm.connectToVsockServer(IBenchmarkService.SERVICE_PORT));
assertThat(benchmarkService).isNotNull();
mListener.onPayloadReady(vm, benchmarkService);
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 263956b..b1a1160 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -16,6 +16,9 @@
package com.android.microdroid.benchmark;
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
@@ -27,7 +30,6 @@
import android.os.RemoteException;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
import android.system.virtualmachine.VirtualMachineException;
import android.util.Log;
@@ -91,7 +93,7 @@
VirtualMachineConfig.Builder builder =
mInner.newVmConfigBuilder("assets/vm_config.json");
VirtualMachineConfig normalConfig =
- builder.debugLevel(DebugLevel.NONE).memoryMib(mem).build();
+ builder.setDebugLevel(DEBUG_LEVEL_NONE).setMemoryMib(mem).build();
mInner.forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
if (tryBootVm(TAG, "test_vm_minimum_memory").payloadStarted) return true;
@@ -140,12 +142,12 @@
List<Double> userspaceBootTimeMetrics = new ArrayList<>();
for (int i = 0; i < trialCount; i++) {
- VirtualMachineConfig.Builder builder =
- mInner.newVmConfigBuilder("assets/vm_config.json");
// To grab boot events from log, set debug mode to FULL
- VirtualMachineConfig normalConfig =
- builder.debugLevel(DebugLevel.FULL).memoryMib(256).build();
+ VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .setMemoryMib(256)
+ .build();
mInner.forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
BootResult result = tryBootVm(TAG, "test_vm_boot_time");
@@ -189,10 +191,9 @@
@Test
public void testVsockTransferFromHostToVM() throws Exception {
- VirtualMachineConfig config =
- mInner.newVmConfigBuilder("assets/vm_config_io.json")
- .debugLevel(DebugLevel.FULL)
- .build();
+ VirtualMachineConfig config = mInner.newVmConfigBuilder("assets/vm_config_io.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
List<Double> transferRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
for (int i = 0; i < IO_TEST_TRIAL_COUNT; ++i) {
@@ -216,10 +217,9 @@
}
private void testVirtioBlkReadRate(boolean isRand) throws Exception {
- VirtualMachineConfig config =
- mInner.newVmConfigBuilder("assets/vm_config_io.json")
- .debugLevel(DebugLevel.FULL)
- .build();
+ VirtualMachineConfig config = mInner.newVmConfigBuilder("assets/vm_config_io.json")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
+ .build();
List<Double> readRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
for (int i = 0; i < IO_TEST_TRIAL_COUNT + 1; ++i) {
@@ -284,8 +284,8 @@
final String vmName = "test_vm_mem_usage";
VirtualMachineConfig config =
mInner.newVmConfigBuilder("assets/vm_config_io.json")
- .debugLevel(DebugLevel.NONE)
- .memoryMib(256)
+ .setDebugLevel(DEBUG_LEVEL_NONE)
+ .setMemoryMib(256)
.build();
mInner.forceCreateNewVirtualMachine(vmName, config);
VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
@@ -406,7 +406,11 @@
new Thread(() -> sendRate.set(runVsockClientAndSendData(vm))).start();
benchmarkService.runVsockServerAndReceiveData(serverFd, NUM_BYTES_TO_TRANSFER);
- mReadRates.add(sendRate.get());
+ Double rate = sendRate.get();
+ if (rate == null) {
+ throw new IllegalStateException("runVsockClientAndSendData() failed");
+ }
+ mReadRates.add(rate);
}
private double runVsockClientAndSendData(VirtualMachine vm) {
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 58b4cf0..36784b9 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -26,6 +26,7 @@
#include <stdio.h>
#include <time.h>
#include <unistd.h>
+#include <vm_payload.h>
#include <binder_rpc_unstable.hpp>
#include <fstream>
@@ -157,19 +158,8 @@
Result<void> run_io_benchmark_tests() {
auto test_service = ndk::SharedRefBase::make<IOBenchmarkService>();
auto callback = []([[maybe_unused]] void* param) {
- // Tell microdroid_manager that we're ready.
- // If we can't, abort in order to fail fast - the host won't proceed without
- // receiving the onReady signal.
- ndk::SpAIBinder binder(
- RpcClient(VMADDR_CID_HOST, IVirtualMachineService::VM_BINDER_SERVICE_PORT));
- auto vm_service = IVirtualMachineService::fromBinder(binder);
- if (vm_service == nullptr) {
- LOG(ERROR) << "failed to connect VirtualMachineService\n";
- abort();
- }
- if (auto status = vm_service->notifyPayloadReady(); !status.isOk()) {
- LOG(ERROR) << "failed to notify payload ready to virtualizationservice: "
- << status.getDescription();
+ if (!notify_payload_ready()) {
+ LOG(ERROR) << "failed to notify payload ready to virtualizationservice";
abort();
}
};
@@ -182,17 +172,10 @@
}
} // Anonymous namespace
-extern "C" int android_native_main([[maybe_unused]] int argc, char* argv[]) {
- if (strcmp(argv[1], "no_io") == 0) {
- // do nothing for now; just leave it alive. good night.
- for (;;) {
- sleep(1000);
- }
- } else if (strcmp(argv[1], "io") == 0) {
- if (auto res = run_io_benchmark_tests(); !res.ok()) {
- LOG(ERROR) << "IO benchmark test failed: " << res.error() << "\n";
- return EXIT_FAILURE;
- }
+extern "C" int android_native_main(int /* argc */, char* /* argv */[]) {
+ if (auto res = run_io_benchmark_tests(); !res.ok()) {
+ LOG(ERROR) << "IO benchmark test failed: " << res.error() << "\n";
+ return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
diff --git a/tests/benchmark/src/native/idlebinary.cpp b/tests/benchmark/src/native/idlebinary.cpp
new file mode 100644
index 0000000..a74e7bf
--- /dev/null
+++ b/tests/benchmark/src/native/idlebinary.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <unistd.h>
+
+extern "C" int android_native_main(int /* argc */, char* /* argv */[]) {
+ // do nothing; just leave it alive. good night.
+ for (;;) {
+ sleep(1000);
+ }
+}
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index a1dee6d..c2fd295 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -77,10 +77,12 @@
return mContext;
}
- /** Create a new VirtualMachineConfig.Builder with the parameterized protection mode. */
+ public VirtualMachineConfig.Builder newVmConfigBuilder() {
+ return new VirtualMachineConfig.Builder(mContext).setProtectedVm(mProtectedVm);
+ }
+
public VirtualMachineConfig.Builder newVmConfigBuilder(String payloadConfigPath) {
- return new VirtualMachineConfig.Builder(mContext, payloadConfigPath)
- .protectedVm(mProtectedVm);
+ return newVmConfigBuilder().setPayloadConfigPath(payloadConfigPath);
}
/**
@@ -148,7 +150,7 @@
if (!mVcpuStartedNanoTime.isPresent()) {
mVcpuStartedNanoTime = OptionalLong.of(System.nanoTime());
}
- if (log.contains("Starting kernel") && !mKernelStartedNanoTime.isPresent()) {
+ if (log.contains("Starting payload...") && !mKernelStartedNanoTime.isPresent()) {
mKernelStartedNanoTime = OptionalLong.of(System.nanoTime());
}
if (log.contains("Run /init as init process") && !mInitStartedNanoTime.isPresent()) {
@@ -239,7 +241,7 @@
@Override
@CallSuper
- public void onDied(VirtualMachine vm, int reason) {
+ public void onStopped(VirtualMachine vm, int reason) {
vm.clearCallback();
mExecutorService.shutdown();
}
@@ -328,16 +330,16 @@
}
@Override
- public void onDied(VirtualMachine vm, int reason) {
+ public void onStopped(VirtualMachine vm, int reason) {
deathReason.complete(reason);
- super.onDied(vm, reason);
+ super.onStopped(vm, reason);
}
};
long apiCallNanoTime = System.nanoTime();
listener.runToFinish(logTag, vm);
return new BootResult(
payloadStarted.getNow(false),
- deathReason.getNow(VirtualMachineCallback.DEATH_REASON_INFRASTRUCTURE_ERROR),
+ deathReason.getNow(VmEventListener.STOP_REASON_INFRASTRUCTURE_ERROR),
apiCallNanoTime,
endTime.getNow(apiCallNanoTime) - apiCallNanoTime,
listener.getVcpuStartedNanoTime(),
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index 9dd2a15..a2deb19 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -426,6 +426,7 @@
}
@Test
+ @Ignore("b/245081929")
@CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
public void testBootFailsWhenProtectedVmStartsWithImagesSignedWithDifferentKey()
throws Exception {
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 9e6958c..e738930 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -20,7 +20,10 @@
"compatibility-common-util-devicesidelib",
],
libs: ["android.system.virtualmachine"],
- jni_libs: ["MicrodroidTestNativeLib"],
+ jni_libs: [
+ "MicrodroidTestNativeLib",
+ "MicrodroidTestNativeCrashLib",
+ ],
platform_apis: true,
use_embedded_native_libs: true,
// We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
@@ -32,22 +35,26 @@
name: "MicrodroidTestNativeLib",
srcs: ["src/native/testbinary.cpp"],
shared_libs: [
- "android.security.dice-ndk",
"com.android.microdroid.testservice-ndk",
"libbase",
"libbinder_ndk",
"libbinder_rpc_unstable",
"MicrodroidTestNativeLibSub",
+ "libvm_payload",
],
static_libs: [
"libfsverity_digests_proto_cc",
"liblog",
"libprotobuf-cpp-lite-ndk",
- "libvm_payload",
],
}
cc_library_shared {
+ name: "MicrodroidTestNativeCrashLib",
+ srcs: ["src/native/crashbinary.cpp"],
+}
+
+cc_library_shared {
name: "MicrodroidTestNativeLibSub",
srcs: ["src/native/testlib.cpp"],
}
diff --git a/tests/testapk/assets/vm_config.json b/tests/testapk/assets/vm_config.json
index da420ff..d12eb5c 100644
--- a/tests/testapk/assets/vm_config.json
+++ b/tests/testapk/assets/vm_config.json
@@ -4,11 +4,7 @@
},
"task": {
"type": "microdroid_launcher",
- "command": "MicrodroidTestNativeLib.so",
- "args": [
- "hello",
- "microdroid"
- ]
+ "command": "MicrodroidTestNativeLib.so"
},
"export_tombstones": true
}
diff --git a/tests/testapk/assets/vm_config_apex.json b/tests/testapk/assets/vm_config_apex.json
index 0f100aa..c00787f 100644
--- a/tests/testapk/assets/vm_config_apex.json
+++ b/tests/testapk/assets/vm_config_apex.json
@@ -4,11 +4,7 @@
},
"task": {
"type": "microdroid_launcher",
- "command": "MicrodroidTestNativeLib.so",
- "args": [
- "hello",
- "microdroid"
- ]
+ "command": "MicrodroidTestNativeLib.so"
},
"apexes": [
{
diff --git a/tests/testapk/assets/vm_config_crash.json b/tests/testapk/assets/vm_config_crash.json
index 282f25c..2951fdf 100644
--- a/tests/testapk/assets/vm_config_crash.json
+++ b/tests/testapk/assets/vm_config_crash.json
@@ -4,10 +4,7 @@
},
"task": {
"type": "microdroid_launcher",
- "command": "MicrodroidTestNativeLib.so",
- "args": [
- "crash"
- ]
+ "command": "MicrodroidTestNativeCrashLib.so"
},
"export_tombstones": true
}
diff --git a/tests/testapk/assets/vm_config_crash_no_tombstone.json b/tests/testapk/assets/vm_config_crash_no_tombstone.json
index be0983d..583f87b 100644
--- a/tests/testapk/assets/vm_config_crash_no_tombstone.json
+++ b/tests/testapk/assets/vm_config_crash_no_tombstone.json
@@ -4,10 +4,7 @@
},
"task": {
"type": "microdroid_launcher",
- "command": "MicrodroidTestNativeLib.so",
- "args": [
- "crash"
- ]
+ "command": "MicrodroidTestNativeCrashLib.so"
},
"export_tombstones": false
}
diff --git a/tests/testapk/assets/vm_config_extra_apk.json b/tests/testapk/assets/vm_config_extra_apk.json
index d7d3dd7..b45e57d 100644
--- a/tests/testapk/assets/vm_config_extra_apk.json
+++ b/tests/testapk/assets/vm_config_extra_apk.json
@@ -4,11 +4,7 @@
},
"task": {
"type": "microdroid_launcher",
- "command": "MicrodroidTestNativeLib.so",
- "args": [
- "hello",
- "microdroid"
- ]
+ "command": "MicrodroidTestNativeLib.so"
},
"extra_apks": [
{
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 4b40293..51c145e 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -15,6 +15,9 @@
*/
package com.android.microdroid.test;
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
@@ -26,8 +29,6 @@
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
-import android.system.virtualmachine.VirtualMachineException;
import android.util.Log;
import com.android.compatibility.common.util.CddTest;
@@ -35,6 +36,7 @@
import com.android.microdroid.testservice.ITestService;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
@@ -53,7 +55,6 @@
import java.util.concurrent.CompletableFuture;
import co.nstant.in.cbor.CborDecoder;
-import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.model.Array;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.MajorType;
@@ -86,92 +87,50 @@
"9.17/C-1-1",
"9.17/C-2-1"
})
- public void connectToVmService() throws VirtualMachineException, InterruptedException {
- assume()
- .withMessage("SKip on 5.4 kernel. b/218303240")
- .that(KERNEL_VERSION)
- .isNotEqualTo("5.4");
+ public void connectToVmService() throws Exception {
+ assumeSupportedKernel();
- VirtualMachineConfig.Builder builder =
- mInner.newVmConfigBuilder("assets/vm_config_extra_apk.json");
- if (Build.SUPPORTED_ABIS.length > 0) {
- String primaryAbi = Build.SUPPORTED_ABIS[0];
- switch(primaryAbi) {
- case "x86_64":
- builder.memoryMib(MIN_MEM_X86_64);
- break;
- case "arm64-v8a":
- builder.memoryMib(MIN_MEM_ARM64);
- break;
- }
- }
- VirtualMachineConfig config = builder.build();
+ VirtualMachineConfig config = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(minMemoryRequired())
+ .build();
VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm_extra_apk", config);
- class TestResults {
- Exception mException;
- Integer mAddInteger;
- String mAppRunProp;
- String mSublibRunProp;
- String mExtraApkTestProp;
- }
- final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
- final CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
- final TestResults testResults = new TestResults();
- VmEventListener listener =
- new VmEventListener() {
- private void testVMService(VirtualMachine vm) {
- try {
- ITestService testService = ITestService.Stub.asInterface(
- vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
- testResults.mAddInteger = testService.addInteger(123, 456);
- testResults.mAppRunProp =
- testService.readProperty("debug.microdroid.app.run");
- testResults.mSublibRunProp =
- testService.readProperty("debug.microdroid.app.sublib.run");
- testResults.mExtraApkTestProp =
- testService.readProperty("debug.microdroid.test.extra_apk");
- } catch (Exception e) {
- testResults.mException = e;
- }
- }
-
- @Override
- public void onPayloadReady(VirtualMachine vm) {
- Log.i(TAG, "onPayloadReady");
- payloadReady.complete(true);
- testVMService(vm);
- forceStop(vm);
- }
-
- @Override
- public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
- Log.i(TAG, "onPayloadStarted");
- payloadStarted.complete(true);
- logVmOutput(TAG, new FileInputStream(stream.getFileDescriptor()),
- "Payload");
- }
- };
- listener.runToFinish(TAG, vm);
- assertThat(payloadStarted.getNow(false)).isTrue();
- assertThat(payloadReady.getNow(false)).isTrue();
+ TestResults testResults = runVmTestService(vm);
assertThat(testResults.mException).isNull();
assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
assertThat(testResults.mAppRunProp).isEqualTo("true");
assertThat(testResults.mSublibRunProp).isEqualTo("true");
+ }
+
+ @Test
+ @CddTest(requirements = {
+ "9.17/C-1-1",
+ "9.17/C-2-1"
+ })
+ public void extraApk() throws Exception {
+ assumeSupportedKernel();
+
+ VirtualMachineConfig config = mInner.newVmConfigBuilder("assets/vm_config_extra_apk.json")
+ .setMemoryMib(minMemoryRequired())
+ .build();
+ VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm_extra_apk", config);
+
+ TestResults testResults = runVmTestService(vm);
assertThat(testResults.mExtraApkTestProp).isEqualTo("PASS");
}
@Test
- public void bootFailsWhenLowMem() throws VirtualMachineException, InterruptedException {
+ public void bootFailsWhenLowMem() throws Exception {
for (int memMib : new int[]{ 10, 20, 40 }) {
- VirtualMachineConfig lowMemConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
- .memoryMib(memMib)
- .debugLevel(DebugLevel.NONE)
+ VirtualMachineConfig lowMemConfig = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setMemoryMib(memMib)
+ .setDebugLevel(DEBUG_LEVEL_NONE)
.build();
VirtualMachine vm = mInner.forceCreateNewVirtualMachine("low_mem", lowMemConfig);
final CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
- final CompletableFuture<Boolean> onDiedExecuted = new CompletableFuture<>();
+ final CompletableFuture<Boolean> onStoppedExecuted = new CompletableFuture<>();
VmEventListener listener =
new VmEventListener() {
@Override
@@ -180,14 +139,14 @@
super.onPayloadReady(vm);
}
@Override
- public void onDied(VirtualMachine vm, int reason) {
- onDiedExecuted.complete(true);
- super.onDied(vm, reason);
+ public void onStopped(VirtualMachine vm, int reason) {
+ onStoppedExecuted.complete(true);
+ super.onStopped(vm, reason);
}
};
listener.runToFinish(TAG, vm);
- // Assert that onDied() was executed but onPayloadReady() was never run
- assertThat(onDiedExecuted.getNow(false)).isTrue();
+ // Assert that onStopped() was executed but onPayloadReady() was never run
+ assertThat(onStoppedExecuted.getNow(false)).isTrue();
assertThat(onPayloadReadyExecuted.getNow(false)).isFalse();
}
}
@@ -197,15 +156,13 @@
"9.17/C-1-1",
"9.17/C-2-7"
})
- public void changingDebugLevelInvalidatesVmIdentity()
- throws VirtualMachineException, InterruptedException, IOException {
- assume()
- .withMessage("SKip on 5.4 kernel. b/218303240")
- .that(KERNEL_VERSION)
- .isNotEqualTo("5.4");
+ public void changingDebugLevelInvalidatesVmIdentity() throws Exception {
+ assumeSupportedKernel();
- VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder("assets/vm_config.json");
- VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+ VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_NONE);
+ VirtualMachineConfig normalConfig = builder.build();
mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
@@ -223,19 +180,18 @@
// Launch the same VM with different debug level. The Java API prohibits this (thankfully).
// For testing, we do that by creating a new VM with debug level, and copy the old instance
// image to the new VM instance image.
- VirtualMachineConfig debugConfig = builder.debugLevel(DebugLevel.FULL).build();
+ VirtualMachineConfig debugConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
mInner.forceCreateNewVirtualMachine("test_vm", debugConfig);
Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isFalse();
}
- private class VmCdis {
+ private static class VmCdis {
public byte[] cdiAttest;
- public byte[] cdiSeal;
+ public byte[] instanceSecret;
}
- private VmCdis launchVmAndGetCdis(String instanceName)
- throws VirtualMachineException, InterruptedException {
+ private VmCdis launchVmAndGetCdis(String instanceName) throws Exception {
VirtualMachine vm = mInner.getVirtualMachineManager().get(instanceName);
final VmCdis vmCdis = new VmCdis();
final CompletableFuture<Exception> exception = new CompletableFuture<>();
@@ -245,9 +201,9 @@
public void onPayloadReady(VirtualMachine vm) {
try {
ITestService testService = ITestService.Stub.asInterface(
- vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
+ vm.connectToVsockServer(ITestService.SERVICE_PORT));
vmCdis.cdiAttest = testService.insecurelyExposeAttestationCdi();
- vmCdis.cdiSeal = testService.insecurelyExposeSealingCdi();
+ vmCdis.instanceSecret = testService.insecurelyExposeVmInstanceSecret();
forceStop(vm);
} catch (Exception e) {
exception.complete(e);
@@ -264,15 +220,12 @@
"9.17/C-1-1",
"9.17/C-2-7"
})
- public void instancesOfSameVmHaveDifferentCdis()
- throws VirtualMachineException, InterruptedException {
- assume()
- .withMessage("SKip on 5.4 kernel. b/218303240")
- .that(KERNEL_VERSION)
- .isNotEqualTo("5.4");
+ public void instancesOfSameVmHaveDifferentCdis() throws Exception {
+ assumeSupportedKernel();
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
- .debugLevel(DebugLevel.FULL)
+ VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
.build();
mInner.forceCreateNewVirtualMachine("test_vm_a", normalConfig);
mInner.forceCreateNewVirtualMachine("test_vm_b", normalConfig);
@@ -281,10 +234,9 @@
assertThat(vm_a_cdis.cdiAttest).isNotNull();
assertThat(vm_b_cdis.cdiAttest).isNotNull();
assertThat(vm_a_cdis.cdiAttest).isNotEqualTo(vm_b_cdis.cdiAttest);
- assertThat(vm_a_cdis.cdiSeal).isNotNull();
- assertThat(vm_b_cdis.cdiSeal).isNotNull();
- assertThat(vm_a_cdis.cdiSeal).isNotEqualTo(vm_b_cdis.cdiSeal);
- assertThat(vm_a_cdis.cdiAttest).isNotEqualTo(vm_b_cdis.cdiSeal);
+ assertThat(vm_a_cdis.instanceSecret).isNotNull();
+ assertThat(vm_b_cdis.instanceSecret).isNotNull();
+ assertThat(vm_a_cdis.instanceSecret).isNotEqualTo(vm_b_cdis.instanceSecret);
}
@Test
@@ -292,24 +244,21 @@
"9.17/C-1-1",
"9.17/C-2-7"
})
- public void sameInstanceKeepsSameCdis()
- throws VirtualMachineException, InterruptedException {
- assume()
- .withMessage("SKip on 5.4 kernel. b/218303240")
- .that(KERNEL_VERSION)
- .isNotEqualTo("5.4");
+ public void sameInstanceKeepsSameCdis() throws Exception {
+ assumeSupportedKernel();
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
- .debugLevel(DebugLevel.FULL)
+ VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
.build();
mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
VmCdis first_boot_cdis = launchVmAndGetCdis("test_vm");
VmCdis second_boot_cdis = launchVmAndGetCdis("test_vm");
// The attestation CDI isn't specified to be stable, though it might be
- assertThat(first_boot_cdis.cdiSeal).isNotNull();
- assertThat(second_boot_cdis.cdiSeal).isNotNull();
- assertThat(first_boot_cdis.cdiSeal).isEqualTo(second_boot_cdis.cdiSeal);
+ assertThat(first_boot_cdis.instanceSecret).isNotNull();
+ assertThat(second_boot_cdis.instanceSecret).isNotNull();
+ assertThat(first_boot_cdis.instanceSecret).isEqualTo(second_boot_cdis.instanceSecret);
}
@Test
@@ -317,18 +266,14 @@
"9.17/C-1-1",
"9.17/C-2-7"
})
- public void bccIsSuperficiallyWellFormed()
- throws VirtualMachineException, InterruptedException, CborException {
- assume()
- .withMessage("SKip on 5.4 kernel. b/218303240")
- .that(KERNEL_VERSION)
- .isNotEqualTo("5.4");
+ public void bccIsSuperficiallyWellFormed() throws Exception {
+ assumeSupportedKernel();
- VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
- .debugLevel(DebugLevel.FULL)
+ VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
.build();
VirtualMachine vm = mInner.forceCreateNewVirtualMachine("bcc_vm", normalConfig);
- final VmCdis vmCdis = new VmCdis();
final CompletableFuture<byte[]> bcc = new CompletableFuture<>();
final CompletableFuture<Exception> exception = new CompletableFuture<>();
VmEventListener listener =
@@ -337,7 +282,7 @@
public void onPayloadReady(VirtualMachine vm) {
try {
ITestService testService = ITestService.Stub.asInterface(
- vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
+ vm.connectToVsockServer(ITestService.SERVICE_PORT));
bcc.complete(testService.getBcc());
forceStop(vm);
} catch (Exception e) {
@@ -398,10 +343,10 @@
file.writeByte(b ^ 1);
}
- private RandomAccessFile prepareInstanceImage(String vmName)
- throws VirtualMachineException, InterruptedException, IOException {
- VirtualMachineConfig config = mInner.newVmConfigBuilder("assets/vm_config.json")
- .debugLevel(DebugLevel.FULL)
+ private RandomAccessFile prepareInstanceImage(String vmName) throws Exception {
+ VirtualMachineConfig config = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_FULL)
.build();
mInner.forceCreateNewVirtualMachine(vmName, config);
@@ -413,8 +358,7 @@
return new RandomAccessFile(instanceImgPath, "rw");
}
- private void assertThatPartitionIsMissing(UUID partitionUuid)
- throws VirtualMachineException, InterruptedException, IOException {
+ private void assertThatPartitionIsMissing(UUID partitionUuid) throws Exception {
RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
assertThat(findPartitionDataOffset(instanceFile, partitionUuid).isPresent())
.isFalse();
@@ -422,7 +366,7 @@
// Flips a bit of given partition, and then see if boot fails.
private void assertThatBootFailsAfterCompromisingPartition(UUID partitionUuid)
- throws VirtualMachineException, InterruptedException, IOException {
+ throws Exception {
RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
OptionalLong offset = findPartitionDataOffset(instanceFile, partitionUuid);
assertThat(offset.isPresent()).isTrue();
@@ -433,7 +377,7 @@
assertThat(result.payloadStarted).isFalse();
// This failure should shut the VM down immediately and shouldn't trigger a hangup.
- assertThat(result.deathReason).isNotEqualTo(VirtualMachineCallback.DEATH_REASON_HANGUP);
+ assertThat(result.deathReason).isNotEqualTo(VirtualMachineCallback.STOP_REASON_HANGUP);
}
@Test
@@ -441,18 +385,17 @@
"9.17/C-1-1",
"9.17/C-2-7"
})
- public void bootFailsWhenMicrodroidDataIsCompromised()
- throws VirtualMachineException, InterruptedException, IOException {
+ public void bootFailsWhenMicrodroidDataIsCompromised() throws Exception {
assertThatBootFailsAfterCompromisingPartition(MICRODROID_PARTITION_UUID);
}
@Test
+ @Ignore("b/249723852")
@CddTest(requirements = {
"9.17/C-1-1",
"9.17/C-2-7"
})
- public void bootFailsWhenPvmFwDataIsCompromised()
- throws VirtualMachineException, InterruptedException, IOException {
+ public void bootFailsWhenPvmFwDataIsCompromised() throws Exception {
if (mProtectedVm) {
assertThatBootFailsAfterCompromisingPartition(PVM_FW_PARTITION_UUID);
} else {
@@ -462,33 +405,105 @@
}
@Test
- public void bootFailsWhenConfigIsInvalid()
- throws VirtualMachineException, InterruptedException, IOException {
+ public void bootFailsWhenConfigIsInvalid() throws Exception {
VirtualMachineConfig.Builder builder =
mInner.newVmConfigBuilder("assets/vm_config_no_task.json");
- VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.FULL).build();
+ VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
mInner.forceCreateNewVirtualMachine("test_vm_invalid_config", normalConfig);
BootResult bootResult = tryBootVm(TAG, "test_vm_invalid_config");
assertThat(bootResult.payloadStarted).isFalse();
assertThat(bootResult.deathReason).isEqualTo(
- VirtualMachineCallback.DEATH_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
+ VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
}
@Test
- public void sameInstancesShareTheSameVmObject()
- throws VirtualMachineException, InterruptedException, IOException {
- VirtualMachineConfig.Builder builder =
- mInner.newVmConfigBuilder("assets/vm_config.json");
- VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+ public void sameInstancesShareTheSameVmObject() throws Exception {
+ VirtualMachineConfig config = mInner.newVmConfigBuilder()
+ .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+ .setDebugLevel(DEBUG_LEVEL_NONE)
+ .build();
+
+ VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm", config);
VirtualMachine vm2 = mInner.getVirtualMachineManager().get("test_vm");
assertThat(vm).isEqualTo(vm2);
- VirtualMachine newVm = mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
+ VirtualMachine newVm = mInner.forceCreateNewVirtualMachine("test_vm", config);
VirtualMachine newVm2 = mInner.getVirtualMachineManager().get("test_vm");
assertThat(newVm).isEqualTo(newVm2);
assertThat(vm).isNotEqualTo(newVm);
}
+
+ private int minMemoryRequired() {
+ if (Build.SUPPORTED_ABIS.length > 0) {
+ String primaryAbi = Build.SUPPORTED_ABIS[0];
+ switch (primaryAbi) {
+ case "x86_64":
+ return MIN_MEM_X86_64;
+ case "arm64-v8a":
+ return MIN_MEM_ARM64;
+ }
+ }
+ return 0;
+ }
+
+ private void assumeSupportedKernel() {
+ assume()
+ .withMessage("Skip on 5.4 kernel. b/218303240")
+ .that(KERNEL_VERSION)
+ .isNotEqualTo("5.4");
+ }
+
+ static class TestResults {
+ Exception mException;
+ Integer mAddInteger;
+ String mAppRunProp;
+ String mSublibRunProp;
+ String mExtraApkTestProp;
+ }
+
+ private TestResults runVmTestService(VirtualMachine vm) throws Exception {
+ CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
+ CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
+ TestResults testResults = new TestResults();
+ VmEventListener listener =
+ new VmEventListener() {
+ private void testVMService(VirtualMachine vm) {
+ try {
+ ITestService testService = ITestService.Stub.asInterface(
+ vm.connectToVsockServer(ITestService.SERVICE_PORT));
+ testResults.mAddInteger = testService.addInteger(123, 456);
+ testResults.mAppRunProp =
+ testService.readProperty("debug.microdroid.app.run");
+ testResults.mSublibRunProp =
+ testService.readProperty("debug.microdroid.app.sublib.run");
+ testResults.mExtraApkTestProp =
+ testService.readProperty("debug.microdroid.test.extra_apk");
+ } catch (Exception e) {
+ testResults.mException = e;
+ }
+ }
+
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ Log.i(TAG, "onPayloadReady");
+ payloadReady.complete(true);
+ testVMService(vm);
+ forceStop(vm);
+ }
+
+ @Override
+ public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+ Log.i(TAG, "onPayloadStarted");
+ payloadStarted.complete(true);
+ logVmOutput(TAG, new FileInputStream(stream.getFileDescriptor()),
+ "Payload");
+ }
+ };
+ listener.runToFinish(TAG, vm);
+ assertThat(payloadStarted.getNow(false)).isTrue();
+ assertThat(payloadReady.getNow(false)).isTrue();
+ return testResults;
+ }
}
diff --git a/tests/testapk/src/native/crashbinary.cpp b/tests/testapk/src/native/crashbinary.cpp
new file mode 100644
index 0000000..9f80fd0
--- /dev/null
+++ b/tests/testapk/src/native/crashbinary.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// A VM payload that crashes as soon as it starts, to allow us to exercise that error path.
+extern "C" int android_native_main(int /* argc */, char* /* argv */[]) {
+ printf("test crash!!!!\n");
+ abort();
+}
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index fd8e776..5d6e901 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#include <aidl/android/security/dice/IDiceNode.h>
#include <aidl/com/android/microdroid/testservice/BnTestService.h>
#include <android-base/file.h>
#include <android-base/properties.h>
@@ -28,15 +27,11 @@
#include <sys/ioctl.h>
#include <sys/system_properties.h>
#include <unistd.h>
+#include <vm_payload.h>
#include <binder_rpc_unstable.hpp>
#include <string>
-#include "vm_payload.h"
-
-using aidl::android::hardware::security::dice::BccHandover;
-using aidl::android::security::dice::IDiceNode;
-
using android::base::ErrnoError;
using android::base::Error;
using android::base::Result;
@@ -78,54 +73,36 @@
return ndk::ScopedAStatus::ok();
}
- ndk::ScopedAStatus insecurelyExposeSealingCdi(std::vector<uint8_t>* out) override {
- ndk::SpAIBinder binder(AServiceManager_getService("android.security.dice.IDiceNode"));
- auto service = IDiceNode::fromBinder(binder);
- if (service == nullptr) {
+ ndk::ScopedAStatus insecurelyExposeVmInstanceSecret(std::vector<uint8_t>* out) override {
+ const uint8_t identifier[] = {1, 2, 3, 4};
+ uint8_t secret[32];
+ if (!get_vm_instance_secret(identifier, sizeof(identifier), secret, sizeof(secret))) {
return ndk::ScopedAStatus::
- fromServiceSpecificErrorWithMessage(0, "Failed to find diced");
+ fromServiceSpecificErrorWithMessage(0, "Failed to VM instance secret");
}
- BccHandover handover;
- auto deriveStatus = service->derive({}, &handover);
- if (!deriveStatus.isOk()) {
- return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(0,
- "Failed call diced");
- }
- *out = {handover.cdiSeal.begin(), handover.cdiSeal.end()};
+ *out = {secret, secret + sizeof(secret)};
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus insecurelyExposeAttestationCdi(std::vector<uint8_t>* out) override {
- ndk::SpAIBinder binder(AServiceManager_getService("android.security.dice.IDiceNode"));
- auto service = IDiceNode::fromBinder(binder);
- if (service == nullptr) {
+ uint8_t cdi[64];
+ size_t cdi_size = get_dice_attestation_cdi(cdi, sizeof(cdi));
+ if (cdi_size == 0) {
return ndk::ScopedAStatus::
- fromServiceSpecificErrorWithMessage(0, "Failed to find diced");
+ fromServiceSpecificErrorWithMessage(0, "Failed to get attestation cdi");
}
- BccHandover handover;
- auto deriveStatus = service->derive({}, &handover);
- if (!deriveStatus.isOk()) {
- return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(0,
- "Failed call diced");
- }
- *out = {handover.cdiAttest.begin(), handover.cdiAttest.end()};
+ *out = {cdi, cdi + cdi_size};
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus getBcc(std::vector<uint8_t>* out) override {
- ndk::SpAIBinder binder(AServiceManager_getService("android.security.dice.IDiceNode"));
- auto service = IDiceNode::fromBinder(binder);
- if (service == nullptr) {
+ uint8_t bcc[4096];
+ size_t bcc_size = get_dice_attestation_chain(bcc, sizeof(bcc));
+ if (bcc_size == 0) {
return ndk::ScopedAStatus::
- fromServiceSpecificErrorWithMessage(0, "Failed to find diced");
+ fromServiceSpecificErrorWithMessage(0, "Failed to get attestation chain");
}
- BccHandover handover;
- auto deriveStatus = service->derive({}, &handover);
- if (!deriveStatus.isOk()) {
- return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(0,
- "Failed call diced");
- }
- *out = {handover.bcc.data.begin(), handover.bcc.data.end()};
+ *out = {bcc, bcc + bcc_size};
return ndk::ScopedAStatus::ok();
}
};
@@ -168,11 +145,6 @@
setvbuf(stdout, nullptr, _IONBF, 0);
setvbuf(stderr, nullptr, _IONBF, 0);
- if (strcmp(argv[1], "crash") == 0) {
- printf("test crash!!!!\n");
- abort();
- }
-
printf("Hello Microdroid ");
for (int i = 0; i < argc; i++) {
printf("%s", argv[i]);
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 2f4f731..0551229 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -27,6 +27,7 @@
"libandroid_logger",
"libanyhow",
"libapkverify",
+ "libbase_rust",
"libbinder_rs",
"libcommand_fds",
"libdisk",
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 563fab0..973bfa3 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -63,7 +63,7 @@
use std::sync::{Arc, Mutex, Weak};
use tombstoned_client::{TombstonedConnection, DebuggerdDumpType};
use vmconfig::VmConfig;
-use vsock::{SockAddr, VsockListener, VsockStream};
+use vsock::{VsockListener, VsockStream};
use zip::ZipArchive;
pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
@@ -268,7 +268,7 @@
}
fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
- if let Ok(SockAddr::Vsock(addr)) = stream.peer_addr() {
+ if let Ok(addr) = stream.peer_addr() {
info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
}
let tb_connection =
@@ -499,7 +499,7 @@
}
Ok(s) => s,
};
- if let Ok(SockAddr::Vsock(addr)) = stream.peer_addr() {
+ if let Ok(addr) = stream.peer_addr() {
let cid = addr.cid();
let port = addr.port();
info!("payload stream connected from cid={}, port={}", cid, port);
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 72af431..54cdeb6 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -41,6 +41,9 @@
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
use tombstoned_client::{TombstonedConnection, DebuggerdDumpType};
+/// external/crosvm
+use base::UnixSeqpacketListener;
+
const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
/// Version of the platform that crosvm currently implements. The format follows SemVer. This
@@ -141,7 +144,8 @@
let (failure_pipe_read, failure_pipe_write) = create_pipe()?;
// If this fails and returns an error, `self` will be left in the `Failed` state.
- let child = Arc::new(run_vm(config, failure_pipe_write)?);
+ let child =
+ Arc::new(run_vm(config, &instance.temporary_directory, failure_pipe_write)?);
let child_clone = child.clone();
let instance_clone = instance.clone();
@@ -435,7 +439,11 @@
}
/// Starts an instance of `crosvm` to manage a new VM.
-fn run_vm(config: CrosvmConfig, failure_pipe_write: File) -> Result<SharedChild, Error> {
+fn run_vm(
+ config: CrosvmConfig,
+ temporary_directory: &Path,
+ failure_pipe_write: File,
+) -> Result<SharedChild, Error> {
validate_config(&config)?;
let mut command = Command::new(CROSVM_PATH);
@@ -525,6 +533,11 @@
command.arg(add_preserved_fd(&mut preserved_fds, kernel));
}
+ let control_server_socket =
+ UnixSeqpacketListener::bind(temporary_directory.join("crosvm.sock"))
+ .context("failed to create control server")?;
+ command.arg("--socket").arg(add_preserved_fd(&mut preserved_fds, &control_server_socket));
+
debug!("Preserving FDs {:?}", preserved_fds);
command.preserved_fds(preserved_fds);
@@ -557,7 +570,7 @@
/// Adds the file descriptor for `file` to `preserved_fds`, and returns a string of the form
/// "/proc/self/fd/N" where N is the file descriptor.
-fn add_preserved_fd(preserved_fds: &mut Vec<RawFd>, file: &File) -> String {
+fn add_preserved_fd(preserved_fds: &mut Vec<RawFd>, file: &dyn AsRawFd) -> String {
let fd = file.as_raw_fd();
preserved_fds.push(fd);
format!("/proc/self/fd/{}", fd)
diff --git a/vmbase/src/console.rs b/vmbase/src/console.rs
index b52d924..fabea91 100644
--- a/vmbase/src/console.rs
+++ b/vmbase/src/console.rs
@@ -18,7 +18,8 @@
use core::fmt::{write, Arguments, Write};
use spin::mutex::SpinMutex;
-const BASE_ADDRESS: usize = 0x3f8;
+/// Base memory-mapped address of the primary UART device.
+pub const BASE_ADDRESS: usize = 0x3f8;
static CONSOLE: SpinMutex<Option<Uart>> = SpinMutex::new(None);
diff --git a/zipfuse/Android.bp b/zipfuse/Android.bp
index 3aba94a..1bdc5fe 100644
--- a/zipfuse/Android.bp
+++ b/zipfuse/Android.bp
@@ -13,9 +13,10 @@
"libclap",
"libfuse_rust",
"liblibc",
- "libzip",
- "libscopeguard",
"liblog_rust",
+ "librustutils",
+ "libscopeguard",
+ "libzip",
],
// libfuse_rust, etc don't support 32-bit targets
multilib: {
diff --git a/zipfuse/src/main.rs b/zipfuse/src/main.rs
index 8400a72..9411759 100644
--- a/zipfuse/src/main.rs
+++ b/zipfuse/src/main.rs
@@ -20,10 +20,11 @@
mod inode;
-use anyhow::Result;
+use anyhow::{Context as AnyhowContext, Result};
use clap::{App, Arg};
use fuse::filesystem::*;
use fuse::mount::*;
+use rustutils::system_properties;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::ffi::{CStr, CString};
@@ -52,6 +53,12 @@
.takes_value(false)
.help("Disallow the execution of binary files"),
)
+ .arg(
+ Arg::with_name("readyprop")
+ .short('p')
+ .takes_value(true)
+ .help("Specify a property to be set when mount is ready"),
+ )
.arg(Arg::with_name("ZIPFILE").required(true))
.arg(Arg::with_name("MOUNTPOINT").required(true))
.get_matches();
@@ -60,7 +67,8 @@
let mount_point = matches.value_of("MOUNTPOINT").unwrap().as_ref();
let options = matches.value_of("options");
let noexec = matches.is_present("noexec");
- run_fuse(zip_file, mount_point, options, noexec)?;
+ let ready_prop = matches.value_of("readyprop");
+ run_fuse(zip_file, mount_point, options, noexec, ready_prop)?;
Ok(())
}
@@ -70,6 +78,7 @@
mount_point: &Path,
extra_options: Option<&str>,
noexec: bool,
+ ready_prop: Option<&str>,
) -> Result<()> {
const MAX_READ: u32 = 1 << 20; // TODO(jiyong): tune this
const MAX_WRITE: u32 = 1 << 13; // This is a read-only filesystem
@@ -94,6 +103,11 @@
}
fuse::mount(mount_point, "zipfuse", mount_flags, &mount_options)?;
+
+ if let Some(property_name) = ready_prop {
+ system_properties::write(property_name, "1").context("Failed to set readyprop")?;
+ }
+
let mut config = fuse::FuseConfig::new();
config.dev_fuse(dev_fuse).max_write(MAX_WRITE).max_read(MAX_READ);
Ok(config.enter_message_loop(ZipFuse::new(zip_file)?)?)