Allow to override AVF debug policy
Bug: 275047565
Test: atest
Change-Id: I1d331341945ee90dcbaa67113e507fb84de8218d
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 7ddf680..61b69f5 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -606,6 +606,26 @@
Ok(fdt)
}
+ /// Creates an empty Flattened Device Tree with a mutable slice.
+ pub fn create_empty_tree(fdt: &mut [u8]) -> Result<&mut Self> {
+ // SAFETY - fdt_create_empty_tree() only write within the specified length,
+ // and returns error if buffer was insufficient.
+ // There will be no memory write outside of the given fdt.
+ let ret = unsafe {
+ libfdt_bindgen::fdt_create_empty_tree(
+ fdt.as_mut_ptr().cast::<c_void>(),
+ fdt.len() as i32,
+ )
+ };
+ fdt_err_expect_zero(ret)?;
+
+ // SAFETY - The FDT will be validated before it is returned.
+ let fdt = unsafe { Self::unchecked_from_mut_slice(fdt) };
+ fdt.check_full()?;
+
+ Ok(fdt)
+ }
+
/// Wraps a slice containing a Flattened Device Tree.
///
/// # Safety
diff --git a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java b/tests/hostside/java/com/android/microdroid/test/DebugPolicyHostTests.java
similarity index 81%
rename from tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
rename to tests/hostside/java/com/android/microdroid/test/DebugPolicyHostTests.java
index 0f6d095..014f9f0 100644
--- a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/DebugPolicyHostTests.java
@@ -48,11 +48,10 @@
import java.io.File;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
-import java.io.FileNotFoundException;
-/** Tests debug policy of pvmfw.bin with custom debug policy */
+/** Tests debug policy */
@RunWith(DeviceJUnit4ClassRunner.class)
-public class PvmfwDebugPolicyHostTests extends MicrodroidHostTestCaseBase {
+public class DebugPolicyHostTests extends MicrodroidHostTestCaseBase {
@NonNull private static final String PVMFW_FILE_NAME = "pvmfw_test.bin";
@NonNull private static final String BCC_FILE_NAME = "bcc.dat";
@NonNull private static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
@@ -70,6 +69,16 @@
@NonNull private static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + PVMFW_FILE_NAME;
@NonNull private static final String CUSTOM_PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
+ @NonNull private static final String CUSTOM_DEBUG_POLICY_FILE_NAME = "debug_policy.dtb";
+
+ @NonNull
+ private static final String CUSTOM_DEBUG_POLICY_PATH =
+ TEST_ROOT + CUSTOM_DEBUG_POLICY_FILE_NAME;
+
+ @NonNull
+ private static final String CUSTOM_DEBUG_POLICY_PATH_PROP =
+ "hypervisor.virtualizationmanager.debug_policy.path";
+
@NonNull
private static final String AVF_DEBUG_POLICY_ADB_DT_PROP_PATH = "/avf/guest/microdroid/adb";
@@ -93,6 +102,7 @@
@Nullable private TestDevice mAndroidDevice;
@Nullable private ITestDevice mMicrodroidDevice;
@Nullable private File mCustomPvmfwBinFileOnHost;
+ @Nullable private File mCustomDebugPolicyFileOnHost;
@Before
public void setUp() throws Exception {
@@ -114,12 +124,14 @@
mBccFileOnHost =
getTestInformation().getDependencyFile(BCC_FILE_NAME, /* targetFirst= */ false);
- // Prepare for loading pvmfw.bin
- // File will be setup in individual test,
- // and then pushed to device in launchProtectedVmAndWaitForBootCompleted.
+ // Prepare for system properties for custom debug policy.
+ // File will be prepared later in individual test by setupCustomDebugPolicy()
+ // and then pushed to device when launching with launchProtectedVmAndWaitForBootCompleted()
+ // or tryLaunchProtectedNonDebuggableVm().
mCustomPvmfwBinFileOnHost =
FileUtil.createTempFile(CUSTOM_PVMFW_FILE_PREFIX, CUSTOM_PVMFW_FILE_SUFFIX);
mAndroidDevice.setProperty(CUSTOM_PVMFW_IMG_PATH_PROP, CUSTOM_PVMFW_IMG_PATH);
+ mAndroidDevice.setProperty(CUSTOM_DEBUG_POLICY_PATH_PROP, CUSTOM_DEBUG_POLICY_PATH);
// Prepare for launching microdroid
mAndroidDevice.installPackage(findTestFile(PACKAGE_FILE_NAME), /* reinstall */ false);
@@ -138,7 +150,8 @@
}
mAndroidDevice.uninstallPackage(PACKAGE_NAME);
- // Cleanup for custom pvmfw.bin
+ // Cleanup for custom debug policies
+ mAndroidDevice.setProperty(CUSTOM_DEBUG_POLICY_PATH_PROP, "");
mAndroidDevice.setProperty(CUSTOM_PVMFW_IMG_PATH_PROP, "");
FileUtil.deleteFile(mCustomPvmfwBinFileOnHost);
@@ -148,21 +161,15 @@
}
@Test
- public void testAdb_boots() throws Exception {
- assumeTrue(
- "Skip if host wouldn't install adbd",
- isDebugPolicyEnabled(AVF_DEBUG_POLICY_ADB_DT_PROP_PATH));
-
- Pvmfw pvmfw = createPvmfw("avf_debug_policy_with_adb.dtbo");
- pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+ public void testAdbInDebugPolicy_withDebugLevelNone_bootWithAdbConnection() throws Exception {
+ prepareCustomDebugPolicy("avf_debug_policy_with_adb.dtbo");
launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_NONE);
}
@Test
- public void testNoAdb_boots() throws Exception {
- Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_adb.dtbo");
- pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+ public void testNoAdbInDebugPolicy_withDebugLevelNone_boots() throws Exception {
+ prepareCustomDebugPolicy("avf_debug_policy_without_adb.dtbo");
// VM would boot, but cannot verify directly because of no adbd in the VM.
CommandResult result = tryLaunchProtectedNonDebuggableVm();
@@ -173,9 +180,8 @@
}
@Test
- public void testNoAdb_noConnection() throws Exception {
- Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_adb.dtbo");
- pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+ public void testNoAdbInDebugPolicy_withDebugLevelNone_noConnection() throws Exception {
+ prepareCustomDebugPolicy("avf_debug_policy_without_adb.dtbo");
assertThrows(
"Microdroid shouldn't be recognized because of missing adb connection",
@@ -185,6 +191,13 @@
MICRODROID_DEBUG_NONE, BOOT_FAILURE_WAIT_TIME_MS));
}
+ @Test
+ public void testNoAdbInDebugPolicy_withDebugLevelFull_bootWithAdbConnection() throws Exception {
+ prepareCustomDebugPolicy("avf_debug_policy_without_adb.dtbo");
+
+ launchProtectedVmAndWaitForBootCompleted(MICRODROID_DEBUG_FULL);
+ }
+
private boolean isDebugPolicyEnabled(@NonNull String dtPropertyPath)
throws DeviceNotAvailableException {
CommandRunner runner = new CommandRunner(mAndroidDevice);
@@ -208,14 +221,16 @@
return new CommandRunner(mMicrodroidDevice).run("xxd", "-p", path);
}
- @NonNull
- private Pvmfw createPvmfw(@NonNull String debugPolicyFileName) throws FileNotFoundException {
- File file =
+ private void prepareCustomDebugPolicy(@NonNull String debugPolicyFileName) throws Exception {
+ mCustomDebugPolicyFileOnHost =
getTestInformation()
.getDependencyFile(debugPolicyFileName, /* targetFirst= */ false);
- return new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost)
- .setDebugPolicyOverlay(file)
- .build();
+
+ Pvmfw pvmfw =
+ new Pvmfw.Builder(mPvmfwBinFileOnHost, mBccFileOnHost)
+ .setDebugPolicyOverlay(mCustomDebugPolicyFileOnHost)
+ .build();
+ pvmfw.serialize(mCustomPvmfwBinFileOnHost);
}
private boolean hasConsoleOutput(@NonNull CommandResult result)
@@ -242,6 +257,7 @@
.debugLevel(debugLevel)
.protectedVm(/* protectedVm= */ true)
.addBootFile(mCustomPvmfwBinFileOnHost, PVMFW_FILE_NAME)
+ .addBootFile(mCustomDebugPolicyFileOnHost, CUSTOM_DEBUG_POLICY_FILE_NAME)
.setAdbConnectTimeoutMs(adbTimeoutMs)
.build(mAndroidDevice);
assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS)).isTrue();
@@ -256,7 +272,8 @@
// but non-debuggable VM may not enable adb.
CommandRunner runner = new CommandRunner(mAndroidDevice);
runner.run("mkdir", "-p", TEST_ROOT);
- mAndroidDevice.pushFile(mCustomPvmfwBinFileOnHost, TEST_ROOT + PVMFW_FILE_NAME);
+ mAndroidDevice.pushFile(mCustomPvmfwBinFileOnHost, CUSTOM_PVMFW_IMG_PATH);
+ mAndroidDevice.pushFile(mCustomDebugPolicyFileOnHost, CUSTOM_DEBUG_POLICY_PATH);
// This will fail because app wouldn't finish itself.
// But let's run the app once and get logs.
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index c913d02..59e507f 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -56,6 +56,7 @@
"libvmconfig",
"libzip",
"libvsock",
+ "liblibfdt",
// TODO(b/202115393) stabilize the interface
"packagemanager_aidl-rust",
],
@@ -78,5 +79,9 @@
rustlibs: [
"libtempfile",
],
+ data: [
+ ":test_avf_debug_policy_with_adb",
+ ":test_avf_debug_policy_without_adb",
+ ],
test_suites: ["general-tests"],
}
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index e8863c7..7172e7d 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -17,16 +17,134 @@
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
VirtualMachineAppConfig::DebugLevel::DebugLevel,
};
-use std::fs::File;
-use std::io::Read;
-use log::info;
+use anyhow::{anyhow, Context, Error, Result};
+use std::fs;
+use std::io::ErrorKind;
+use std::path::{Path, PathBuf};
+use std::ffi::{CString, NulError};
+use log::{warn, info};
use rustutils::system_properties;
+use libfdt::{Fdt, FdtError};
+use lazy_static::lazy_static;
-const DEBUG_POLICY_LOG_PATH: &str = "/proc/device-tree/avf/guest/common/log";
-const DEBUG_POLICY_RAMDUMP_PATH: &str = "/proc/device-tree/avf/guest/common/ramdump";
-const DEBUG_POLICY_ADB_PATH: &str = "/proc/device-tree/avf/guest/microdroid/adb";
+const CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP: &str =
+ "hypervisor.virtualizationmanager.debug_policy.path";
+const DEVICE_TREE_EMPTY_TREE_SIZE_BYTES: usize = 100; // rough estimation.
-const SYSPROP_CUSTOM_DEBUG_POLICY_PATH: &str = "hypervisor.virtualizationmanager.debug_policy.path";
+struct DPPath {
+ node_path: CString,
+ prop_name: CString,
+}
+
+impl DPPath {
+ fn new(node_path: &str, prop_name: &str) -> Result<Self, NulError> {
+ Ok(Self { node_path: CString::new(node_path)?, prop_name: CString::new(prop_name)? })
+ }
+
+ fn to_path(&self) -> PathBuf {
+ // SAFETY -- unwrap() is safe for to_str() because node_path and prop_name were &str.
+ PathBuf::from(
+ [
+ "/sys/firmware/devicetree/base",
+ self.node_path.to_str().unwrap(),
+ "/",
+ self.prop_name.to_str().unwrap(),
+ ]
+ .concat(),
+ )
+ }
+}
+
+lazy_static! {
+ static ref DP_LOG_PATH: DPPath = DPPath::new("/avf/guest/common", "log").unwrap();
+ static ref DP_RAMDUMP_PATH: DPPath = DPPath::new("/avf/guest/common", "ramdump").unwrap();
+ static ref DP_ADB_PATH: DPPath = DPPath::new("/avf/guest/microdroid", "adb").unwrap();
+}
+
+/// Get debug policy value in bool. It's true iff the value is explicitly set to <1>.
+fn get_debug_policy_bool(path: &Path) -> Result<bool> {
+ let value = match fs::read(path) {
+ Ok(value) => value,
+ Err(error) if error.kind() == ErrorKind::NotFound => return Ok(false),
+ Err(error) => Err(error).with_context(|| format!("Failed to read {path:?}"))?,
+ };
+
+ // DT spec uses big endian although Android is always little endian.
+ match u32::from_be_bytes(value.try_into().map_err(|_| anyhow!("Malformed value in {path:?}"))?)
+ {
+ 0 => Ok(false),
+ 1 => Ok(true),
+ value => Err(anyhow!("Invalid value {value} in {path:?}")),
+ }
+}
+
+/// Get property value in bool. It's true iff the value is explicitly set to <1>.
+/// It takes path as &str instead of &Path, because we don't want OsStr.
+fn get_fdt_prop_bool(fdt: &Fdt, path: &DPPath) -> Result<bool> {
+ let (node_path, prop_name) = (&path.node_path, &path.prop_name);
+ let node = match fdt.node(node_path) {
+ Ok(Some(node)) => node,
+ Err(error) if error != FdtError::NotFound => Err(error)
+ .map_err(Error::msg)
+ .with_context(|| format!("Failed to get node {node_path:?}"))?,
+ _ => return Ok(false),
+ };
+
+ match node.getprop_u32(prop_name) {
+ Ok(Some(0)) => Ok(false),
+ Ok(Some(1)) => Ok(true),
+ Ok(Some(_)) => Err(anyhow!("Invalid prop value {prop_name:?} in node {node_path:?}")),
+ Err(error) if error != FdtError::NotFound => Err(error)
+ .map_err(Error::msg)
+ .with_context(|| format!("Failed to get prop {prop_name:?}")),
+ _ => Ok(false),
+ }
+}
+
+/// Fdt with owned vector.
+struct OwnedFdt {
+ buffer: Vec<u8>,
+}
+
+impl OwnedFdt {
+ fn from_overlay_onto_new_fdt(overlay_file_path: &Path) -> Result<Self> {
+ let mut overlay_buf = match fs::read(overlay_file_path) {
+ Ok(fdt) => fdt,
+ Err(error) if error.kind() == ErrorKind::NotFound => Default::default(),
+ Err(error) => {
+ Err(error).with_context(|| format!("Failed to read {overlay_file_path:?}"))?
+ }
+ };
+
+ let overlay_buf_size = overlay_buf.len();
+
+ let fdt_estimated_size = overlay_buf_size + DEVICE_TREE_EMPTY_TREE_SIZE_BYTES;
+ let mut fdt_buf = vec![0_u8; fdt_estimated_size];
+ let fdt = Fdt::create_empty_tree(fdt_buf.as_mut_slice())
+ .map_err(Error::msg)
+ .context("Failed to create an empty device tree")?;
+
+ if !overlay_buf.is_empty() {
+ let overlay_fdt = Fdt::from_mut_slice(overlay_buf.as_mut_slice())
+ .map_err(Error::msg)
+ .with_context(|| "Malformed {overlay_file_path:?}")?;
+
+ // SAFETY - Return immediately if error happens. Damaged fdt_buf and fdt are discarded.
+ unsafe {
+ fdt.apply_overlay(overlay_fdt).map_err(Error::msg).with_context(|| {
+ "Failed to overlay {overlay_file_path:?} onto empty device tree"
+ })?;
+ }
+ }
+
+ Ok(Self { buffer: fdt_buf })
+ }
+
+ fn as_fdt(&self) -> &Fdt {
+ // SAFETY - Checked validity of buffer when instantiate.
+ unsafe { Fdt::unchecked_from_slice(&self.buffer) }
+ }
+}
/// Debug configurations for both debug level and debug policy
#[derive(Debug)]
@@ -37,43 +155,36 @@
debug_policy_adb: bool,
}
-/// Get debug policy value in bool. It's true iff the value is explicitly set to <1>.
-fn get_debug_policy_bool(path: &'static str) -> Option<bool> {
- let mut file = File::open(path).ok()?;
- let mut log: [u8; 4] = Default::default();
- file.read_exact(&mut log).ok()?;
- // DT spec uses big endian although Android is always little endian.
- Some(u32::from_be_bytes(log) == 1)
-}
-
impl DebugConfig {
pub fn new(debug_level: DebugLevel) -> Self {
- match system_properties::read(SYSPROP_CUSTOM_DEBUG_POLICY_PATH).unwrap_or_default() {
- Some(debug_policy_path) if !debug_policy_path.is_empty() => {
- // TODO: Read debug policy file and override log, adb, ramdump for testing.
- info!("Debug policy is disabled by sysprop");
- Self {
- debug_level,
- debug_policy_log: false,
- debug_policy_ramdump: false,
- debug_policy_adb: false,
- }
+ match system_properties::read(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP).unwrap_or_default() {
+ Some(path) if !path.is_empty() => {
+ match Self::from_custom_debug_overlay_policy(debug_level, Path::new(&path)) {
+ Ok(debug_config) => {
+ info!("Loaded custom debug policy overlay {path}: {debug_config:?}");
+ return debug_config;
+ }
+ Err(err) => warn!("Failed to load custom debug policy overlay {path}: {err:?}"),
+ };
}
_ => {
- let debug_config = Self {
- debug_level,
- debug_policy_log: get_debug_policy_bool(DEBUG_POLICY_LOG_PATH)
- .unwrap_or_default(),
- debug_policy_ramdump: get_debug_policy_bool(DEBUG_POLICY_RAMDUMP_PATH)
- .unwrap_or_default(),
- debug_policy_adb: get_debug_policy_bool(DEBUG_POLICY_ADB_PATH)
- .unwrap_or_default(),
+ match Self::from_host(debug_level) {
+ Ok(debug_config) => {
+ info!("Loaded debug policy from host OS: {debug_config:?}");
+ return debug_config;
+ }
+ Err(err) => warn!("Failed to load debug policy from host OS: {err:?}"),
};
- info!("Loaded debug policy from host OS: {:?}", debug_config);
-
- debug_config
}
}
+
+ info!("Debug policy is disabled");
+ Self {
+ debug_level,
+ debug_policy_log: false,
+ debug_policy_ramdump: false,
+ debug_policy_adb: false,
+ }
}
/// Get whether console output should be configred for VM to leave console and adb log.
@@ -91,4 +202,123 @@
pub fn is_ramdump_needed(&self) -> bool {
self.debug_level != DebugLevel::NONE || self.debug_policy_ramdump
}
+
+ // TODO: Remove this code path in user build for removing libfdt depenency.
+ fn from_custom_debug_overlay_policy(debug_level: DebugLevel, path: &Path) -> Result<Self> {
+ match OwnedFdt::from_overlay_onto_new_fdt(path) {
+ Ok(fdt) => Ok(Self {
+ debug_level,
+ debug_policy_log: get_fdt_prop_bool(fdt.as_fdt(), &DP_LOG_PATH)?,
+ debug_policy_ramdump: get_fdt_prop_bool(fdt.as_fdt(), &DP_RAMDUMP_PATH)?,
+ debug_policy_adb: get_fdt_prop_bool(fdt.as_fdt(), &DP_ADB_PATH)?,
+ }),
+ Err(err) => Err(err),
+ }
+ }
+
+ fn from_host(debug_level: DebugLevel) -> Result<Self> {
+ Ok(Self {
+ debug_level,
+ debug_policy_log: get_debug_policy_bool(&DP_LOG_PATH.to_path())?,
+ debug_policy_ramdump: get_debug_policy_bool(&DP_RAMDUMP_PATH.to_path())?,
+ debug_policy_adb: get_debug_policy_bool(&DP_ADB_PATH.to_path())?,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::ensure;
+
+ fn can_set_sysprop() -> bool {
+ if let Ok(Some(value)) = system_properties::read("ro.build.type") {
+ return "user".eq(&value);
+ }
+ false // if we're in doubt, skip test.
+ }
+
+ #[test]
+ fn test_read_avf_debug_policy_with_adb() -> Result<()> {
+ let debug_config = DebugConfig::from_custom_debug_overlay_policy(
+ DebugLevel::FULL,
+ "avf_debug_policy_with_adb.dtbo".as_ref(),
+ )
+ .unwrap();
+
+ assert_eq!(DebugLevel::FULL, debug_config.debug_level);
+ assert!(!debug_config.debug_policy_log);
+ assert!(!debug_config.debug_policy_ramdump);
+ assert!(debug_config.debug_policy_adb);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_read_avf_debug_policy_without_adb() -> Result<()> {
+ let debug_config = DebugConfig::from_custom_debug_overlay_policy(
+ DebugLevel::FULL,
+ "avf_debug_policy_without_adb.dtbo".as_ref(),
+ )
+ .unwrap();
+
+ assert_eq!(DebugLevel::FULL, debug_config.debug_level);
+ assert!(!debug_config.debug_policy_log);
+ assert!(!debug_config.debug_policy_ramdump);
+ assert!(!debug_config.debug_policy_adb);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_invalid_sysprop_disables_debug_policy() -> Result<()> {
+ let debug_config = DebugConfig::from_custom_debug_overlay_policy(
+ DebugLevel::NONE,
+ "/a/does/not/exist/path.dtbo".as_ref(),
+ )
+ .unwrap();
+
+ assert_eq!(DebugLevel::NONE, debug_config.debug_level);
+ assert!(!debug_config.debug_policy_log);
+ assert!(!debug_config.debug_policy_ramdump);
+ assert!(!debug_config.debug_policy_adb);
+
+ Ok(())
+ }
+
+ fn test_new_with_custom_policy_internal() -> Result<()> {
+ let debug_config = DebugConfig::new(DebugLevel::NONE);
+
+ ensure!(debug_config.debug_level == DebugLevel::NONE);
+ ensure!(!debug_config.debug_policy_log);
+ ensure!(!debug_config.debug_policy_ramdump);
+ ensure!(debug_config.debug_policy_adb);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_new_with_custom_policy() -> Result<()> {
+ if !can_set_sysprop() {
+ // Skip test if we can't override sysprop.
+ return Ok(());
+ }
+
+ // Setup
+ let old_sysprop = system_properties::read(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP)
+ .context("Failed to read existing sysprop")?
+ .unwrap_or_default();
+ let file_name = "avf_debug_policy_with_adb.dtbo";
+ system_properties::write(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP, file_name)
+ .context("Failed to set sysprop")?;
+
+ // Run test
+ let test_result = test_new_with_custom_policy_internal();
+
+ // Clean up.
+ system_properties::write(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP, &old_sysprop)
+ .context("Failed to restore sysprop")?;
+
+ test_result
+ }
}