Merge "Remove old key management"
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index 0f60384..85f86d0 100644
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "8160972"
+ build_id: "8194384"
target: "u-boot_pvmfw"
source_file: "pvmfw.img"
}
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index dcacb0f..63a6fd6 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -112,10 +112,8 @@
.context("Failed to create system log file")?;
let console_fd = ParcelFileDescriptor::new(console_fd);
let log_fd = ParcelFileDescriptor::new(log_fd);
- // Full debug is not available in a protected VM
- let debug_level = if protected_vm { DebugLevel::APP_ONLY } else { DebugLevel::FULL };
- info!("Debug mode is {:?}", debug_level);
- (Some(console_fd), Some(log_fd), debug_level)
+ info!("Running in debug mode");
+ (Some(console_fd), Some(log_fd), DebugLevel::FULL)
} else {
(None, None, DebugLevel::NONE)
};
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index c48c4fe..14ff111 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -223,7 +223,7 @@
}
/** Loads a virtual machine that is already created before. */
- /* package */ static @NonNull VirtualMachine load(
+ /* package */ static @Nullable VirtualMachine load(
@NonNull Context context, @NonNull String name) throws VirtualMachineException {
File configFilePath = getConfigFilePath(context, name);
VirtualMachineConfig config;
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 3654886..51fa51f 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -17,6 +17,7 @@
package android.system.virtualmachine;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import java.lang.ref.WeakReference;
@@ -72,7 +73,7 @@
* Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
* such virtual machine.
*/
- public @NonNull VirtualMachine get(@NonNull String name) throws VirtualMachineException {
+ public @Nullable VirtualMachine get(@NonNull String name) throws VirtualMachineException {
return VirtualMachine.load(mContext, name);
}
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index 27c2d2b..395513f 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 1e96246..10b90d3 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -86,7 +86,7 @@
// Run MicrodroidTests#connectToVmService test, which should fail
final DeviceTestRunOptions options = new DeviceTestRunOptions(PACKAGE_NAME)
.setTestClassName(PACKAGE_NAME + ".MicrodroidTests")
- .setTestMethodName("connectToVmService")
+ .setTestMethodName("connectToVmService[protectedVm=false]")
.setCheckResults(false);
assertFalse(runDeviceTests(options));
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 63fdca1..30f5933 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -18,19 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.junit.Assume.assumeNoException;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import android.content.Context;
import android.os.Build;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
+import android.sysprop.HypervisorProperties;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
@@ -49,19 +45,20 @@
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
+import java.util.OptionalLong;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-@RunWith(JUnit4.class)
+@RunWith(Parameterized.class)
public class MicrodroidTests {
@Rule public Timeout globalTimeout = Timeout.seconds(300);
@@ -73,6 +70,14 @@
public VirtualMachine mVm;
}
+ @Parameterized.Parameters(name = "protectedVm={0}")
+ public static Object[] protectedVmConfigs() {
+ return new Object[] { false, true };
+ }
+
+ @Parameterized.Parameter
+ public boolean mProtectedVm;
+
private boolean mPkvmSupported = false;
private Inner mInner;
@@ -88,6 +93,17 @@
assumeNoException(e);
return;
}
+ if (mProtectedVm) {
+ assume()
+ .withMessage("Skip where protected VMs aren't support")
+ .that(HypervisorProperties.hypervisor_protected_vm_supported().orElse(false))
+ .isTrue();
+ } else {
+ assume()
+ .withMessage("Skip where VMs aren't support")
+ .that(HypervisorProperties.hypervisor_vm_supported().orElse(false))
+ .isTrue();
+ }
mInner = new Inner();
mInner.mContext = ApplicationProvider.getApplicationContext();
mInner.mVmm = VirtualMachineManager.getInstance(mInner.mContext);
@@ -98,6 +114,9 @@
if (!mPkvmSupported) {
return;
}
+ if (mInner == null) {
+ return;
+ }
if (mInner.mVm == null) {
return;
}
@@ -142,7 +161,7 @@
}
}
- private static final int MIN_MEM_ARM64 = 135;
+ private static final int MIN_MEM_ARM64 = 145;
private static final int MIN_MEM_X86_64 = 196;
@Test
@@ -153,8 +172,8 @@
.isNotEqualTo("5.4");
VirtualMachineConfig.Builder builder =
- new VirtualMachineConfig.Builder(mInner.mContext,
- "assets/vm_config_extra_apk.json");
+ new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config_extra_apk.json")
+ .protectedVm(mProtectedVm);
if (Build.SUPPORTED_ABIS.length > 0) {
String primaryAbi = Build.SUPPORTED_ABIS[0];
switch(primaryAbi) {
@@ -169,58 +188,55 @@
VirtualMachineConfig config = builder.build();
mInner.mVm = mInner.mVmm.getOrCreate("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 boolean mPayloadReadyCalled = false;
- private boolean mPayloadStartedCalled = false;
-
- private void testVMService(Future<IBinder> service) {
+ private void testVMService(VirtualMachine vm) {
try {
- IBinder binder = service.get();
-
- ITestService testService = ITestService.Stub.asInterface(binder);
- assertEquals(
- testService.addInteger(123, 456),
- 123 + 456);
- assertEquals(
- testService.readProperty("debug.microdroid.app.run"),
- "true");
- assertEquals(
- testService.readProperty("debug.microdroid.app.sublib.run"),
- "true");
- assertEquals(
- testService.readProperty("debug.microdroid.test.extra_apk"),
- "PASS");
+ 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) {
- fail("Exception while testing service: " + e.toString());
+ testResults.mException = e;
}
}
@Override
public void onPayloadReady(VirtualMachine vm) {
- mPayloadReadyCalled = true;
- try {
- testVMService(vm.connectToVsockServer(ITestService.SERVICE_PORT));
- } catch (Exception e) {
- fail("Exception while connecting to service: " + e.toString());
- }
-
+ payloadReady.complete(true);
+ testVMService(vm);
forceStop(vm);
}
@Override
public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
- mPayloadStartedCalled = true;
- }
-
- @Override
- public void onDied(VirtualMachine vm, @DeathReason int reason) {
- assertTrue(mPayloadReadyCalled);
- assertTrue(mPayloadStartedCalled);
- super.onDied(vm, reason);
+ payloadStarted.complete(true);
}
};
listener.runToFinish(mInner.mVm);
+ assertThat(payloadStarted.getNow(false)).isTrue();
+ assertThat(payloadReady.getNow(false)).isTrue();
+ assertThat(testResults.mException).isNull();
+ assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
+ assertThat(testResults.mAppRunProp).isEqualTo("true");
+ assertThat(testResults.mSublibRunProp).isEqualTo("true");
+ assertThat(testResults.mExtraApkTestProp).isEqualTo("PASS");
}
@Test
@@ -237,7 +253,8 @@
.isNotEqualTo("5.4");
VirtualMachineConfig.Builder builder =
- new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
+ new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json")
+ .protectedVm(mProtectedVm);
VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
mInner.mVm = mInner.mVmm.getOrCreate("test_vm", normalConfig);
VmEventListener listener =
@@ -260,40 +277,28 @@
Files.copy(newVmConfig.toPath(), oldVmConfig.toPath(), REPLACE_EXISTING);
newVm.delete();
mInner.mVm = mInner.mVmm.get("test_vm"); // re-load with the copied-in config file.
+ final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
listener =
new VmEventListener() {
- private boolean mPayloadStarted = false;
- private boolean mErrorOccurred = false;
-
@Override
public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
- mPayloadStarted = true;
+ payloadStarted.complete(true);
forceStop(vm);
}
-
- @Override
- public void onError(VirtualMachine vm, int errorCode, String message) {
- mErrorOccurred = true;
- forceStop(vm);
- }
-
- @Override
- public void onDied(VirtualMachine vm, @DeathReason int reason) {
- assertFalse(mPayloadStarted);
- assertTrue(mErrorOccurred);
- super.onDied(vm, reason);
- }
};
listener.runToFinish(mInner.mVm);
+ assertThat(payloadStarted.getNow(false)).isFalse();
}
private byte[] launchVmAndGetSecret(String instanceName)
throws VirtualMachineException, InterruptedException {
VirtualMachineConfig.Builder builder =
- new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
+ new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json")
+ .protectedVm(mProtectedVm);
VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
mInner.mVm = mInner.mVmm.getOrCreate(instanceName, normalConfig);
final CompletableFuture<byte[]> secret = new CompletableFuture<>();
+ final CompletableFuture<Exception> exception = new CompletableFuture<>();
VmEventListener listener =
new VmEventListener() {
@Override
@@ -302,13 +307,14 @@
ITestService testService = ITestService.Stub.asInterface(
vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
secret.complete(testService.insecurelyExposeSecret());
+ forceStop(vm);
} catch (Exception e) {
- fail("Exception while connecting to service: " + e.toString());
+ exception.complete(e);
}
- forceStop(vm);
}
};
listener.runToFinish(mInner.mVm);
+ assertThat(exception.getNow(null)).isNull();
return secret.getNow(null);
}
@@ -352,6 +358,35 @@
assertThat(vm_secret_first_boot).isEqualTo(vm_secret_second_boot);
}
+ private static final UUID MICRODROID_PARTITION_UUID =
+ UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75");
+ private static final long BLOCK_SIZE = 512;
+
+ // Find the starting offset which holds the data of a partition having UUID.
+ // This is a kind of hack; rather than parsing QCOW2 we exploit the fact that the cluster size
+ // is normally greater than 512. It implies that the partition data should exist at a block
+ // which follows the header block
+ private OptionalLong findPartitionDataOffset(RandomAccessFile file, UUID uuid)
+ throws IOException {
+ // For each 512-byte block in file, check header
+ long fileSize = file.length();
+
+ for (long idx = 0; idx + BLOCK_SIZE < fileSize; idx += BLOCK_SIZE) {
+ file.seek(idx);
+ long high = file.readLong();
+ long low = file.readLong();
+ if (uuid.equals(new UUID(high, low))) return OptionalLong.of(idx + BLOCK_SIZE);
+ }
+ return OptionalLong.empty();
+ }
+
+ private void flipBit(RandomAccessFile file, long offset) throws IOException {
+ file.seek(offset);
+ int b = file.readByte();
+ file.seek(offset);
+ file.writeByte(b ^ 1);
+ }
+
@Test
public void bootFailsWhenInstanceDiskIsCompromised()
throws VirtualMachineException, InterruptedException, IOException {
@@ -361,6 +396,7 @@
VirtualMachineConfig config =
new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json")
+ .protectedVm(mProtectedVm)
.debugLevel(DebugLevel.NONE)
.build();
@@ -370,23 +406,17 @@
mInner.mVm = mInner.mVmm.getOrCreate("test_vm_integrity", config);
+ final CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
VmEventListener listener =
new VmEventListener() {
- private boolean mPayloadReadyCalled = false;
-
@Override
public void onPayloadReady(VirtualMachine vm) {
- mPayloadReadyCalled = true;
+ payloadReady.complete(true);
forceStop(vm);
}
-
- @Override
- public void onDied(VirtualMachine vm, @DeathReason int reason) {
- assertTrue(mPayloadReadyCalled);
- super.onDied(vm, reason);
- }
};
listener.runToFinish(mInner.mVm);
+ assertThat(payloadReady.getNow(false)).isTrue();
// Launch the same VM after flipping a bit of the instance image.
// Flip actual data, as flipping trivial bits like the magic string isn't interesting.
@@ -395,40 +425,24 @@
File instanceImgPath = new File(vmDir, "instance.img");
RandomAccessFile instanceFile = new RandomAccessFile(instanceImgPath, "rw");
- // microdroid data partition starts at 0x60200, actual data at 0x60400, based on experiment
- // TODO: parse image file (QEMU qcow2) correctly?
- long headerOffset = 0x60400;
- instanceFile.seek(headerOffset);
- int b = instanceFile.readByte();
- instanceFile.seek(headerOffset);
- instanceFile.writeByte(b ^ 1);
- instanceFile.close();
+ // microdroid data partition must exist.
+ OptionalLong microdroidPartitionOffset =
+ findPartitionDataOffset(instanceFile, MICRODROID_PARTITION_UUID);
+ assertThat(microdroidPartitionOffset.isPresent()).isTrue();
+ flipBit(instanceFile, microdroidPartitionOffset.getAsLong());
mInner.mVm = mInner.mVmm.get("test_vm_integrity"); // re-load the vm with new instance disk
+ final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
listener =
new VmEventListener() {
- private boolean mPayloadStarted = false;
- private boolean mErrorOccurred = false;
-
@Override
public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
- mPayloadStarted = true;
+ payloadStarted.complete(true);
forceStop(vm);
}
-
- @Override
- public void onError(VirtualMachine vm, int errorCode, String message) {
- mErrorOccurred = true;
- forceStop(vm);
- }
-
- @Override
- public void onDied(VirtualMachine vm, @DeathReason int reason) {
- assertFalse(mPayloadStarted);
- assertTrue(mErrorOccurred);
- super.onDied(vm, reason);
- }
};
listener.runToFinish(mInner.mVm);
+ assertThat(payloadStarted.getNow(false)).isFalse();
+ flipBit(instanceFile, microdroidPartitionOffset.getAsLong());
}
}