Merge "Revert "Switch to Microdroid kernel""
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 9c9bf92..bfd5ccf 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -43,6 +43,35 @@
 fastboot reboot
 ```
 
+Due to a bug in Android 13 for these devices, pKVM may stop working after an
+[OTA update](https://source.android.com/devices/tech/ota). To prevent this, it
+is necessary to manually replicate the `pvmfw` partition across A/B slots:
+
+```shell
+adb root
+SLOT=$(adb shell getprop ro.boot.slot_suffix)
+adb pull /dev/block/by-name/pvmfw${SLOT} pvmfw.img
+adb reboot bootloader
+fastboot --slot other flash pvmfw pvmfw.img
+fastboot reboot
+```
+
+Otherwise, if an OTA has already made pKVM unusable, the working partition
+should be copied over from the "other" slot:
+
+```shell
+adb pull $(adb shell ls "/dev/block/by-name/pvmfw!(${SLOT})") pvmfw.img
+adb reboot bootloader
+fastboot flash pvmfw pvmfw.img
+fastboot reboot
+```
+
+Finally, if the `pvmfw` partition has been corrupted, both slots may be flashed
+using the [`pvmfw.img` pre-built](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/pvmfw/pvmfw.img)
+as long as the bootloader remains unlocked. Otherwise, a fresh install of
+Android 13 followed by the manual steps above for flashing the `other` slot
+should be used as a last resort.
+
 ## Running demo app
 
 The instruction is [here](../../demo/README.md).
diff --git a/tests/testapk/assets/vm_config_no_task.json b/tests/testapk/assets/vm_config_no_task.json
new file mode 100644
index 0000000..3162bd0
--- /dev/null
+++ b/tests/testapk/assets/vm_config_no_task.json
@@ -0,0 +1,6 @@
+{
+  "os": {
+    "name": "microdroid"
+  },
+  "export_tombstones": true
+}
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 5c48a41..59f9d17 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -27,6 +27,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemProperties;
 import android.sysprop.HypervisorProperties;
+import android.system.virtualizationservice.DeathReason;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
@@ -479,10 +480,21 @@
         file.writeByte(b ^ 1);
     }
 
-    private boolean tryBootVm(String vmName)
+    private static class BootResult {
+        public final boolean payloadStarted;
+        public final int deathReason;
+
+        BootResult(boolean payloadStarted, int deathReason) {
+            this.payloadStarted = payloadStarted;
+            this.deathReason = deathReason;
+        }
+    }
+
+    private BootResult tryBootVm(String vmName)
             throws VirtualMachineException, InterruptedException {
         mInner.mVm = mInner.mVmm.get(vmName); // re-load the vm before running tests
         final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
+        final CompletableFuture<Integer> deathReason = new CompletableFuture<>();
         VmEventListener listener =
                 new VmEventListener() {
                     @Override
@@ -490,9 +502,15 @@
                         payloadStarted.complete(true);
                         forceStop(vm);
                     }
+                    @Override
+                    public void onDied(VirtualMachine vm, int reason) {
+                        deathReason.complete(reason);
+                        super.onDied(vm, reason);
+                    }
                 };
         listener.runToFinish(mInner.mVm);
-        return payloadStarted.getNow(false);
+        return new BootResult(
+                payloadStarted.getNow(false), deathReason.getNow(DeathReason.INFRASTRUCTURE_ERROR));
     }
 
     private RandomAccessFile prepareInstanceImage(String vmName)
@@ -506,7 +524,7 @@
         oldVm.delete();
         mInner.mVmm.getOrCreate(vmName, config);
 
-        assertThat(tryBootVm(vmName)).isTrue();
+        assertThat(tryBootVm(vmName).payloadStarted).isTrue();
 
         File vmRoot = new File(mInner.mContext.getFilesDir(), "vm");
         File vmDir = new File(vmRoot, vmName);
@@ -530,7 +548,7 @@
         assertThat(offset.isPresent()).isTrue();
 
         flipBit(instanceFile, offset.getAsLong());
-        assertThat(tryBootVm("test_vm_integrity")).isFalse();
+        assertThat(tryBootVm("test_vm_integrity").payloadStarted).isFalse();
     }
 
     @Test
@@ -571,4 +589,22 @@
             assertThatPartitionIsMissing(PVM_FW_PARTITION_UUID);
         }
     }
+
+    @Test
+    public void bootFailsWhenConfigIsInvalid()
+            throws VirtualMachineException, InterruptedException, IOException {
+        VirtualMachine existingVm = mInner.mVmm.get("test_vm_invalid_config");
+        if (existingVm != null) {
+            existingVm.delete();
+        }
+
+        VirtualMachineConfig.Builder builder =
+                mInner.newVmConfigBuilder("assets/vm_config_no_task.json");
+        VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+        mInner.mVmm.create("test_vm_invalid_config", normalConfig);
+
+        BootResult bootResult = tryBootVm("test_vm_invalid_config");
+        assertThat(bootResult.payloadStarted).isFalse();
+        assertThat(bootResult.deathReason).isEqualTo(DeathReason.MICRODROID_INVALID_PAYLOAD_CONFIG);
+    }
 }
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index 0acef2b..a0d84b4 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -47,6 +47,9 @@
             enabled: true,
         },
     },
+    sanitize: {
+        hwaddress: false,
+    },
     apex_available: ["com.android.virt"],
 }