Merge "Not to spawn thread when writing the atom VmExited"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3217ee1..d17b434 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,6 +4,9 @@
       "name": "MicrodroidHostTestCases"
     },
     {
+      "name": "ComposHostTestCases"
+    },
+    {
       "name": "MicrodroidTestApp"
     },
     {
@@ -33,9 +36,6 @@
       "name": "ComposBenchmarkApp"
     },
     {
-      "name": "ComposHostTestCases"
-    },
-    {
       "name": "AVFHostTestCases"
     }
   ],
diff --git a/apex/manifest.json b/apex/manifest.json
index 3330a67..eac57c3 100644
--- a/apex/manifest.json
+++ b/apex/manifest.json
@@ -1,4 +1,4 @@
 {
   "name": "com.android.virt",
-  "version": 1
+  "version": 2
 }
diff --git a/authfs/tests/benchmarks/Android.bp b/authfs/tests/benchmarks/Android.bp
index 38ece79..b30ecdd 100644
--- a/authfs/tests/benchmarks/Android.bp
+++ b/authfs/tests/benchmarks/Android.bp
@@ -24,6 +24,7 @@
         ":CtsApkVerityTestPrebuiltFiles",
         ":MicrodroidTestApp",
     ],
+    required: ["MicrodroidTestPreparer"],
 }
 
 cc_binary {
diff --git a/authfs/tests/benchmarks/AndroidTest.xml b/authfs/tests/benchmarks/AndroidTest.xml
index 7ca3a80..9216006 100644
--- a/authfs/tests/benchmarks/AndroidTest.xml
+++ b/authfs/tests/benchmarks/AndroidTest.xml
@@ -49,6 +49,8 @@
             value="/data/local/tmp/authfs/input.apk.fsv_sig" />
     </target_preparer>
 
+    <target_preparer class="com.android.microdroid.test.preparer.DisableMicrodroidDebugPolicyPreparer" />
+
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="throw-if-cmd-fail" value="true" />
         <!-- Now that the files are pushed to the device, enable fs-verity for the targeting file.
diff --git a/compos/apex/manifest.json b/compos/apex/manifest.json
index cdb87a3..7a07b1b 100644
--- a/compos/apex/manifest.json
+++ b/compos/apex/manifest.json
@@ -1,4 +1,4 @@
 {
   "name": "com.android.compos",
-  "version": 1
+  "version": 2
 }
diff --git a/compos/benchmark/Android.bp b/compos/benchmark/Android.bp
index 3d46a39..dc0c01c 100644
--- a/compos/benchmark/Android.bp
+++ b/compos/benchmark/Android.bp
@@ -15,7 +15,9 @@
         "MicrodroidTestHelper",
         "truth-prebuilt",
     ],
-    platform_apis: true,
+    sdk_version: "test_current",
     use_embedded_native_libs: true,
     compile_multilib: "64",
+
+    host_required: ["MicrodroidTestPreparer"],
 }
diff --git a/compos/benchmark/AndroidTest.xml b/compos/benchmark/AndroidTest.xml
index f98b743..8c65187 100644
--- a/compos/benchmark/AndroidTest.xml
+++ b/compos/benchmark/AndroidTest.xml
@@ -27,6 +27,8 @@
         <option name="force-root" value="true" />
     </target_preparer>
 
+    <target_preparer class="com.android.microdroid.test.preparer.DisableMicrodroidDebugPolicyPreparer" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.compos.benchmark" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
index b884b9e..3f5ff20 100644
--- a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
+++ b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
@@ -20,6 +20,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
@@ -69,7 +70,7 @@
     }
 
     @After
-    public void tearDown() throws Exception {
+    public void tearDown() {
         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
     }
 
@@ -96,7 +97,7 @@
             Long compileEndTime = System.nanoTime();
             Timestamp afterCompileLatestTime = getLatestDex2oatSuccessTime();
 
-            assertTrue(afterCompileLatestTime != null);
+            assertNotNull(afterCompileLatestTime);
             assertTrue(
                     beforeCompileLatestTime == null
                             || beforeCompileLatestTime.before(afterCompileLatestTime));
@@ -135,7 +136,7 @@
             threadGetMetrics.start();
 
             Long compileStartTime = System.nanoTime();
-            String output = executeCommand(command);
+            String output = runInShellWithStderr(TAG, mInstrumentation.getUiAutomation(), command);
             Long compileEndTime = System.nanoTime();
             assertThat(output).containsMatch("All Ok");
             double elapsedSec = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index eb2e072..8c7aeeb 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -23,6 +23,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeFalse;
+
 import android.platform.test.annotations.RootPermissionTest;
 
 import com.android.microdroid.test.host.CommandRunner;
@@ -81,6 +83,8 @@
     @Before
     public void setUp() throws Exception {
         assumeDeviceIsCapable(getDevice());
+        // We get a very high level of (apparently bogus) OOM errors on Cuttlefish (b/264496291).
+        assumeFalse("Skipping test on Cuttlefish", isCuttlefish());
 
         String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME);
         if (value == null) {
diff --git a/pvmfw/README.md b/pvmfw/README.md
index 2d2b253..4e93648 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -270,3 +270,166 @@
 
 For details about device tree properties for debug policies, see
 [microdroid's debugging policy guide](../microdroid/README.md#option-1-running-microdroid-on-avf-debug-policy-configured-device).
+
+### Platform Requirements
+
+pvmfw is intended to run in a virtualized environment according to the `crosvm`
+[memory layout][crosvm-mem] for protected VMs and so it expects to have been
+loaded at address `0x7fc0_0000` and uses the 2MiB region at address
+`0x7fe0_0000` as scratch memory. It makes use of the virtual PCI bus to obtain a
+virtio interface to the host and prints its logs through the 16550 UART (address
+`0x3f8`).
+
+At boot, pvmfw discovers the running hypervisor in order to select the
+appropriate hypervisor calls to share/unshare memory, mark IPA regions as MMIO,
+obtain trusted true entropy, and reboot the virtual machine. In particular, it
+makes use of the following hypervisor calls:
+
+- Arm [SMC Calling Convention][smccc] v1.1 or above:
+
+    - `SMCCC_VERSION`
+    - Vendor Specific Hypervisor Service Call UID Query
+
+- Arm [Power State Coordination Interface][psci] v1.0 or above:
+
+    - `PSCI_VERSION`
+    - `PSCI_FEATURES`
+    - `PSCI_SYSTEM_RESET`
+    - `PSCI_SYSTEM_SHUTDOWN`
+
+- Arm [True Random Number Generator Firmware Interface][smccc-trng] v1.0:
+
+    - `TRNG_VERSION`
+    - `TRNG_FEATURES`
+    - `TRNG_RND`
+
+- When running under KVM, the pKVM-specific hypervisor interface must provide:
+
+    - `MEMINFO` (function ID `0xc6000002`)
+    - `MEM_SHARE` (function ID `0xc6000003`)
+    - `MEM_UNSHARE` (function ID `0xc6000004`)
+    - `MMIO_GUARD_INFO` (function ID `0xc6000005`)
+    - `MMIO_GUARD_ENROLL` (function ID `0xc6000006`)
+    - `MMIO_GUARD_MAP` (function ID `0xc6000007`)
+    - `MMIO_GUARD_UNMAP` (function ID `0xc6000008`)
+
+[crosvm-mem]: https://crosvm.dev/book/appendix/memory_layout.html
+[psci]: https://developer.arm.com/documentation/den0022
+[smccc]: https://developer.arm.com/documentation/den0028
+[smccc-trng]: https://developer.arm.com/documentation/den0098
+
+## Booting Protected Virtual Machines
+
+### Boot Protocol
+
+As the hypervisor makes pvmfw the entry point of the VM, the initial value of
+the registers it receives is configured by the VMM and is expected to follow the
+[Linux ABI] _i.e._
+
+- x0 = physical address of device tree blob (dtb) in system RAM.
+- x1 = 0 (reserved for future use)
+- x2 = 0 (reserved for future use)
+- x3 = 0 (reserved for future use)
+
+Images to be verified, which have been loaded to guest memory by the VMM prior
+to booting the VM, are described to pvmfw using the device tree (x0):
+
+- the kernel in the `/config` DT node _e.g._
+
+    ```
+    / {
+        config {
+            kernel-address = <0x80200000>;
+            kernel-size = <0x1000000>;
+        };
+    };
+    ````
+
+- the (optional) ramdisk in the standard `/chosen` node _e.g._
+
+    ```
+    / {
+        chosen {
+            linux,initrd-start = <0x82000000>;
+            linux,initrd-end = <0x82800000>;
+        };
+    };
+    ```
+
+[Linux ABI]: https://www.kernel.org/doc/Documentation/arm64/booting.txt
+
+### Handover ABI
+
+After verifying the guest kernel, pvmfw boots it using the Linux ABI described
+above. It uses the device tree to pass the following:
+
+- a reserved memory node containing the produced BCC:
+
+    ```
+    / {
+        reserved-memory {
+            #address-cells = <0x02>;
+            #size-cells = <0x02>;
+            ranges;
+            dice {
+                compatible = "google,open-dice";
+                no-map;
+                reg = <0x0 0x7fe0000>, <0x0 0x1000>;
+            };
+        };
+    };
+    ```
+
+- the `/chosen/avf,new-instance` flag, set when pvmfw generated a new secret
+  (_i.e._ the pVM instance was booted for the first time). This should be used
+  by the next stages to ensure that an attacker isn't trying to force new
+  secrets to be generated by one stage, in isolation;
+
+- the `/chosen/avf,strict-boot` flag, always set and can be used by guests to
+  enable extra validation
+
+### Guest Image Signing
+
+pvmfw verifies the guest kernel image (loaded by the VMM) by re-using tools and
+formats introduced by the Android Verified Boot. In particular, it expects the
+kernel region (see `/config/kernel-{address,size}` described above) to contain
+an appended VBMeta structure, which can be generated as follows:
+
+```
+avbtool add_hash_footer --image <kernel.bin> \
+    --partition_name boot \
+    --dynamic_partition_size \
+    --key $KEY
+```
+
+In cases where a ramdisk is required by the guest, pvmfw must also verify it. To
+do so, it must be covered by a hash descriptor in the VBMeta of the kernel:
+
+```
+cp <initrd.bin> /tmp/
+avbtool add_hash_footer --image /tmp/<initrd.bin> \
+    --partition_name $INITRD_NAME \
+    --dynamic_partition_size \
+    --key $KEY
+avbtool add_hash_footer --image <kernel.bin> \
+    --partition_name boot \
+    --dynamic_partition_size \
+    --include_descriptor_from_image /tmp/<initrd.bin> \
+    --key $KEY
+```
+
+Note that the `/tmp/<initrd.bin>` file is only created to temporarily hold the
+hash descriptor to be added to the kernel footer and that the unsigned
+`<initrd.bin>` should be passed to the VMM when booting a pVM.
+
+The name of the AVB "partition" for the ramdisk (`$INITRD_NAME`) can be used by
+the signer to specify if pvmfw must consider the guest to be debuggable
+(`initrd_debug`) or not (`initrd_normal`), which will be reflected in the
+certificate of the guest and will affect the secrets being provisioned.
+
+If pVM guest kernels are built and/or packaged using the Android Build system,
+the signing described above is recommended to be done through an
+`avb_add_hash_footer` Soong module (see [how we sign the Microdroid
+kernel][soong-udroid]).
+
+[soong-udroid]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/Virtualization/microdroid/Android.bp;l=427;drc=ca0049be4d84897b8c9956924cfae506773103eb
diff --git a/pvmfw/src/crypto.rs b/pvmfw/src/crypto.rs
index 275de7a..0785a7a 100644
--- a/pvmfw/src/crypto.rs
+++ b/pvmfw/src/crypto.rs
@@ -14,8 +14,6 @@
 
 //! Wrapper around BoringSSL/OpenSSL symbols.
 
-use crate::cstr;
-
 use core::convert::AsRef;
 use core::ffi::{c_char, c_int, CStr};
 use core::fmt;
@@ -23,6 +21,9 @@
 use core::num::NonZeroU32;
 use core::ptr;
 
+use crate::cstr;
+
+use bssl_ffi::CRYPTO_library_init;
 use bssl_ffi::ERR_get_error_line;
 use bssl_ffi::ERR_lib_error_string;
 use bssl_ffi::ERR_reason_error_string;
@@ -298,3 +299,8 @@
         Err(ErrorIterator {})
     }
 }
+
+pub fn init() {
+    // SAFETY - Configures the internal state of the library - may be called multiple times.
+    unsafe { CRYPTO_library_init() }
+}
diff --git a/pvmfw/src/debug_policy.rs b/pvmfw/src/debug_policy.rs
deleted file mode 100644
index bbf7e04..0000000
--- a/pvmfw/src/debug_policy.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2023, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Support for the debug policy overlay in pvmfw
-
-use core::fmt;
-use libfdt::FdtError;
-
-#[derive(Debug, Clone)]
-pub enum DebugPolicyError {
-    /// The provided baseline FDT was invalid or malformed, so cannot access certain node/prop
-    Fdt(&'static str, FdtError),
-    /// The provided debug policy FDT was invalid or malformed.
-    DebugPolicyFdt(&'static str, FdtError),
-    /// The overlaid result FDT is invalid or malformed, and may be corrupted.
-    OverlaidFdt(&'static str, FdtError),
-}
-
-impl fmt::Display for DebugPolicyError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Self::Fdt(s, e) => write!(f, "Invalid baseline FDT. {s}: {e}"),
-            Self::DebugPolicyFdt(s, e) => write!(f, "Invalid overlay FDT. {s}: {e}"),
-            Self::OverlaidFdt(s, e) => write!(f, "Invalid overlaid FDT. {s}: {e}"),
-        }
-    }
-}
-
-/// Applies the debug policy device tree overlay to the pVM DT.
-///
-/// # Safety
-///
-/// When an error is returned by this function, the input `Fdt` should be
-/// discarded as it may have have been partially corrupted during the overlay
-/// application process.
-unsafe fn apply_debug_policy(
-    fdt: &mut libfdt::Fdt,
-    debug_policy: &mut [u8],
-) -> Result<(), DebugPolicyError> {
-    let overlay = libfdt::Fdt::from_mut_slice(debug_policy)
-        .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to load debug policy overlay", e))?;
-
-    fdt.unpack().map_err(|e| DebugPolicyError::Fdt("Failed to unpack", e))?;
-
-    let fdt = fdt
-        .apply_overlay(overlay)
-        .map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to apply overlay", e))?;
-
-    fdt.pack().map_err(|e| DebugPolicyError::OverlaidFdt("Failed to re-pack", e))
-}
-
-/// Handles debug policies.
-///
-/// # Safety
-///
-/// This may corrupt the input `Fdt` when overlaying debug policy or applying
-/// ramdump configuration.
-pub unsafe fn handle_debug_policy(
-    fdt: &mut libfdt::Fdt,
-    debug_policy: Option<&mut [u8]>,
-) -> Result<(), DebugPolicyError> {
-    if let Some(dp) = debug_policy {
-        apply_debug_policy(fdt, dp)?;
-    }
-
-    Ok(())
-}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 8219882..00f0e9b 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -15,7 +15,7 @@
 //! Low-level entry and exit points of pvmfw.
 
 use crate::config;
-use crate::debug_policy::{handle_debug_policy, DebugPolicyError};
+use crate::crypto;
 use crate::fdt;
 use crate::heap;
 use crate::helpers;
@@ -53,16 +53,6 @@
     SecretDerivationError,
 }
 
-impl From<DebugPolicyError> for RebootReason {
-    fn from(error: DebugPolicyError) -> Self {
-        match error {
-            DebugPolicyError::Fdt(_, _) => RebootReason::InvalidFdt,
-            DebugPolicyError::DebugPolicyFdt(_, _) => RebootReason::InvalidConfig,
-            DebugPolicyError::OverlaidFdt(_, _) => RebootReason::InternalError,
-        }
-    }
-}
-
 main!(start);
 
 /// Entry point for pVM firmware.
@@ -192,6 +182,8 @@
         RebootReason::InternalError
     })?;
 
+    crypto::init();
+
     // SAFETY - We only get the appended payload from here, once. It is mapped and the linker
     // script prevents it from overlapping with other objects.
     let appended_data = unsafe { get_appended_data_slice() };
@@ -234,19 +226,11 @@
     })?;
 
     // This wrapper allows main() to be blissfully ignorant of platform details.
-    crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc_slice, &mut memory)?;
+    crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc_slice, debug_policy, &mut memory)?;
 
     helpers::flushed_zeroize(bcc_slice);
     helpers::flush(slices.fdt.as_slice());
 
-    // SAFETY - As we `?` the result, there is no risk of using a bad `slices.fdt`.
-    unsafe {
-        handle_debug_policy(slices.fdt, debug_policy).map_err(|e| {
-            error!("Unexpected error when handling debug policy: {e:?}");
-            RebootReason::from(e)
-        })?;
-    }
-
     info!("Expecting a bug making MMIO_GUARD_UNMAP return NOT_SUPPORTED on success");
     memory.mmio_unmap_all().map_err(|e| {
         error!("Failed to unshare MMIO ranges: {e}");
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 7d88455..d15eaba 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -22,6 +22,7 @@
 use crate::memory::MAX_ADDR;
 use crate::RebootReason;
 use alloc::ffi::CString;
+use alloc::vec::Vec;
 use core::cmp::max;
 use core::cmp::min;
 use core::ffi::CStr;
@@ -36,6 +37,7 @@
 use libfdt::FdtNode;
 use log::debug;
 use log::error;
+use log::info;
 use tinyvec::ArrayVec;
 
 /// Extract from /config the address range containing the pre-loaded kernel. Absence of /config is
@@ -672,6 +674,7 @@
     bcc: &[u8],
     new_instance: bool,
     strict_boot: bool,
+    debug_policy: Option<&mut [u8]>,
 ) -> libfdt::Result<()> {
     fdt.unpack()?;
 
@@ -680,6 +683,13 @@
     set_or_clear_chosen_flag(fdt, cstr!("avf,strict-boot"), strict_boot)?;
     set_or_clear_chosen_flag(fdt, cstr!("avf,new-instance"), new_instance)?;
 
+    if let Some(debug_policy) = debug_policy {
+        apply_debug_policy(fdt, debug_policy)?;
+        info!("Debug policy applied.");
+    } else {
+        info!("No debug policy found.");
+    }
+
     fdt.pack()?;
 
     Ok(())
@@ -712,3 +722,26 @@
 
     Ok(())
 }
+
+fn apply_debug_policy(fdt: &mut Fdt, debug_policy: &mut [u8]) -> libfdt::Result<()> {
+    let backup_fdt = Vec::from(fdt.as_slice());
+
+    let overlay = match Fdt::from_mut_slice(debug_policy) {
+        Ok(overlay) => overlay,
+        Err(e) => {
+            info!("Corrupted debug policy found: {e}. Not applying.");
+            return Ok(());
+        }
+    };
+    let backup_overlay = Vec::from(overlay.as_slice());
+
+    // SAFETY - on failure, the corrupted fdts are discarded and are restored using the backups.
+    if let Err(e) = unsafe { fdt.apply_overlay(overlay) } {
+        error!("Failed to apply debug policy: {e}. Recovering...");
+        fdt.copy_from_slice(backup_fdt.as_slice())?;
+        overlay.copy_from_slice(backup_overlay.as_slice())?;
+        // A successful restoration is considered success because an invalid debug policy
+        // shouldn't DOS the pvmfw
+    }
+    Ok(())
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 00ff61f..06cc81e 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -21,7 +21,6 @@
 
 mod config;
 mod crypto;
-mod debug_policy;
 mod dice;
 mod entry;
 mod exceptions;
@@ -65,6 +64,7 @@
     signed_kernel: &[u8],
     ramdisk: Option<&[u8]>,
     current_bcc_handover: &[u8],
+    debug_policy: Option<&mut [u8]>,
     memory: &mut MemoryTracker,
 ) -> Result<(), RebootReason> {
     info!("pVM firmware");
@@ -122,7 +122,7 @@
     flush(next_bcc);
 
     let strict_boot = true;
-    modify_for_next_stage(fdt, next_bcc, new_instance, strict_boot).map_err(|e| {
+    modify_for_next_stage(fdt, next_bcc, new_instance, strict_boot, debug_policy).map_err(|e| {
         error!("Failed to configure device tree: {e}");
         RebootReason::InternalError
     })?;
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index dac4993..9c512bf 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -26,6 +26,7 @@
     sdk_version: "test_current",
     use_embedded_native_libs: true,
     compile_multilib: "64",
+    host_required: ["MicrodroidTestPreparer"],
 }
 
 cc_library_shared {
diff --git a/tests/benchmark/AndroidTest.xml b/tests/benchmark/AndroidTest.xml
index 29bc95a..8c8bfbe 100644
--- a/tests/benchmark/AndroidTest.xml
+++ b/tests/benchmark/AndroidTest.xml
@@ -30,6 +30,7 @@
         <option name="post-push" value="chmod 755 /data/local/tmp/perf-setup.sh;/data/local/tmp/perf-setup.sh" />
         <option name="cleanup" value="true" />
     </target_preparer>
+    <target_preparer class="com.android.microdroid.test.preparer.DisableMicrodroidDebugPolicyPreparer" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.microdroid.benchmark" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/tests/benchmark_hostside/AndroidTest.xml b/tests/benchmark_hostside/AndroidTest.xml
index 5161269..7a998b1 100644
--- a/tests/benchmark_hostside/AndroidTest.xml
+++ b/tests/benchmark_hostside/AndroidTest.xml
@@ -18,7 +18,9 @@
         <option name="force-root" value="true" />
     </target_preparer>
 
+    <target_preparer class="com.android.microdroid.test.preparer.DisableMicrodroidDebugPolicyPreparer" />
+
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="AVFHostTestCases.jar" />
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index c9eafad..6f07efd 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -19,5 +19,17 @@
         "MicrodroidTestHelper",
         "truth-prebuilt",
     ],
-    sdk_version: "system_current",
+    sdk_version: "test_current",
+}
+
+java_test_helper_library {
+    name: "MicrodroidTestPreparer",
+    srcs: ["src/java/com/android/microdroid/test/preparer/*.java"],
+    libs: ["tradefed"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    host_supported: true,
+    device_supported: false,
 }
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 744f94c..4e1d238 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
@@ -97,15 +97,6 @@
         }
     }
 
-    public final boolean getDebugPolicyBoolean(String debugPolicy) throws IOException {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        String debugPolicyFilePath = "/proc/device-tree" + debugPolicy;
-        String cmd = "su root xxd -p " + debugPolicyFilePath;
-        String dp = runInShell(TAG, uiAutomation, cmd).trim();
-        return "00000001".equals(dp);
-    }
-
     private Context mCtx;
     private boolean mProtectedVm;
 
@@ -444,7 +435,25 @@
             return stdout;
         } catch (IOException e) {
             Log.e(tag, "Error executing: " + command, e);
-            throw new RuntimeException("Failed to run the command.");
+            throw new RuntimeException("Failed to run the command.", e);
+        }
+    }
+
+    /** Execute a command. Returns the concatenation of stdout and stderr. */
+    protected String runInShellWithStderr(String tag, UiAutomation uiAutomation, String command) {
+        ParcelFileDescriptor[] files = uiAutomation.executeShellCommandRwe(command);
+        try (InputStream stdout = new ParcelFileDescriptor.AutoCloseInputStream(files[0]);
+                InputStream stderr = new ParcelFileDescriptor.AutoCloseInputStream(files[2]);
+                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            files[1].close(); // The command's stdin
+            stdout.transferTo(out);
+            stderr.transferTo(out);
+            String output = out.toString("UTF-8");
+            Log.i(tag, "Got output : " + stdout);
+            return output;
+        } catch (IOException e) {
+            Log.e(tag, "Error executing: " + command, e);
+            throw new RuntimeException("Failed to run the command.", e);
         }
     }
 
diff --git a/tests/helper/src/java/com/android/microdroid/test/preparer/DisableMicrodroidDebugPolicyPreparer.java b/tests/helper/src/java/com/android/microdroid/test/preparer/DisableMicrodroidDebugPolicyPreparer.java
new file mode 100644
index 0000000..47be8b8
--- /dev/null
+++ b/tests/helper/src/java/com/android/microdroid/test/preparer/DisableMicrodroidDebugPolicyPreparer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.microdroid.test.preparer;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.BaseTargetPreparer;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+/**
+ * Target preparer that disables microdroid's device policy for future VMs. This requires adb root
+ * for configuring the relevant sysprop.
+ *
+ * <p>Will restore back to original value on tear down. adb will be also unrooted if it wasn't root.
+ */
+@OptionClass(alias = "disable-microdroid-debug-policy-preparer")
+public final class DisableMicrodroidDebugPolicyPreparer extends BaseTargetPreparer {
+    private static final String SYSPROP_CUSTOM_DEBUG_POLICY_PATH =
+            "hypervisor.virtualizationmanager.debug_policy.path";
+
+    private boolean mWasRoot = false;
+    private String mOldDebugPolicyPath;
+
+    @Option(
+            name = "debug-policy-path",
+            description = "Debug policy path for sysprop " + SYSPROP_CUSTOM_DEBUG_POLICY_PATH)
+    private String mDebugPolicyPath = "/data/local/tmp/virt/stub_debug_policy.dts";
+
+    @Override
+    public void setUp(TestInformation testInfo)
+            throws TargetSetupError, BuildError, DeviceNotAvailableException {
+        ITestDevice device = testInfo.getDevice();
+        mWasRoot = device.isAdbRoot();
+        if (!mWasRoot && !device.enableAdbRoot()) {
+            throw new TargetSetupError("Failed to adb root device", device.getDeviceDescriptor());
+        }
+
+        try {
+            CLog.d("Bypassing micrdroid debug policy");
+            mOldDebugPolicyPath = device.getProperty(SYSPROP_CUSTOM_DEBUG_POLICY_PATH);
+            boolean result = device.setProperty(SYSPROP_CUSTOM_DEBUG_POLICY_PATH, mDebugPolicyPath);
+            if (!result) {
+                throw new TargetSetupError(
+                        "Bypassing microdroid debug policy failed", device.getDeviceDescriptor());
+            }
+        } finally {
+            if (!mWasRoot) {
+                device.disableAdbRoot();
+            }
+        }
+    }
+
+    @Override
+    public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+        ITestDevice device = testInfo.getDevice();
+        if (e instanceof DeviceNotAvailableException) {
+            CLog.d("device not available: skipping teardown");
+            return;
+        }
+
+        if (!mWasRoot) {
+            device.enableAdbRoot();
+        }
+
+        CLog.d("Resetting microdroid debug policy");
+        device.setProperty(
+                SYSPROP_CUSTOM_DEBUG_POLICY_PATH,
+                mOldDebugPolicyPath == null ? "" : mOldDebugPolicyPath);
+
+        if (!mWasRoot) {
+            device.disableAdbRoot();
+        }
+    }
+}
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 248755f..c71a8ec 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -75,4 +75,5 @@
         "libsparse",
         "libz",
     ],
+    required: ["MicrodroidTestPreparer"],
 }
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
index 18728ad..429d910 100644
--- a/tests/hostside/AndroidTest.xml
+++ b/tests/hostside/AndroidTest.xml
@@ -25,6 +25,8 @@
         <option name="force-root" value="false"/>
     </target_preparer>
 
+    <target_preparer class="com.android.microdroid.test.preparer.DisableMicrodroidDebugPolicyPreparer" />
+
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="MicrodroidHostTestCases.jar" />
     </test>
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 7044ae7..38bc485 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -28,13 +28,15 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.common.truth.TruthJUnit.assume;
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
-import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import com.google.common.base.Strings;
 import com.google.common.truth.BooleanSubject;
 
+import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -54,7 +56,8 @@
 import android.system.virtualmachine.VirtualMachineDescriptor;
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
-import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.VsrTest;
@@ -1583,22 +1586,6 @@
         }
     }
 
-    private boolean isConsoleOutputEnabledByDebugPolicy() {
-        if (isUserBuild()) {
-            Log.i(
-                    TAG,
-                    "Debug policy is inaccessible in user build. Assumes that console output is"
-                            + " disabled");
-            return false;
-        }
-        try {
-            return getDebugPolicyBoolean("/avf/guest/common/log");
-        } catch (IOException e) {
-            Log.w(TAG, "Fail to read debug policy. Assumes false", e);
-            return false;
-        }
-    }
-
     private boolean checkVmOutputIsRedirectedToLogcat(boolean debuggable) throws Exception {
         String time =
                 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
@@ -1632,21 +1619,33 @@
     @Test
     public void outputIsRedirectedToLogcatIfNotCaptured() throws Exception {
         assumeSupportedDevice();
-        assumeFalse(
-                "Debug policy would turn on console output. Perhaps userdebug build?",
-                isConsoleOutputEnabledByDebugPolicy());
 
         assertThat(checkVmOutputIsRedirectedToLogcat(true)).isTrue();
     }
 
+    private boolean setSystemProperties(String name, String value) {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        String cmd = "setprop " + name + " " + (value.isEmpty() ? "\"\"" : value);
+        return runInShellWithStderr(TAG, uiAutomation, cmd).trim().isEmpty();
+    }
+
     @Test
     public void outputIsNotRedirectedToLogcatIfNotDebuggable() throws Exception {
         assumeSupportedDevice();
-        assumeFalse(
-                "Debug policy would turn on console output. Perhaps userdebug build?",
-                isConsoleOutputEnabledByDebugPolicy());
 
-        assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
+        // Disable debug policy to ensure no log output.
+        String sysprop = "hypervisor.virtualizationmanager.debug_policy.path";
+        String old = SystemProperties.get(sysprop);
+        assumeTrue(
+                "Can't disable debug policy. Perhapse user build?",
+                setSystemProperties(sysprop, ""));
+
+        try {
+            assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
+        } finally {
+            assertThat(setSystemProperties(sysprop, old)).isTrue();
+        }
     }
 
     @Test