Merge "Always shutdown the VM when onDeath() os received"
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 03f832d..84129b6 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -49,7 +49,10 @@
 pub type Inode = u64;
 type Handle = u64;
 
-const DEFAULT_METADATA_TIMEOUT: Duration = Duration::from_secs(5);
+/// Maximum time for a file's metadata to be cached by the kernel. Since any file and directory
+/// changes (if not read-only) has to go through AuthFS to be trusted, the timeout can be maximum.
+const DEFAULT_METADATA_TIMEOUT: Duration = Duration::MAX;
+
 const ROOT_INODE: Inode = 1;
 
 /// `AuthFsEntry` defines the filesystem entry type supported by AuthFS.
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index ae4a29d..6049991 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -22,12 +22,9 @@
 use std::collections::HashMap;
 use std::env;
 use std::ffi::OsString;
-use std::fs::read_dir;
 use std::path::{self, Path, PathBuf};
 use std::process::Command;
 
-use crate::artifact_signer::ArtifactSigner;
-use crate::signing_key::DiceSigner;
 use authfs_aidl_interface::aidl::com::android::virt::fs::{
     AuthFsConfig::{
         AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation,
@@ -105,12 +102,15 @@
     system_properties::read_bool(name, false).unwrap_or(false)
 }
 
-pub fn odrefresh(
+pub fn odrefresh<F>(
     odrefresh_path: &Path,
     context: OdrefreshContext,
     authfs_service: Strong<dyn IAuthFsService>,
-    signer: DiceSigner,
-) -> Result<ExitCode> {
+    success_fn: F,
+) -> Result<ExitCode>
+where
+    F: FnOnce(PathBuf) -> Result<()>,
+{
     // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
     // is out of scope.
     let authfs_config = AuthFsConfig {
@@ -183,13 +183,8 @@
     info!("odrefresh exited with {:?}", exit_code);
 
     if exit_code == ExitCode::CompilationSuccess {
-        // authfs only shows us the files we created, so it's ok to just sign everything under
-        // the target directory.
         let target_dir = art_apex_data.join(context.target_dir_name);
-        let mut artifact_signer = ArtifactSigner::new(&target_dir);
-        add_artifacts(&target_dir, &mut artifact_signer)?;
-
-        artifact_signer.write_info_and_signature(signer, &target_dir.join("compos.info"))?;
+        success_fn(target_dir)?;
     }
 
     Ok(exit_code)
@@ -245,24 +240,6 @@
     Ok(())
 }
 
-fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
-    for entry in
-        read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
-    {
-        let entry = entry?;
-        let file_type = entry.file_type()?;
-        if file_type.is_dir() {
-            add_artifacts(&entry.path(), artifact_signer)?;
-        } else if file_type.is_file() {
-            artifact_signer.add_artifact(&entry.path())?;
-        } else {
-            // authfs shouldn't create anything else, but just in case
-            bail!("Unexpected file type in artifacts: {:?}", entry);
-        }
-    }
-    Ok(())
-}
-
 fn spawn_jailed_task(executable: &Path, args: &[String], env_vars: &[String]) -> Result<Minijail> {
     // TODO(b/185175567): Run in a more restricted sandbox.
     let jail = Minijail::new()?;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 3ec15dd..5f3ee62 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -18,17 +18,19 @@
 //! file descriptors backed by authfs (via authfs_service) and pass the file descriptors to the
 //! actual compiler.
 
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
 use binder_common::new_binder_exception;
 use compos_common::binder::to_binder_result;
 use log::warn;
 use std::default::Default;
-use std::path::PathBuf;
+use std::fs::read_dir;
+use std::path::{Path, PathBuf};
 use std::sync::RwLock;
 
+use crate::artifact_signer::ArtifactSigner;
 use crate::compilation::{odrefresh, OdrefreshContext};
 use crate::dice::Dice;
-use crate::signing_key::{DiceSigner, DiceSigningKey};
+use crate::signing_key::DiceSigningKey;
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
 use compos_aidl_interface::aidl::com::android::compos::{
     CompOsKeyData::CompOsKeyData,
@@ -57,17 +59,6 @@
     key_blob: RwLock<Vec<u8>>,
 }
 
-impl CompOsService {
-    fn new_signer(&self) -> BinderResult<DiceSigner> {
-        let key = &*self.key_blob.read().unwrap();
-        if key.is_empty() {
-            Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
-        } else {
-            to_binder_result(self.signing_key.new_signer(key))
-        }
-    }
-}
-
 impl Interface for CompOsService {}
 
 impl ICompOsService for CompOsService {
@@ -91,6 +82,14 @@
         zygote_arch: &str,
         system_server_compiler_filter: &str,
     ) -> BinderResult<i8> {
+        let key = &*self.key_blob.read().unwrap();
+        if key.is_empty() {
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_STATE,
+                "Key is not initialized",
+            ));
+        }
+
         let context = to_binder_result(OdrefreshContext::new(
             compilation_mode,
             system_dir_fd,
@@ -103,8 +102,16 @@
 
         let authfs_service = get_authfs_service()?;
         let exit_code = to_binder_result(
-            odrefresh(&self.odrefresh_path, context, authfs_service, self.new_signer()?)
-                .context("odrefresh failed"),
+            odrefresh(&self.odrefresh_path, context, authfs_service, |output_dir| {
+                // authfs only shows us the files we created, so it's ok to just sign everything
+                // under the output directory.
+                let mut artifact_signer = ArtifactSigner::new(&output_dir);
+                add_artifacts(&output_dir, &mut artifact_signer)?;
+
+                let signer = to_binder_result(self.signing_key.new_signer(key))?;
+                artifact_signer.write_info_and_signature(signer, &output_dir.join("compos.info"))
+            })
+            .context("odrefresh failed"),
         )?;
         Ok(exit_code as i8)
     }
@@ -126,3 +133,21 @@
 fn get_authfs_service() -> BinderResult<Strong<dyn IAuthFsService>> {
     Ok(authfs_aidl_interface::binder::get_interface(AUTHFS_SERVICE_NAME)?)
 }
+
+fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
+    for entry in
+        read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
+    {
+        let entry = entry?;
+        let file_type = entry.file_type()?;
+        if file_type.is_dir() {
+            add_artifacts(&entry.path(), artifact_signer)?;
+        } else if file_type.is_file() {
+            artifact_signer.add_artifact(&entry.path())?;
+        } else {
+            // authfs shouldn't create anything else, but just in case
+            bail!("Unexpected file type in artifacts: {:?}", entry);
+        }
+    }
+    Ok(())
+}
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index bc8a4a5..10bcbf4 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -16,5 +16,9 @@
         "VirtualizationTestHelper",
     ],
     per_testcase_directory: true,
-    data: [":MicrodroidTestApp"],
+    data: [
+        ":MicrodroidTestApp",
+        ":microdroid_general_sepolicy.conf",
+    ],
+    data_native_bins: ["sepolicy-analyze"],
 }
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index e15f1ae..e3f1968 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -149,9 +149,26 @@
     }
 
     public static CommandResult runOnMicrodroidForResult(String... cmd) {
-        final long timeout = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+        final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
         return RunUtil.getDefault()
-                .runTimedCmd(timeout, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+                .runTimedCmd(timeoutMs, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+    }
+
+    public static void pullMicrodroidFile(String path, File target) {
+        final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+        CommandResult result =
+                RunUtil.getDefault()
+                        .runTimedCmd(
+                                timeoutMs,
+                                "adb",
+                                "-s",
+                                MICRODROID_SERIAL,
+                                "pull",
+                                path,
+                                target.getPath());
+        if (result.getStatus() != CommandStatus.SUCCESS) {
+            fail("pulling " + path + " has failed: " + result);
+        }
     }
 
     // Asserts the command will fail on Microdroid.
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 6aa7566..25adc40 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -17,18 +17,24 @@
 package android.virt.test;
 
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RunUtil;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.util.Optional;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
@@ -43,6 +49,16 @@
     private static final int NUM_VCPUS = 3;
     private static final String CPU_AFFINITY = "0,1,2";
 
+    // TODO(b/176805428): remove this
+    private boolean isCuttlefish() throws Exception {
+        String productName = getDevice().getProperty("ro.product.name");
+        return (null != productName)
+                && (productName.startsWith("aosp_cf_x86")
+                        || productName.startsWith("aosp_cf_arm")
+                        || productName.startsWith("cf_x86")
+                        || productName.startsWith("cf_arm"));
+    }
+
     private int minMemorySize() throws DeviceNotAvailableException {
         CommandRunner android = new CommandRunner(getDevice());
         String abi = android.run("getprop", "ro.product.cpu.abi");
@@ -103,6 +119,31 @@
         assertThat(runOnMicrodroid("cat /proc/cpuinfo | grep processor | wc -l"),
                 is(Integer.toString(NUM_VCPUS)));
 
+        // TODO(b/176805428): adb is broken for nested VM
+        if (!isCuttlefish()) {
+            // Check neverallow rules on microdroid
+            File policyFile = FileUtil.createTempFile("microdroid_sepolicy", "");
+            pullMicrodroidFile("/sys/fs/selinux/policy", policyFile);
+
+            File generalPolicyConfFile = findTestFile("microdroid_general_sepolicy.conf");
+            File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze");
+
+            CommandResult result =
+                    RunUtil.getDefault()
+                            .runTimedCmd(
+                                    10000,
+                                    sepolicyAnalyzeBin.getPath(),
+                                    policyFile.getPath(),
+                                    "neverallow",
+                                    "-w",
+                                    "-f",
+                                    generalPolicyConfFile.getPath());
+            assertEquals(
+                    "neverallow check failed: " + result.getStderr().trim(),
+                    result.getStatus(),
+                    CommandStatus.SUCCESS);
+        }
+
         shutdownMicrodroid(getDevice(), cid);
     }