Merge "Add tests to ensure instance.img integrity"
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 061e8cf..0587299 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -52,6 +52,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.RandomAccessFile;
 import java.nio.file.Files;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
@@ -345,4 +346,82 @@
         assertThat(vm_secret_second_boot).isNotNull();
         assertThat(vm_secret_first_boot).isEqualTo(vm_secret_second_boot);
     }
+
+    @Test
+    public void bootFailsWhenInstanceDiskIsCompromised()
+            throws VirtualMachineException, InterruptedException, IOException {
+        assume().withMessage("Skip on Cuttlefish. b/195765441")
+                .that(android.os.Build.DEVICE)
+                .isNotEqualTo("vsoc_x86_64");
+
+        VirtualMachineConfig config =
+                new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json")
+                        .debugLevel(DebugLevel.NONE)
+                        .build();
+
+        // Remove any existing VM so we can start from scratch
+        VirtualMachine oldVm = mInner.mVmm.getOrCreate("test_vm_integrity", config);
+        oldVm.delete();
+
+        mInner.mVm = mInner.mVmm.getOrCreate("test_vm_integrity", config);
+
+        VmEventListener listener =
+                new VmEventListener() {
+                    private boolean mPayloadReadyCalled = false;
+
+                    @Override
+                    public void onPayloadReady(VirtualMachine vm) {
+                        mPayloadReadyCalled = true;
+                        forceStop(vm);
+                    }
+
+                    @Override
+                    public void onDied(VirtualMachine vm, @DeathReason int reason) {
+                        assertTrue(mPayloadReadyCalled);
+                    }
+                };
+        listener.runToFinish(mInner.mVm);
+
+        // 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.
+        File vmRoot = new File(mInner.mContext.getFilesDir(), "vm");
+        File vmDir = new File(vmRoot, "test_vm_integrity");
+        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();
+
+        mInner.mVm = mInner.mVmm.get("test_vm_integrity"); // re-load the vm with new instance disk
+        listener =
+                new VmEventListener() {
+                    private boolean mPayloadStarted = false;
+                    private boolean mErrorOccurred = false;
+
+                    @Override
+                    public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+                        mPayloadStarted = 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);
+                    }
+                };
+        listener.runToFinish(mInner.mVm);
+    }
 }